Dragon

How a Dragon Program Runs

Most compiled languages make you name an entry point. Go insists on func main() inside package main. Java wants public static void main(String[] args) with the signature exactly so. C needs a main that returns int. Python splits the difference with a convention rather than a rule - the if __name__ == "__main__": guard that every script grows so it can tell "run me" from "import me."

Dragon throws the whole ceremony out. The file you hand to dragon run or dragon build is the program, and its top-level statements are the program body. There is no entry function, no magic name, no __name__ guard. You write statements at the top level of a file, and they execute, top to bottom, when you run that file. That's the entire model.

def greet(name: str) -> str {
    return f"Hello, {name}!"
}

print(greet("Dragon"))   # Hello, Dragon!

Run that file and it prints Hello, Dragon!. The print is not inside any function - it is the program. The def above it contributes a definition that the print then uses.

No name is special

Because the file is the entry point, no function name carries any privilege. A function called main is just a function; it runs only if something calls it:

def setup() -> None { print("2. setup() called explicitly") }
def main() -> None { print("3. main() called explicitly") }

print("1. top-level: program starts")
setup()
main()
print("4. top-level: program ends")
1. top-level: program starts
2. setup() called explicitly
3. main() called explicitly
4. top-level: program ends

main ran only because the top-level code called it - exactly like setup. Remove the call and it never runs at all. This is the trap to be aware of coming from Go or Java: defining main and expecting it to fire on its own is a silent no-op.

def main() -> None {
    print("this never runs - nothing calls it")
}

print("the file is the program")   # the only line that executes

There is no __name__ either. Dragon has no dual "am I the main module or an import" identity to disambiguate, because top-level execution is the only model - so the guard it would protect doesn't exist. Writing print(__name__) is a compile error (Undefined variable: __name__), not a runtime surprise. If you're porting Python, delete the if __name__ == "__main__": line and de-indent its body: that body simply is your top-level program.

Two surface modes, one language

Dragon reads two file extensions. .dr is the canonical surface - curly-brace blocks, def() constructors, the works. .py is an adoption ramp for Python developers - indentation-based blocks and def __init__(self) - but it is the same language underneath, with the same mandatory type annotations and the same scoping rules. It is not "optional typing for Python files"; the types are enforced just as strictly.

# point.dr  - canonical surface
class Point {
    def(x: float, y: float) {
        self.x = x
        self.y = y
    }
}

const p: Point = Point(3.0, 4.0)
print(f"({p.x}, {p.y})")     # (3.0, 4.0)
# point.py  - adoption surface, identical semantics
class Point:
    x: float
    y: float

    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

p: Point = Point(3.0, 4.0)
print(f"({p.x}, {p.y})")     # (3.0, 4.0)

Both compile to the same thing and run identically. The differences are purely syntactic plus the constructor spelling (def() versus def __init__(self)). Constructors are covered in Classes and Objects.

What an import contributes

If the file you run is the program, what is every other file? An import. An imported module contributes its definitions - functions, classes - and runs its module-level variable initializers, so the constants and tables it declares are ready when your code calls into it:

# util.dr  (imported)
factor: int = 6                      # initializer runs - `factor` is ready

def multiply(n: int) -> int {
    return n * factor
}
# main.dr  (the file you run)
from util import multiply

print(multiply(7))                   # 42
dragon run main.dr      # 42

The rule of thumb that follows: put a module's definitions and constants at its top level, but keep program behavior - the statements that do the work - in the file you actually run. The full resolution rules, package layout, and the global keyword for writing module-level state live in Modules and Packages and Variables, Constants, and Statics.

Source to native binary

There is no interpreter, no bytecode VM, and no JIT warm-up between your source and the CPU. dragon build compiles your file (and everything it imports) ahead of time into a standalone native executable; dragon run does the same to a temporary binary, runs it, and cleans up. What executes is machine code. The mechanics of those subcommands - run, build, check, the optimization flags, and the module search path - are the subject of The dragon CLI.

At a glance

FactConsequence
The file you run is the programtop-level statements execute top to bottom
No magic maina def main runs only if called; uncalled, it's a no-op
No __name__if __name__ == "__main__": is unnecessary - delete it, de-indent the body
.dr and .py are one languagesame types, same scoping; only surface syntax differs
Imports contribute definitions + run their initializerskeep behavior in the entry file
Source compiles to a native binaryno interpreter, VM, or JIT

With the toolchain and the execution model both in hand, Part 2 begins the language proper - variables and constants, the built-in types, operators, comments, and control flow.