- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
Active Oberon 是 Pascal / Modula-2 / Oberon 语言家族的最后一位成员,由苏黎世联邦理工学院(ETH Zürich)以尼克劳斯·维尔特(Niklaus Wirth)和约尔格·古特克内希特(Jürg Gutknecht)为核心的小组于 1996–1998 年设计。它完整保留了 Oberon 的一切经典特质——一页纸即可写下的语法、带显式导出的模块、强静态类型与垃圾回收——并在此基础上加入了其名称所指向的核心能力:活动对象(active objects)。本指南以仓库中的 uk/active-oberon.md(乌克兰语版)、active-oberon.md(英文原版)与 ru/active-oberon.md(俄语版)为骨架,带你从零读懂一份可直接被当前 A2 系统 Fox 编译器接受的完整示例模块,掌握类型系统、控制流、对象模型、语言级并发、数学数组与命令扩展等全部要点。
语言定位:为什么并发是"语法"而不是"库"
在绝大多数主流语言里,线程、锁与条件变量是标准库或第三方库的产物;而在 Active Oberon 中,并行是语言的一部分:
- 对象可以携带自己的线程——在对象体上写
BEGIN {ACTIVE}; - 任意语句块都可以声明为临界区——
BEGIN {EXCLUSIVE}; AWAIT(条件)直接取代条件变量,成为语言内置的等待原语。
整套 A2 操作系统——内核、驱动、编译器、网络栈与图形界面——全部用 Active Oberon 写成,因此 A2 本身就是尝试这门语言最自然的环境。除并发外,语言还扩展了运算符重载、枚举类型、带逐元素运算的数学(张量)数组,以及用于硬件描述的 cell(单元)机制。
需要说明的版本前提:下文代码面向A2 当前的 Fox 编译器。更早期的 A2 / Bluebottle 源码使用历史类型名(LONGINT、LONGREAL、SHORTINT),而现代代码统一写作SIGNED32、FLOAT64、SIGNED8。
完整示例:LearnActiveOberon 模块
下面这份代码就是本指南的主体,它同时是仓库文档的完整继承。请将它保存为LearnActiveOberon.Mod(文件命名与模块名一致),即可在 A2 系统中编译运行:
(* Коментарі виглядають так, і (* вони вкладаються *). *) (** Коментар із двома зірочками — це документація, її збирають документаційні інструменти. *) (* Одиниця компіляції — це завжди MODULE. Ключові слова пишуть великими літерами, ідентифікатори чутливі до регістру, а ім'я модуля повторюється після останнього END. Файл зазвичай називають так само, як модуль. *) MODULE LearnActiveOberon; (* Домовленість про регістр: сканер читає ключові слова незалежно від регістру, а стиль файлу визначає його розширення. Файл X.Mod пишуть ВЕЛИКИМИ літерами, файл x.mod — малими, разом із наперед оголошеними типами та вбудованими процедурами: module HelloLower; import KernelLog; procedure Do*; var i: signed32; begin KernelLog.String("hi"); KernelLog.Ln end Do; end HelloLower. *) IMPORT KernelLog, (* вивід у системний журнал *) Streams, (* абстракція Reader / Writer *) Commands, (* контекст команди: ввід, вивід, аргументи *) Log := KernelLog; (* імпорт можна перейменувати *) CONST Max* = 100; (* "*" експортує ідентифікатор *) Greeting = "Hello"; (* без позначки: видно лише в цьому модулі *) EvenBits = {0, 2, 4, 6}; (* константа-множина *) TYPE Name* = ARRAY 32 OF CHAR; (* рядок, що завершується 0X *) Handler* = PROCEDURE {DELEGATE} (n: SIGNED32); Point* = RECORD x*, y*: SIGNED32; END; Point3d* = RECORD (Point) (* розширення запису *) z*: SIGNED32; END; Color* = ENUM Red*, Green*, Blue* END; (* перелічуваний тип *) Vec2* = RECORD x*, y*: FLOAT64 END; VAR counter-: SIGNED32; (* "-" експортує лише для читання *) origin: Point; (* ------------------------------------------------------------------ *) (* Типи, літерали та оператори *) (* ------------------------------------------------------------------ *) PROCEDURE Basics; VAR b: BOOLEAN; c: CHAR; i8: SIGNED8; i16: SIGNED16; i32: SIGNED32; i64: SIGNED64; u32: UNSIGNED32; f32: FLOAT32; f64: FLOAT64; z: COMPLEX64; s: SET; (* невелика бітова множина *) adr: ADDRESS; size: SIZE; (* цілі розміру вказівника *) any: ANY; (* посилання на будь-який об'єкт *) BEGIN (* INTEGER, REAL і SET — варіанти типової ширини; код A2 зазвичай вказує ширину явно: SIGNED32, FLOAT64, SET32, ... *) i32 := 42; i32 := 2AH; i32 := 0x2A; i32 := 0b101010; i32 := 1'000'000; (* роздільники розрядів дозволені *) i8 := -1; i16 := 1000; i64 := 1; u32 := 0FFFFFFFFH; c := "A"; c := 41X; (* символ, шістнадцятковий символ *) f32 := 1.5; f64 := 1.5E-3; z := 1 + 2*IMAG; (* IMAG — уявна одиниця *) f64 := RE(z); f64 := IM(z); (* Арифметика: + - * / DIV MOD, цілочисельне ділення — це DIV *) i32 := 7 DIV 2; i32 := 7 MOD 2; f64 := 7 / 2; i32 := ABS(-3); INC(i32); DEC(i32, 2); (* Відношення: = # < <= > >= IN IS. Логічні: & OR ~ (не) *) b := (i32 = 5) OR (i32 # 6) & ~(i32 < 0); (* Множини *) s := {0, 3, 5..7}; s := s + {1}; s := s * EvenBits; s := s - {3}; b := 3 IN s; INCL(s, 9); EXCL(s, 0); (* Перетворення типів завжди явні *) i32 := ORD("A"); c := CHR(65); f64 := i32; i32 := ENTIER(f64); adr := ADDRESSOF(i32); size := SIZEOF(Point); any := NIL; ASSERT(i32 >= 0); (* пастка (trap), якщо умова хибна *) END Basics; (* ------------------------------------------------------------------ *) (* Керування виконанням *) (* ------------------------------------------------------------------ *) PROCEDURE ControlFlow(n: SIGNED32): SIGNED32; VAR i, sum: SIGNED32; c: CHAR; color: Color; BEGIN IF n < 0 THEN n := -n ELSIF n = 0 THEN n := 1 ELSE (* нічого *) END; c := "x"; CASE c OF "a".."m": sum := 1 | "n".."z": sum := 2 ELSE sum := 0 END; color := Color.Green; (* елементи переліку кваліфіковані *) IF color = Color.Green THEN sum := sum + 1 END; WHILE n > 1 DO n := n DIV 2 END; REPEAT INC(n) UNTIL n >= 4; FOR i := 0 TO 10 BY 2 DO sum := sum + i END; LOOP INC(i); IF i > 100 THEN EXIT END END; VAR local := sum + 1; (* оголошення VAR усередині блоку *) RETURN local END ControlFlow; (* ------------------------------------------------------------------ *) (* Масиви, рядки, записи та вказівники *) (* ------------------------------------------------------------------ *) TYPE IntArray* = POINTER TO ARRAY OF SIGNED32; (* динамічний масив *) PointPtr* = POINTER TO Point; PROCEDURE Sum(CONST a: ARRAY OF SIGNED32): SIGNED32; (* відкритий масив *) VAR i: SIZE; s: SIGNED32; (* LEN повертає SIZE *) BEGIN FOR i := 0 TO LEN(a) - 1 DO s := s + a[i] END; RETURN s END Sum; PROCEDURE Swap(VAR a, b: SIGNED32); (* VAR = за посиланням *) VAR t: SIGNED32; BEGIN t := a; a := b; b := t END Swap; PROCEDURE Aggregates; VAR fixed: ARRAY 4 OF SIGNED32; matrix: ARRAY 3, 3 OF FLOAT64; (* багатовимірний *) dyn: IntArray; name: Name; p: Point3d; pp: PointPtr; BEGIN fixed[0] := 1; fixed[1] := 2; matrix[1, 2] := 0.5; NEW(dyn, 10); (* довжина відома під час виконання *) dyn[0] := Sum(fixed); Swap(fixed[0], fixed[1]); COPY(Greeting, name); (* безпечне копіювання рядка *) Strings0(name); p.x := 1; p.y := 2; p.z := 3; (* Point3d успадковує x та y *) origin.x := p.x; origin.y := p.y; NEW(pp); (* збирач сміття, звільняти не треба *) pp.x := 10; pp := NIL; END Aggregates; PROCEDURE Strings0(VAR name: Name); VAR i: SIGNED32; BEGIN i := 0; WHILE name[i] # 0X DO INC(i) END; (* рядки завершуються 0X *) Log.String(name); Log.Int(i, 1); Log.Ln END Strings0; (* ------------------------------------------------------------------ *) (* Процедури, делегати та обробка пасток *) (* ------------------------------------------------------------------ *) PROCEDURE Outer(n: SIGNED32): SIGNED32; PROCEDURE Inner(k: SIGNED32): SIGNED32; (* вкладена, бачить n *) BEGIN RETURN k * n END Inner; BEGIN RETURN Inner(2) END Outer; PROCEDURE Print(n: SIGNED32); BEGIN Log.Int(n, 1); Log.Ln END Print; PROCEDURE UseDelegate; VAR h: Handler; BEGIN h := Print; (* звичайна процедура *) h(42); (* DELEGATE може містити й метод разом з його об'єктом. *) END UseDelegate; PROCEDURE MayTrap(a, b: SIGNED32): SIGNED32; VAR result: SIGNED32; BEGIN result := a DIV b; (* пастка, якщо b = 0 *) RETURN result FINALLY (* виконується також після пастки *) RETURN 0 END MayTrap; (* ------------------------------------------------------------------ *) (* Перевизначення операторів *) (* ------------------------------------------------------------------ *) OPERATOR "+"* (CONST a, b: Vec2): Vec2; VAR r: Vec2; BEGIN r.x := a.x + b.x; r.y := a.y + b.y; RETURN r END "+"; (* ------------------------------------------------------------------ *) (* Об'єкти: методи, конструктор, успадкування *) (* ------------------------------------------------------------------ *) TYPE Shape* = OBJECT VAR name-: Name; PROCEDURE &Init*(CONST n: ARRAY OF CHAR); (* конструктор *) BEGIN COPY(n, name) END Init; PROCEDURE Area*(): FLOAT64; (* можна перевизначити *) BEGIN RETURN 0 END Area; PROCEDURE Describe*; BEGIN Log.String(name); Log.String(" area="); Log.Int(ENTIER(SELF.Area()), 1); Log.Ln (* SELF — цей об'єкт *) END Describe; END Shape; Circle* = OBJECT (Shape) (* одиничне успадкування *) VAR radius: FLOAT64; PROCEDURE &InitCircle*(r: FLOAT64); BEGIN Init("circle"); (* успадкований метод *) radius := r END InitCircle; PROCEDURE Area*(): FLOAT64; (* перевизначення *) BEGIN RETURN 3.14159 * radius * radius END Area; END Circle; PROCEDURE UseObjects; VAR s: Shape; c: Circle; BEGIN NEW(c, 2.0); (* NEW передає аргументи конструктора *) c.Describe; s := c; (* Circle є Shape *) s.Describe; (* динамічна диспетчеризація *) IF s IS Circle THEN (* перевірка типу під час виконання *) Log.String(s(Circle).name); Log.Ln (* охоронець типу *) END; WITH s: Circle DO (* охоронець типу для цілої області *) Log.Int(ENTIER(s.Area()), 1); Log.Ln END END UseObjects; (* ------------------------------------------------------------------ *) (* Активні об'єкти — те, за що мову названо *Active* Oberon *) (* ------------------------------------------------------------------ *) TYPE (* Об'єкт із тілом BEGIN {ACTIVE} має власний потік, який стартує одразу після створення об'єкта. {EXCLUSIVE} перетворює об'єкт на монітор: усередині такого блоку одночасно виконується лише одна активність, а AWAIT блокує до виконання умови. *) Buffer* = OBJECT VAR data: ARRAY 16 OF SIGNED32; head, tail, count: SIGNED32; alive: BOOLEAN; PROCEDURE &Init*; BEGIN head := 0; tail := 0; count := 0; alive := TRUE END Init; PROCEDURE Put*(x: SIGNED32); BEGIN {EXCLUSIVE} AWAIT(count < LEN(data)); (* чекати, доки з'явиться місце *) data[tail] := x; tail := (tail + 1) MOD LEN(data); INC(count) END Put; PROCEDURE Get*(): SIGNED32; VAR x: SIGNED32; BEGIN {EXCLUSIVE} AWAIT(count > 0); (* чекати на елемент *) x := data[head]; head := (head + 1) MOD LEN(data); DEC(count); RETURN x END Get; PROCEDURE Close*; BEGIN {EXCLUSIVE} alive := FALSE END Close; BEGIN {ACTIVE} (* тіло об'єкта — це потік *) WHILE alive DO Log.Int(SELF.Get(), 1); Log.Ln END END Buffer; PROCEDURE Producer*; VAR b: Buffer; i: SIGNED32; BEGIN NEW(b); (* тут стартує потік *) FOR i := 1 TO 5 DO b.Put(i) END; b.Close END Producer; (* Тілом критичної секції може бути ціла процедура, а простий блок BEGIN {EXCLUSIVE} ... END можна написати й усередині процедури. *) PROCEDURE Guarded(o: Buffer); BEGIN BEGIN {EXCLUSIVE} INC(counter) END END Guarded; (* ------------------------------------------------------------------ *) (* Математичні масиви з поелементними операторами *) (* ------------------------------------------------------------------ *) PROCEDURE MathArrays; VAR v: ARRAY [*] OF FLOAT64; (* одновимірний, відкритий *) m: ARRAY [*, *] OF FLOAT64; (* двовимірний *) t: ARRAY [?] OF FLOAT64; (* тензор довільного рангу *) w: ARRAY [3] OF FLOAT64; (* фіксована довжина *) f: FLOAT64; BEGIN NEW(v, 3); NEW(m, 3, 3); v := [1.0, 2.0, 3.0]; (* конструктор масиву *) v := v .* v; (* поелементний добуток *) v := 2 * v; (* скаляр поширюється на всі елементи *) m := m + m; v := m * v; (* добуток матриці на вектор *) f := SUM(v); f := v[1]; v[0..1] := v[1..2]; (* діапазони та зрізи *) Log.Int(LEN(v, 0), 1); Log.Ln; (* довжина нульового виміру *) Log.Int(DIM(m), 1); Log.Ln (* кількість вимірів *) END MathArrays; (* ------------------------------------------------------------------ *) (* Команди: процедури, які запускають з оболонки A2 *) (* ------------------------------------------------------------------ *) (* Експортовану процедуру без параметрів або з параметром Commands.Context можна запустити з командного рядка чи клацанням по будь-якому тексту в системі. *) PROCEDURE Hello*(context: Commands.Context); VAR name: Name; BEGIN context.arg.SkipWhitespace; context.arg.String(name); (* прочитати аргумент *) IF name = "" THEN COPY("world", name) END; context.out.String(Greeting); context.out.String(", "); context.out.String(name); context.out.String("!"); context.out.Ln; context.out.Update (* виштовхнути буфер *) END Hello; PROCEDURE WriteTo(w: Streams.Writer); BEGIN w.String("Streams.Writer works with files, network and screen"); w.Ln; w.Update END WriteTo; (* Тіло модуля виконується один раз, під час завантаження модуля. *) BEGIN counter := 0; KernelLog.String("LearnActiveOberon loaded"); KernelLog.Ln END LearnActiveOberon. (* Текст після останньої крапки компілятор ігнорує, тому вихідні тексти A2 традиційно завершують командами для збирання й перевірки модуля. В A2 їх виконують клацанням середньою кнопкою миші: Compiler.Compile LearnActiveOberon.Mod ~ LearnActiveOberon.Hello Active Oberon ~ LearnActiveOberon.Producer ~ System.Free LearnActiveOberon ~ *)接下来逐节剖析这份代码背后的设计。
模块、导出与导入:一切皆 MODULE
编译单元永远是MODULE,关键字习惯大写(扫描器对关键字不区分大小写),标识符则严格区分大小写,模块名要在最后一个END后重复一次,文件名通常与模块名一致。代码注释有两种:单星号(* ... *)支持嵌套;双星号(** ... **)是文档注释,会被文档工具收集。
大小写风格由文件扩展名决定:X.Mod中关键字写全大写;x.mod中写小写,包括预声明类型与内建过程(如module HelloLower; ... end HelloLower.的小写风格示例)。
IMPORT子句用来引入其他模块:KernelLog(系统日志输出)、Streams(Reader/Writer 抽象)、Commands(命令上下文:输入、输出、参数),并且支持重命名导入Log := KernelLog。从模块组织上看,这正是 Oberon 家族"模块即封装单元"哲学的延续:每个模块通过*和-后缀精确控制对外可见性。
导出标记:
*(如Max*、Point*)——导出该标识符,其他模块可见;-(如counter-)——只读导出:客户端可以读取但不能赋值;- 无标记(如
Greeting)——仅模块内部可见。
CONST中除了整数、字符串,还可以定义集合常量(EvenBits = {0, 2, 4, 6})。TYPE区展示了记录(RECORD)、记录扩展(RECORD (Point),即继承x、y后新增z)、枚举(ENUM Red*, Green*, Blue* END)与委托类型(PROCEDURE {DELEGATE} (n: SIGNED32))。
类型、字面量与运算符:宽度显式、转换强制
Basics过程展示了完整的标量类型家族:
| 类别 | 类型 | 要点 |
|---|---|---|
| 有符号整数 | SIGNED8/SIGNED16/SIGNED32/SIGNED64 | 默认宽度变体写作INTEGER,A2 代码通常显式标注宽度 |
| 无符号整数 | UNSIGNED32 | 十六进制后缀H(0FFFFFFFFH) |
| 浮点 | FLOAT32/FLOAT64 | 默认宽度变体为REAL |
| 复数 | COMPLEX64 | IMAG为虚数单位,RE(z)/IM(z)取实部虚部 |
| 字符 | CHAR | "A"或十六进制字符41X |
| 布尔 | BOOLEAN | 逻辑运算&(与)、OR(或)、~(非) |
| 集合 | SET(SET32等显式宽度) | 小型位集合 |
| 指针宽度整数 | ADDRESS/SIZE | 与指针等宽 |
| 任意引用 | ANY | 指向任意对象的引用,可赋NIL |
字面量语法非常丰富:
- 十进制、十六进制(
2AH与0x2A两种写法)、二进制(0b101010); - 允许数字分隔符
1'000'000,可读性大幅提升; - 字符十六进制写法
41X。
运算符约定值得注意:
- 整数除法是
DIV,取模是MOD,/只用于浮点除法(7 DIV 2 = 3,7 / 2 = 3.5); - 关系运算符为
=(相等)、#(不等)、< <= > >=、IN(集合成员)、IS(类型测试); - 集合运算:
+并、*交、-差,配合INCL(s, 9)/EXCL(s, 0); - 转换永远显式:
ORD("A")、CHR(65)、ENTIER(f64)(截断取整)、ADDRESSOF(i32)、SIZEOF(Point); ASSERT(i32 >= 0)在条件为假时触发陷阱(trap)——这是 Oberon 家族统一的运行时错误模型。
控制流:经典结构 + 内联 VAR
ControlFlow覆盖了全部经典结构:
IF / ELSIF / ELSE / END——注意每个分支块都以END收尾;CASE——支持区间分支("a".."m")与ELSE,分支间用|分隔;WHILE、REPEAT ... UNTIL(至少执行一次)、FOR i := 0 TO 10 BY 2(支持步长);LOOP ... EXIT——无限循环配合显式退出;- 块内联
VAR声明:VAR local := sum + 1可以在语句块中间声明局部变量,类型由初始化表达式推断,这是对 Oberon 语法的现代扩展。
枚举成员使用限定名访问(Color.Green),这正是模块化命名在类型系统内的延续。
数组、字符串、记录与指针:安全的内存模型
| 机制 | 语法 | 说明 |
|---|---|---|
| 固定数组 | ARRAY 4 OF SIGNED32 | 编译期定长 |
| 多维数组 | ARRAY 3, 3 OF FLOAT64 | matrix[1, 2]访问 |
| 开放数组参数 | ARRAY OF SIGNED32(配CONST) | LEN(a)取长度,返回SIZE |
| 动态数组 | POINTER TO ARRAY OF SIGNED32 | NEW(dyn, 10)运行时定长 |
| 字符串 | ARRAY 32 OF CHAR | 以0X结尾;COPY(src, dst)安全复制;手写遍历WHILE name[i] # 0X |
| 记录与扩展 | RECORD (Point) | 子记录继承父字段 |
| 指针 | POINTER TO Point | NEW(pp)分配;无free,由垃圾回收器管理 |
Sum展示了开放数组参数的标准用法:FOR i := 0 TO LEN(a) - 1。Swap用VAR参数实现真正的按引用传参。NEW(pp)之后直接pp := NIL无需手动释放——GC 是语言级保证。Strings0则演示了经典 Oberon 风格:字符串就是字符数组,以0X为终止符。
过程、委托与陷阱处理
- 嵌套过程:
Inner定义在Outer内部,可以读取外层参数n,形成词法闭包; - 委托(delegate):
Handler* = PROCEDURE {DELEGATE} (n: SIGNED32)类型的值既可以指向普通过程(h := Print; h(42)),也可以绑定"方法 + 其所属对象"; FINALLY子句:MayTrap在BEGIN ... FINALLY ... END中,无论正常返回还是发生陷阱(如b = 0时的除零),FINALLY块都会执行并返回0——这是语言级的"清理 + 兜底"机制,比传统语言的try/finally更简洁。
运算符重载:OPERATOR声明
OPERATOR "+"* (CONST a, b: Vec2): Vec2; VAR r: Vec2; BEGIN r.x := a.x + b.x; r.y := a.y + b.y; RETURN r END "+";运算符以过程形式定义,*导出后即可在模块外使用:对Vec2记录做a + b会逐分量相加。配合{DYNAMIC}修饰符(见下文),运算符还能在运行时动态分派。
对象模型:方法、构造器、继承与类型守卫
Shape/Circle演示了完整的面向对象语法:
- 对象类型用
OBJECT ... END声明,字段与方法写在内部; - 构造器以
&前缀标识(PROCEDURE &Init*),通过NEW(c, 2.0)把参数传给构造器; SELF等价于其他语言的this(SELF.Area());- 单继承:
Circle* = OBJECT (Shape);子类构造器可以调用继承的方法(Init("circle")); - 动态分派:
s := c后调用s.Describe会执行Circle的Area版本; - 运行时类型测试
s IS Circle与类型守卫s(Circle):在测试通过后把s当作Circle使用; - 区域类型守卫
WITH s: Circle DO ... END:在整个块内把s视为Circle,避免逐次强转。
从源码结构看(active-oberon.md),这正是 A2 系统内部大量驱动与子系统共享的基础对象模式。
活动对象:语言级并发的核心
这是 Active Oberon 区别于其他 Oberon 分支的灵魂(对应 active-oberon.md)。Buffer对象把三种并发原语浓缩在一个经典的生产者-消费者示例中:
PROCEDURE Put*(x: SIGNED32); BEGIN {EXCLUSIVE} AWAIT(count < LEN(data)); (* 等待缓冲区出现空位 *) data[tail] := x; tail := (tail + 1) MOD LEN(data); INC(count) END Put; PROCEDURE Get*(): SIGNED32; BEGIN {EXCLUSIVE} AWAIT(count > 0); (* 等待有元素可取 *) ... END Get; BEGIN {ACTIVE} (* 对象体 = 独立线程 *) WHILE alive DO Log.Int(SELF.Get(), 1); Log.Ln END END Buffer;工作机制:
{ACTIVE}使对象体成为一条独立活动(线程),在NEW(b)创建对象的那一刻立即启动;{EXCLUSIVE}把方法体声明为临界区:同一对象的任意排他块内,同时至多一条活动在执行——对象由此变成了监视器(monitor);AWAIT(条件)阻塞当前活动直到条件成立,语言运行时负责调度,无需手动管理条件变量或锁;Producer只负责Put(1..5)并Close,消费循环由对象体内的活动线程完成——线程、同步、等待全部是语言特性而非库调用。
Guarded还展示了更细粒度的用法:BEGIN {EXCLUSIVE} ... END这样的排他块可以内嵌在普通过程里,整个过程体也可以整体作为临界区。
数学数组:张量与逐元素运算
MathArrays展示了面向数值计算的新语法:
| 声明 | 含义 |
|---|---|
ARRAY [*] OF FLOAT64 | 一维开放数组 |
ARRAY [*, *] OF FLOAT64 | 二维开放数组 |
ARRAY [?] OF FLOAT64 | 任意秩的张量 |
ARRAY [3] OF FLOAT64 | 定长数组 |
配套的运算与内建函数:
- 数组构造器
v := [1.0, 2.0, 3.0]; - 逐元素乘法
v .* v; - 标量广播
v := 2 * v(标量自动扩展到每个元素); - 矩阵加法
m := m + m、矩阵乘向量v := m * v; - 归约函数
SUM(v); - 区间与切片
v[0..1] := v[1..2]; LEN(v, 0)取第 0 维长度,DIM(m)取维数。
这套机制让 Active Oberon 在 A2 中可以直接承担信号处理、数值模拟等任务,而不必退回到裸循环。
命令:从 A2 外壳启动模块功能
A2 的交互模型非常独特:导出且无参数(或仅带Commands.Context参数)的过程就是一条命令,可以从命令行执行,也可以在任何文本上点击中键直接运行。Hello演示了命令的标准形态:
PROCEDURE Hello*(context: Commands.Context); BEGIN context.arg.SkipWhitespace; context.arg.String(name); (* 读取命令行参数 *) ... context.out.String(...); context.out.Ln; context.out.Update (* 冲刷输出缓冲 *) END Hello;context.arg是参数读取器,context.out是输出写入器;Streams.Writer抽象统一了文件、网络与屏幕三种输出目标(WriteTo过程),这正是 A2 中"一切皆流"的设计;- 模块体(
BEGIN ... END LearnActiveOberon.)在模块加载时执行一次,用于初始化; - 最终点号之后的文本被编译器忽略,因此 A2 源码传统上把构建与测试命令写在文件末尾:
Compiler.Compile LearnActiveOberon.Mod ~ LearnActiveOberon.Hello Active Oberon ~ LearnActiveOberon.Producer ~ System.Free LearnActiveOberon ~~是 A2 命令行的结束符;System.Free可在运行中卸载模块——A2 就是通过"编译-加载-运行-卸载"的循环进行开发,无需重启系统。
花括号修饰符全集:语言的可组合开关
几乎每一种声明——过程、类型、变量、参数、语句块、cell——都可以携带花括号修饰符列表。语法分析器接受其中的任意标识符,由编译器拒绝不认识的修饰符。按用途分组如下:
并发(对象体与语句块)
| 修饰符 | 作用 |
|---|---|
{ACTIVE} | 对象体作为独立活动(线程),创建对象即启动 |
{EXCLUSIVE} | 块为临界区:同一对象排他块内至多一条活动 |
{PRIORITY(n)} | 活动体的优先级 |
{SAFE} | 活动体在陷阱后重启并抵抗终止 |
{REALTIME} | 实时活动,只能使用实时安全的操作 |
{UNCOOPERATIVE} | 块不参与协作式调度(内核与底层代码用) |
面向对象
| 修饰符 | 作用 |
|---|---|
{ABSTRACT} | 记录/对象类型或方法无实现 |
{FINAL} | 记录不可扩展,方法不可覆写 |
{OVERRIDE} | 显式标注方法覆写(否则编译器自行推断) |
{DELEGATE} | 过程类型的值可为"方法 + 其对象" |
{DYNAMIC} | 运算符在运行时动态分派 |
内存与安全
| 修饰符 | 作用 |
|---|---|
{UNTRACED} | 指针变量不被回收器跟踪 |
{UNTRACKED} | 语句块内局部引用不被跟踪 |
{UNSAFE} | POINTER {UNSAFE} TO ...原始指针:与ADDRESS兼容、无类型守卫无检查 |
{UNCHECKED} | 块编译时不生成 NIL、越界与栈检查 |
{DISPOSABLE} | 指针/对象用DISPOSE释放而非回收器 |
{ALIGNED(n)} | 符号按 n 字节对齐 |
{OFFSET(n)} | 字段/变量放在固定偏移处 |
{MOVABLE} | ADDRESS参数可能指向回收器会搬移的内存 |
{REGISTER} | 尽量把变量/参数放寄存器 |
过程与链接
| 修饰符 | 作用 |
|---|---|
{WINAPI}/{C}/{PlatformCC} | 过程(类型)的调用约定 |
{INTERRUPT} | 过程是中断处理程序 |
{NORETURN} | 过程永不返回 |
{PLAIN} | 无活动帧:无局部变量与参数 |
{OPENING}/{CLOSING} | 在所有模块体之前/之后链接,二者隐含PLAIN |
{ALIGNSTACK} | 进入过程时对齐栈 |
{PCOFFSET(n)} | 过程类型的程序计数器偏移(后端用) |
{Fingerprint=x} | 固定符号指纹而非计算 |
{TEST} | 标记测试过程,供编译器--test选项使用 |
Active Cells(硬件描述 / FPGA 后端):描述生成 cell 或通道的属性,包括{DataMemorySize(n)}、{CodeMemorySize(n)}、{InstructionWidth(n)}、{ChannelWidth(n)}、{ChannelDepth(n)}、{Channels}、{Vector}、{FloatingPoint}、{NoMul}、{HasNonBlockingIO}、{FrequencyDivider(n)}、{Engine}、{TRM}、{TRMS}、{BaseMem}、{BaseDiv}、{Backend(s)}、{Runtime(s)}。这意味着同一种语言既能写内核与驱动,也能通过后端描述并生成硬件,是 Oberon 家族"单语言覆盖软硬件"理念的极致体现。
值得注意的细节:惯例与底层能力
- 大小写:标识符区分大小写;关键字不区分;文件扩展名决定书写风格(
X.Mod大写 /x.mod小写); - 导出:
*导出、-只读导出; - 无
free:系统统一垃圾回收; - 运行时模块热插拔:
System.Free Module ~可在系统运行中卸载模块——A2 的整个开发流程都建立在此之上; - 底层访问:
SYSTEM模块提供不安全操作(SYSTEM.GET、SYSTEM.PUT、SYSTEM.VAL、SYSTEM.MOVE),CODE ... END可直接内嵌汇编; - 条件编译:
#if ... #else ... #end,符号通过编译器参数传入(如--define=UNIX,AMD64)。
如何在当前仓库继续深入
本文所述全部内容均以 uk/active-oberon.md 为蓝本,并与仓库内的 active-oberon.md(英文原版)及 ru/active-oberon.md(俄语版)逐行对应。三个版本内容一致,适合对照阅读以校准术语。若要在真实环境中运行上述代码,可参考原文档给出的社区与项目资源方向(A2 官方源码、活跃维护的分支a2oberon、以及免安装 A2 即可构建 Linux/Windows 控制台程序的minia2SDK),将示例保存为LearnActiveOberon.Mod后按文件末尾的命令编译并测试。仓库本身是只读的教学资源,任何修改建议请通过该仓库的贡献流程提出。
- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
相关推荐
Active Oberon 语言实战指南:并发内建的语言、A2 操作系统与完整语法速览
Active Oberon 语言实战指南:并发内建的语言、A2 操作系统与完整语法速览 本指南基于当前仓库根目录下的 active oberon.md http
文档教程从零上手 Active Oberon:内建并发的 Oberon 家族语言与 A2 系统实战指南
从零上手 Active Oberon:内建并发的 Oberon 家族语言与 A2 系统实战指南 本文以 learnxinyminutes docs 仓库中的 A
文档教程Groovy 动态语言快速上手:learnxinyminutes-docs 仓库 JVM 语言教程深度解读
Groovy 动态语言快速上手:learnxinyminutes docs 仓库 JVM 语言教程深度解读 本指南以 learnxinyminutes docs
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考