Fortran
The scientific-computing style works here too.
How to run it on your phone
vml run examples/fortran/test/sysinfo.f90
Compiling takes a while (one to two minutes for C, a few seconds for scripting languages). Once the program is running, the gamepad is at the bottom of the screen - the D-pad and the four action buttons are all there. Tap the back arrow in the top-right corner to return to the Shell.
What to know before you write
- The entry point is
program p ... end program p - Use
callfor procedures with no return value
Examples
| File | What it shows |
|---|---|
app/sokoban.f90 | Sokoban (two-array state plus movement rules plus level-complete check) |
test/bench.f90 | Benchmark |
test/sysinfo.f90 | Device information |
Also in this folder: demo/demo_std.f90, math/main.f90, test/libs.f90, tty/demo_tty.f90, ui/demo_ui.f90
Pitfalls we hit on real devices
- An
ifcondition cannot parse a division right after a bracket: compute it into a variable first.
The rest of this page is taken straight from the VML source (third_party/vml/VMLPrepares/FortranCompiler/): the README covers what this front end supports and how to compile it, and the language reference covers the syntax itself. When upstream changes, regenerating this page brings it up to date.
Language reference
Fortran language compiler specification
Version: v1.0 | Date: 2026-07-06 | Revised by: Shenzhen Tanso Intelligent Technology Co., Ltd.
Metadata
| Attribute | Value |
|---|---|
| Standard | Fortran 90/95 subset |
| First released | 1957 (Fortran 90: 1991) |
| Completeness | ~98% |
| File extensions | .f90, .f, .f95, .f03, .f08, .for, .ftn |
| Lines of code | ~1,590 lines of C# |
Overview
Fortran (FORmula TRANslation) was the world's first high-level programming language, designed for scientific computing and numerical analysis. This compiler implements a subset of Fortran 90/95 and supports free-form source form.
Core characteristics:
- Case insensitive — every identifier and keyword is stored internally in lowercase
- Free-form source — no fixed column limits, statements can span lines
- Column-form comments —
*, orC/cin column 1, marks a comment line (fixed-form compatible) - MCU-safe subset — asynchronous features, threads, GC and other unsupported features are skipped by default
Supported features
1. Data types
| Type keyword | Internal mapping | Description |
|---|---|---|
integer | integer (i32) | 32-bit signed integer |
real | real (float) | 32-bit IEEE 754 single-precision float |
double precision | real (float) | Mapped to 32-bit float (VML has no native f64 hardware support; a soft-float library is required) |
complex | complex | Complex type (syntax reserved, runtime support is limited) |
logical | logical (i32) | Logical value, stored internally as the integer 0/1 |
character | character | String type (address loaded through MOVE R0, label) |
Note: The
doublekeyword is recognized but mapped toreal(single precision).doubleprecision(without a space) is also mapped toreal. Full double-precision float support requires theVML_FLOAT64_SOFTmacro to be enabled so the soft-float library is linked.
2. Program structure
program program_name
! declaration section
integer :: x, y
real :: pi = 3.14159
! executable section
x = 10
call mysub(x)
contains
subroutine mysub(n)
integer :: n
print *, 'n = ', n
end subroutine mysub
function add(a, b) result(sum)
integer :: a, b
integer :: sum
sum = a + b
end function add
end program program_name
Supported top-level constructs:
program name/end program [name]— main program (theprogramheader is optional; the default name ismain)subroutine name(params)/end subroutine [name]— subroutine (no return value)function name(params) result(resultVar)/end function [name]— function (has a return value)contains— separates the main program body from internal procedure definitions
Syntax notes:
- The keyword after
end(program/subroutine/function) and the name are both optional resultclause:function square(x) result(y)makesythe result variable name, which affects the return value after assignments inside the function- A function's default return type is
integer; a type keyword may be placed in front of thefunctionline to declare otherwise:real function foo(x)
3. Variable declarations
! basic declaration (the :: separator is optional)
integer :: i, j, k
real :: temperature
logical :: flag
! declaration with dimension (parses, but array operations are not implemented yet)
integer :: a(10), b(20)
! parameter keyword (recognized by the lexer/parser, no code generated)
parameter :: pi = 3.14159
Implementation details:
- The
::separator is optional in declarations — bothinteger xandinteger :: xare accepted - Variables are allocated on the stack frame (
R12base), 4 bytes each - A function's result variable (the name given by
result) is automatically given space on the stack frame
4. Implicit and explicit typing
The compiler recognizes an implicit none statement and skips it. By default, declaring implicit none at the top of a program is recommended in order to disable Fortran's implicit typing rules (which treat I-N initial letters as integer and the rest as real).
Note: The current compiler does not enforce explicit declarations — an undeclared variable is automatically allocated as a global data-segment variable when it is used. This differs from standard Fortran behavior.
5. Operators
| Precedence | Operator | Meaning | Description |
|---|---|---|---|
| Lowest | .or. | logical or | short-circuit evaluation |
.and. | logical and | short-circuit evaluation | |
== / .eq. | equal to | both C style and Fortran style work | |
/= / .ne. | not equal to | both C style and Fortran style work | |
< / .lt. | less than | ||
> / .gt. | greater than | ||
<= / .le. | less than or equal | ||
>= / .ge. | greater than or equal | ||
+ | addition | ||
- | subtraction | ||
* | multiplication | ||
/ | division | ||
| Highest | ** | exponentiation | currently simplified to * (no native POW instruction) |
Logical operators (only the .dot. form is supported):
.not.— logical not (unary prefix).and.— logical and.or.— logical or.eqv.— logical equivalence (the lexical keyword is registered).neqv.— logical non-equivalence (the lexical keyword is registered)
Note:
.eqv.and.neqv.are recognized by the Lexer as keywords, but no dedicated code generation has been implemented in the current CodeGenerator. Likewise, comparison operators support both the Fortran style (.eq.,.ne.,.lt.,.gt.,.le.,.ge.) and the C style (==,/=,<,>,<=,>=).
6. Assignment
x = 42 ! correct: Fortran uses = rather than ==
flag = .true. ! logical assignment
y = x ** 2 + 1 ! expression assignment
Assignment uses the = operator (not ==). == is the equality comparison operator.
7. Control flow
IF statements
if (x > 0) then
print *, 'positive'
else if (x < 0) then
print *, 'negative'
else
print *, 'zero'
end if
if (cond) then/else if (cond) then/else/end if- The condition is wrapped in parentheses
() - The
thenkeyword must appear after the condition else ifis implemented as a chain by nesting the following IF node insideelseBody
DO WHILE loops
i = 1
do while (i <= 10)
print *, i
i = i + 1
end do
- The loop body sits between
do while (cond)andend do - The condition is evaluated before each iteration (an entry-condition loop)
- The loop counter is not incremented automatically — it must be updated by hand
Counted DO loops
do i = 1, 10
print *, i
end do
do j = 10, 1, -1 ! step = -1
print *, j
end do
- Syntax:
do var = start, end [, step] - The loop body sits between
doandend do - The loop variable is allocated on the stack frame and keeps its final value after the loop ends
- Restriction: step only supports integer literals (such as
1,-1), not expressions - The loop condition is
var <= end(correct for a positive step; behavior for a negative step may not match standard Fortran)
8. Subroutine and function calls
CALL statement (calling a subroutine)
call mysub(arg1, arg2)
call print_values(x, y, z)
- Argument push order: right to left (
__cdeclstyle) - Arguments are popped after the call
Function calls (inside expressions)
result = add(a, b) ! function call as an expression
x = square(y) + offset ! nested inside an expression
- A function name followed by
(args)triggers a function call - The return value is passed through the
R0register
9. Built-in functions (SYSCALL)
The Fortran compiler provides basic I/O through VML system calls:
| print argument type | SYSCALL | Description |
|---|---|---|
| String literal | SYSCALL 1 | Print a string (R0 = label address, MOVE semantics) |
| Integer/variable | SYSCALL 5 | Print an integer (R0 = value) |
| Spacing | SYSCALL 4 | Print a single character (space separator) |
| Newline | SYSCALL 4 | Print \n |
Shared built-in runtime functions (provided by Lib/shared/builtins.vml): peek / poke / putchar / abs / min / max / random / sleep / alloc.
The stop [code] statement calls the exit SYSCALL (SYSCALL 3) with an optional exit code (0 by default).
10. Logical literals
| Literal | Internal value | Description |
|---|---|---|
.true. / true | 1 (i32) | both the Fortran style and the simplified form work |
.false. / false | 0 (i32) | both the Fortran style and the simplified form work |
The lexer also accepts
true/false(without the surrounding dots) as keywords, but standard Fortran should use.true./.false..
11. Comments
! this is a line comment (supported in both free form and fixed form)
program main
integer :: x ! end-of-line comment
end program
Fixed-form compatibility:
* an asterisk in column 1 marks a comment line
C or c in column 1 marks a comment line
12. String literals
print *, 'Hello, World!'
print *, 'It''s Fortran' ! two single quotes escape to one
- Single-quoted strings:
'text' - Double-quoted strings (a non-standard extension):
"text" - Internal escaping:
''means one literal single quote - Strings are allocated in the data segment and their address is loaded into
R0withMOVE R0, label
13. Continuation lines
In free form, & continues a line:
x = a + b + &
c + d
The lexer's handling of & and continuation-line behavior is dealt with by the preprocessing stage.
Expression precedence
From lowest to highest:
1. .or. (logical or)
2. .and. (logical and)
3. == /= < > <= >= (comparison)
.eq. .ne. .lt. .gt. .le. .ge.
4. + - (add/subtract)
5. * / ** (multiply/divide/power)
6. - + .not. (unary minus/plus/logical not)
7. literals / variables / function calls / ( expr )
The parsing implementation matches:
ParseLogicalOr → .or.
ParseLogicalAnd → .and.
ParseComparison → == /= < > <= >=
ParseAdditive → + -
ParseMultiplicative → * / **
ParseUnary → - + .not.
ParsePrimary → literals / variables / function calls / ( )
Preprocessing
The compiler integrates CompilerBase.Preprocessor, which supports the following:
Predefined macros
| Macro | Value | Description |
|---|---|---|
__VML__ | 1 | VML toolchain identifier |
__VML_VERSION__ | "1.65.32" | VML version number |
__FORTRAN__ | 1 | Fortran compiler identifier |
__DATE__ | "Jun 01 2026" | Compilation date (English format) |
__TIME__ | "HH:mm:ss" | Compilation time |
Module import
use math_lib ! import the module math_lib
- The
use module_namesyntax triggers a module import - The compiler resolves
.vmlmodule files from the source directory and the library paths - The
Lib/fortran/builtin.vmlruntime library is linked automatically
Conditional compilation
#ifdef __VML__
print *, 'VML compiler'
#endif
All standard C preprocessor directives are supported: #define, #ifdef, #ifndef, #if, #else, #elif, #endif, #include, #undef.
Limitations and unimplemented features
Keywords registered in the Lexer/Token but not implemented in the CodeGenerator
| Keyword | Status | Description |
|---|---|---|
module / use / only | syntax skipped / module import | use is only used for library imports; module blocks are not supported |
dimension / allocatable | recognized by the lexer | Array dimension declarations are merely parsed and skipped |
implicit / none | recognized by the parser (NopNode) | Parsed as a no-op; explicit declarations are not enforced |
parameter | recognized by the lexer | Constant declarations generate no code |
intent / in / out / inout | recognized by the lexer | A parameter's intent attribute is ignored |
write / read | recognized by the lexer | Only print *, is implemented (treated as write) |
.eqv. / .neqv. | recognized by the lexer | Code generation is not implemented |
complex | recognized by the lexer | Complex arithmetic is not implemented |
Core unsupported features
| Feature | Description |
|---|---|
| Array operations | Declaration syntax is parsed and skipped; whole-array operations, array sections and WHERE/FORALL are not supported |
| Module system | module/end module blocks are not supported |
| Derived types | type/end type user-defined types are not supported |
| Pointers | The pointer attribute is not supported |
| Interface blocks | interface/end interface are not supported |
| FORMAT statements | Formatted I/O is not supported; only print *, (list-directed output) is |
| READ/WRITE | Only print *, is available; read * is not supported |
| COMMON blocks | Common blocks are not supported; variables are allocated on the stack frame |
| ENTRY | Multiple entry points are not supported |
| SAVE | The variable persistence attribute is not supported |
| DATA | DATA initialization statements are not supported |
| IMPLICIT rules | Explicit declarations (implicit none) are recommended but not enforced |
| Double precision | double precision is mapped to single-precision float |
Exponentiation «/»** | Simplified to multiplication, not real exponentiation |
| Negative DO step | The loop condition <= behaves incorrectly for a negative step |
| Array parameters | Subroutines/functions cannot accept arrays as parameters |
Compiler architecture
source file (.f90/.f)
│
▼
Lexer.cs ─────────── lexing (case insensitive)
│ Keywords: 65 keywords + operators
│ Comments: ! line comment, * C/c in column 1
│ Strings: '...' and "..." ('' escape)
│ Numbers: integer / real / scientific notation (E/D) / kind (_4)
▼
Parser.cs ────────── recursive-descent parsing
│ Statements: program/subroutine/function/if/do while/do for/call/print
│ Expressions: 7 precedence levels (Pratt style)
│ Declarations: type [::] name [, name]*
▼
ASTNode.cs ───────── abstract syntax tree
│ ProgramNode / SubroutineNode / FunctionNode
│ IfNode / DoWhileNode / ForNode
│ CallNode / ReturnNode / StopNode / PrintNode
│ AssignNode / BinaryNode / UnaryNode / FuncCallNode
│ LiteralNode / VarNode / VarDeclNode
▼
CodeGenerator.cs ─── VML code generation
│ Extends CodeGeneratorBase
│ Stack frame management: R12 base + nextStackOffset
│ Symbol table: Dictionary<string, int> (name → stack offset)
│ Expressions: ExpressionManager three-operand form
▼
FortranCompiler.cs ─ compiler entry point
│ Compile(string) → VmlProgram
│ CompileFile(path) → VmlProgram (with preprocessing + library linking)
│ Preprocessing: Preprocessor + predefined macros
│ Library linking: use statements + builtin.vml
Calling convention
- Subroutine calls (CALL): arguments PUSHed right to left, jumped to with the
CALLinstruction - Function calls (expressions): arguments PUSHed right to left, jumped to with the
CALLinstruction, return value in theR0register - Internal label naming:
- Subroutines:
sub_<name>(lowercase) - Functions:
func_<name>(lowercase)
- Subroutines:
- Return: the
RETinstruction (a subroutine returns void; a function has its result already loaded intoR0when it returns)
Library structure
Lib/fortran/
builtin.vml ← linked automatically by the compiler
├── .linked "../shared/builtins.vml" (peek/poke/putchar/abs/min/max/random/sleep/alloc)
├── .linked "../shared/device.vml" (device operations)
├── .linked "../shared/sysinfo.vml" (date/time/exit)
├── .linked "../shared/softfloat.vml" (VML_FLOAT32_SOFT)
├── .linked "../shared/softdouble.vml" (VML_FLOAT64_SOFT)
├── .linked "../shared/softint64.vml" (VML_INT64_SOFT)
├── .linked "../shared/float.vml" (basic floating point)
└── .linked "../shared/syscall.inc.vml" (system call constants)
Minimal complete program example
program hello
implicit none
integer :: i
do i = 1, 5
print *, 'Hello Fortran!', i
end do
contains
function square(x) result(y)
integer :: x
integer :: y
y = x * x
end function square
end program hello
🆕 String encoding (v1.65.19)
This language compiler uses the VML string system indirectly through the shared library (Lib/shared/).
| Directive | Width | Encoding | C type |
|---|---|---|---|
.string | 8-bit | UTF-8 | char* |
.wstring | 16-bit | UTF-16LE | wchar_t* |
.ustring | 32-bit | UTF-32LE | char32_t* |
MCU mode (default): strings are output as UTF-8 (.string) OS mode: the encoding can be determined through the VML_WSTRING macro
The shared library already provides wide-string conversion functions (wchar.h/uchar.h), which each language compiler can use as needed.
Compiler README
Fortran compiler
Path: VMLPrepares/FortranCompiler/ Completeness: ~98% | 🟢 production ready Standard library: Lib/fortran/
Features
- ✅ Lexing + parsing + code generation (Lexer/Parser/CodeGenerator)
- ✅ program/subroutine/function structure
- ✅ contains internal procedures
- ✅ function name(args) result(r) syntax
- ✅ Control flow (if/else/do/do while/exit/cycle)
- ✅ Data types (integer/real/double precision/logical/character)
- ✅ Comparison operators (.eq./.ne./.lt./.gt./.le./.ge.)
- ✅ Logical operators (.and./.or./.eqv./.neqv.)
- ✅ print */read */write */call/return/stop
- ✅ ! comments
- ✅ 24 tests, 0 failures
- ✅ Array sections / whole-array operations (A(:)=B(:)+C(:)) — MCU loop unrolling
- ✅ module/use — module system
- ✅ else if chains — recursive parsing support
- ✅ C-style preprocessing (#ifdef/#ifndef/#define/#include)
Compilation modes
MCU mode (default --mode mcu)
MCU mode is tuned for microcontrollers and bare-metal environments.
Skipped (not supported by the MCU):
- None
- Dynamic array allocation
Kept:
- Basic numeric computation
- Subroutine/function calls
- print/write mapped to the UART
OS mode (--mode os)
OS mode supports every language feature.
RAM levels
--ram k: kilobyte level--ram m: megabyte level (default)--ram g: gigabyte level
Usage
dotnet run --project VMLTool -- input.f90 -o output.vml
Tests
VMLTests/NewCompilerTests.cs— 24 tests (0 failures)VMLTests/FullPipelineTests.cs— full pipeline testsExamples/fortran/— example programs (start/factorial/file_io/info)