Browse Source

Separate Builder and Foc from FreeOberon; Move constants to Config

Arthur Yefimov 3 năm trước cách đây
mục cha
commit
f09234c1bc
12 tập tin đã thay đổi với 619 bổ sung456 xóa
  1. 1 0
      Data/Texts/en.dat
  2. 1 0
      Data/Texts/ru.dat
  3. 442 0
      src/Builder.Mod
  4. 6 1
      src/Config_linux.Mod
  5. 6 1
      src/Config_win32.Mod
  6. 3 4
      src/Editor.Mod
  7. 2 2
      src/FoStrings.Mod
  8. 74 0
      src/Fob.Mod
  9. 30 441
      src/FreeOberon.Mod
  10. 33 1
      src/Graph.Mod
  11. 19 6
      src/make.sh
  12. 2 0
      src/pack_linux.sh

+ 1 - 0
Data/Texts/en.dat

@@ -187,6 +187,7 @@
 243 "concatenation of module, type, and guarded variable exceeds maximum name length"
 244 "cyclic type definition not allowed"
 265 "unsupported string operation"
+400 "file not found"
 401 "file contains wrong module name"
 421 "compilation failed"
 422 "linking failed"

+ 1 - 0
Data/Texts/ru.dat

@@ -187,6 +187,7 @@
 243 "сцепление модуля, типа и защищённой переменной превышает максимальную длину имени"
 244 "циклическое определение типа не допускается"
 265 "строковая операция не поддерживается"
+400 "файл не найден"
 401 "файл содержит неверное имя модуля"
 421 "не удалось скомпилировать программу"
 422 "не удалось скомпоновать программу"

+ 442 - 0
src/Builder.Mod

@@ -0,0 +1,442 @@
+MODULE Builder;
+(* Copyright 2017-2022 Arthur Yefimov
+
+This file is part of Free Oberon.
+
+Free Oberon is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Free Oberon is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT Term, FoStrings, Files, Utf8, Config, Strings, Int, Out, Kernel;
+
+TYPE
+  StrList* = POINTER TO StrListDesc;
+  StrListDesc* = RECORD
+    s*: ARRAY 256 OF CHAR; (* Module name *)
+    fname*: ARRAY 256 OF CHAR; (* Filename of module source *)
+    next*: StrList
+  END;
+
+  ErrorHandler* = PROCEDURE (IN fname: ARRAY OF CHAR;
+    col, line, error: INTEGER; IN msg: ARRAY OF CHAR);
+
+VAR
+  sysModules: StrList;
+  workDir: ARRAY 256 OF CHAR; (* Directory of main file of compiled program *)
+
+PROCEDURE IsSysModule(IN name: ARRAY OF CHAR): BOOLEAN;
+VAR p: StrList;
+BEGIN p := sysModules;
+  WHILE (p # NIL) & (p.s # name) DO p := p.next END ;
+RETURN p # NIL END IsSysModule;
+
+PROCEDURE ModuleExists(IN fname: ARRAY OF CHAR): BOOLEAN;
+VAR F: Files.File;
+  exists: BOOLEAN;
+BEGIN F := Files.Old(fname); exists := F # NIL;
+  IF F # NIL THEN Files.Close(F) END ;
+RETURN exists END ModuleExists;
+
+PROCEDURE SetWorkDir*(IN fname: ARRAY OF CHAR);
+VAR i: INTEGER;
+BEGIN i := Strings.Length(fname);
+  WHILE (i # -1) & (fname[i] # '/') & (fname[i] # '\') DO DEC(i) END;
+  IF i # -1 THEN Strings.Extract(fname, 0, i + 1, workDir)
+  ELSE workDir[0] := 0X
+  END
+END SetWorkDir;
+
+PROCEDURE SplitModName(IN s: ARRAY OF CHAR;
+    VAR m1, m2: ARRAY OF CHAR): BOOLEAN;
+VAR i: INTEGER;
+BEGIN i := 1;
+  WHILE (s[i] # 0X) & ~(('A' <= s[i]) & (s[i] <= 'Z') &
+                        ~(('A' <= s[i - 1]) & (s[i - 1] <= 'Z')))
+  DO INC(i)
+  END;
+  IF s[i] # 0X THEN
+    Strings.Extract(s, 0, i, m1);
+    Strings.Extract(s, i, LEN(m2), m2)
+  END ;
+RETURN s[i] # 0X END SplitModName;
+
+PROCEDURE FindModule(IN mod: ARRAY OF CHAR; VAR fname: ARRAY OF CHAR): BOOLEAN;
+VAR ok: BOOLEAN;
+  s, m1, m2: ARRAY 256 OF CHAR;
+BEGIN ok := FALSE; (* Try 'Programs/A/GuiButtons.Mod' *)
+  s := workDir$; Strings.Append(mod, s); Strings.Append('.Mod', s);
+  IF ModuleExists(s) THEN fname := s$; ok := TRUE
+  ELSIF SplitModName(mod, m1, m2) THEN (* Try 'Programs/A/Gui/Buttons.Mod' *)
+    s := workDir$; Strings.Append(m1, s); Strings.Append('/', s);
+    Strings.Append(m2, s); Strings.Append('.Mod', s);
+    IF ModuleExists(s) THEN fname := s$; ok := TRUE END
+  END ;
+RETURN ok END FindModule;
+
+(* !TODO move out, rewrite *)
+PROCEDURE StringsFindNext*(what, where: ARRAY OF CHAR;
+    begin: INTEGER; VAR found: BOOLEAN; VAR resultPos: INTEGER);
+VAR i: INTEGER;
+BEGIN
+  IF begin < Strings.Length(where) THEN i := 0;
+    LOOP
+      IF what[i] = 0X THEN
+        (* reached end of what *)
+        found := TRUE; resultPos := begin - i;
+        EXIT
+      ELSIF where[begin] = 0X THEN
+        (* end of string (but not of what) *)
+        found := FALSE;
+        EXIT
+      ELSIF where[begin] = what[i] THEN
+        (* characters identic, compare next one *)
+        INC(begin); INC(i)
+      ELSE
+        (* difference found: reset indices and restart *)
+        DEC(begin, i - 1); i := 0
+      END
+    END
+  ELSE found := FALSE
+  END
+END StringsFindNext;
+
+PROCEDURE ReadInt(IN s: ARRAY OF CHAR; VAR i, res: INTEGER);
+BEGIN res := 0;
+  WHILE ('0' <= s[i]) & (s[i] <= '9') DO
+    res := res * 10 + ORD(s[i]) - ORD('0');
+    INC(i)
+  END
+END ReadInt;
+
+PROCEDURE ParseErrors(VAR s: ARRAY OF CHAR; fname: ARRAY OF CHAR;
+    VAR line, col, error: INTEGER);
+VAR i, j, st, len, skip: INTEGER; found: BOOLEAN;
+BEGIN fname[0] := 0X; line := 1; col := 1;
+  StringsFindNext(' translating ', s, 0, found, i);
+  IF found THEN INC(i, 13); j := 0; (* Read module name *)
+    WHILE (j < LEN(fname) - 1) & (s[i] > ' ') DO
+      fname[j] := s[i]; INC(i); INC(j)
+    END;
+    fname[j] := 0X;
+    (* Remove everything up to the following line *)
+    WHILE (s[i] # 0X) & (s[i] # 0AX) DO INC(i) END;
+    Strings.Delete(s, 0, i + 1); i := 0;
+    (* Read line and column numbers, i.e. '10:23' *)
+    WHILE (s[i] # 0X) & (s[i] <= ' ') DO INC(i) END;
+    IF ('0' <= s[i]) & (s[i] <= '9') THEN
+      ReadInt(s, i, line);
+      IF s[i] = ':' THEN INC(i) END;
+      IF ('0' <= s[i]) & (s[i] <= '9') THEN
+        ReadInt(s, i, col)
+      END;
+      WHILE (s[i] # 0X) & (s[i] # 'e') DO INC(i) END;
+      IF (s[i + 1] = 'r') & (s[i + 2] = 'r') & (s[i + 3] = ' ') THEN
+        INC(i, 4); ReadInt(s, i, error)
+      ELSE error := 0
+      END;
+      FoStrings.MakeErrorStr(error, s)
+    END
+  ELSE StringsFindNext('): ', s, 0, found, i); (* In case of gcc error *)
+    IF found THEN Strings.Delete(s, 0, i + 3) END;
+    i := 0; WHILE (s[i] # 0X) & (s[i] # 0AX) & (s[i] # 0DX) DO INC(i) END;
+    s[i] := 0X
+  END
+END ParseErrors;
+
+(* "Module.Mod" -> "Module" *)
+PROCEDURE GetModuleName*(IN fname: ARRAY OF CHAR; VAR modname: ARRAY OF CHAR);
+VAR i, j: INTEGER;
+BEGIN i := 0; j := 0;
+  WHILE fname[i] # 0X DO INC(i) END; DEC(i);
+  WHILE (i # -1) & (fname[i] # '/') DO DEC(i) END; INC(i);
+  WHILE (fname[i] # 0X) & (fname[i] # '.') DO
+    modname[j] := fname[i]; INC(i); INC(j)
+  END;
+  modname[j] := 0X
+END GetModuleName;
+
+PROCEDURE RunCommand(IN fname, mod: ARRAY OF CHAR; link, graph, main: BOOLEAN;
+    list: StrList; onError: ErrorHandler): BOOLEAN;
+CONST bufLen = 20480;
+VAR buf: ARRAY bufLen OF SHORTCHAR;
+  p: StrList;
+  len, err, line, col, error: INTEGER;
+  command: ARRAY 32 OF CHAR;
+  q: ARRAY 1024 OF SHORTCHAR;
+  z: ARRAY 1024 OF CHAR;
+  cmd: ARRAY 1024 OF CHAR;
+  s, sN: ARRAY 80 OF CHAR;
+  success, ok: BOOLEAN;
+BEGIN ok := TRUE;
+  IF ~link THEN command := 'compile'
+  ELSIF graph THEN command := 'link_graph'
+  ELSE command := 'link_console'
+  END;
+  IF Config.isWindows THEN
+    IF Term.SearchPath('cmd.exe', q) # 0 THEN
+      Utf8.Decode(q, cmd);
+      Strings.Insert('"', 0, cmd);
+      Strings.Append('" /C Data\bin\', cmd);
+      Strings.Append(command, cmd);
+      Strings.Append('.bat ', cmd)
+    ELSE ok := FALSE; onError('', -1, -1, -1, 'Could not find cmd.exe')
+    END
+  ELSE (* Linux *)
+    cmd := 'Data/bin/'; Strings.Append(command, cmd);
+    Strings.Append('.sh ', cmd)
+  END;
+
+  IF ok THEN
+    IF Strings.Pos(Config.stdPath, fname, 0) = 0 THEN
+      Strings.Extract(fname, Strings.Length(Config.stdPath), LEN(s), s)
+    ELSE s := fname$
+    END;
+    Strings.Append(s, cmd);
+
+    IF main THEN Strings.Append(' -m', cmd)
+    ELSIF link & (list # NIL) THEN
+      p := list;
+      WHILE p.next # NIL DO
+        IF ModuleExists(p.fname) THEN
+          Strings.Append(' ', cmd); Strings.Append(p.s, cmd);
+          Strings.Append('.c', cmd)
+        END;
+        p := p.next
+      END
+    END;
+    Utf8.Encode(cmd, q);
+    success := (Term.RunProcess(q, buf, bufLen, len, err) # 0) & (err = 0);
+    IF ~success & (onError # NIL) THEN
+      s := ' Command returned '; Int.Append(err, s);
+      Strings.Append(' exit status ', s);
+      IF (len > 0) & (len < bufLen) THEN
+        IF buf[len - 1] = 0AX THEN buf[len - 1] := 0X ELSE buf[len] := 0X END;
+        Utf8.Decode(buf, z);
+        ParseErrors(z, fname, line, col, error);
+        onError(fname, col, line, error, z)
+      ELSIF link THEN FoStrings.GetErrorStr(422, z)
+      ELSE FoStrings.GetErrorStr(421, z)
+      END;
+      (*IF z[0] = 0X THEN ShowError(s) ELSE ShowError(z) END*)
+      IF z[0] = 0X THEN onError('', -1, -1, -1, s)
+      ELSE onError('', -1, -1, -1, z)
+      END
+    END
+  END ;
+RETURN success END RunCommand;
+
+PROCEDURE Compile(IN fname, mod: ARRAY OF CHAR; main: BOOLEAN;
+  onError: ErrorHandler): BOOLEAN;
+BEGIN RETURN RunCommand(fname, mod, FALSE, FALSE, main, NIL, onError)
+END Compile;
+
+PROCEDURE Link(IN fname, mod: ARRAY OF CHAR;
+    graph: BOOLEAN; list: StrList; VAR exename: ARRAY OF CHAR;
+    onError: ErrorHandler): BOOLEAN;
+VAR ok: BOOLEAN;
+  s: ARRAY 2048 OF CHAR;
+  res: INTEGER;
+BEGIN ok := RunCommand(fname, mod, TRUE, graph, FALSE, list, onError);
+  IF ok THEN (* Move executable file if workDir is non-standard *)
+    s := mod$; IF Config.isWindows THEN Strings.Append('.exe', s) END;
+    exename := 'bin/'; Strings.Append(s, exename);
+    IF workDir # Config.stdPath THEN
+      Strings.Insert(workDir, 0, s);
+      Files.Rename(exename, s, res);
+      IF res = 0 THEN exename := s$ END
+    END
+  END ;
+RETURN ok END Link;
+
+PROCEDURE ResetSysModules*;
+
+  PROCEDURE Add(s: ARRAY OF CHAR);
+  VAR p: StrList;
+  BEGIN NEW(p); p.s := s$; p.fname[0] := 0X;
+    p.next := sysModules; sysModules := p
+  END Add;
+
+BEGIN sysModules := NIL;
+  Add('SYSTEM');   Add('Texts');    Add('Files');   Add('Strings');
+  Add('In');       Add('Out');      Add('Math');    Add('MathL');
+  Add('Modules');  Add('Platform'); Add('Oberon');  Add('Reals');
+  Add('VT100');    Add('Graph');    Add('TermBox'); Add('Term');
+  Add('Allegro5'); Add('Dir');      Add('Int');     Add('Random')
+END ResetSysModules;
+
+PROCEDURE SkipComment(VAR R: Files.Rider; VAR ch: CHAR; VAR s: ARRAY OF CHAR);
+VAR last: CHAR;
+BEGIN last := ch; Files.ReadChar(R, ch);
+  WHILE ~R.eof & ((last # '*') OR (ch # ')')) DO
+    IF (last = '(') & (ch = '*') THEN SkipComment(R, ch, s) END;
+    last := ch; Files.ReadChar(R, ch)
+  END;
+  IF ~R.eof THEN Files.ReadChar(R, ch) END;
+  WHILE ~R.eof & (ch <= ' ') DO Files.ReadChar(R, ch) END
+END SkipComment;
+
+PROCEDURE ReadCh(VAR R: Files.Rider; VAR ch: CHAR; VAR line, col: INTEGER);
+BEGIN Files.ReadChar(R, ch);
+  IF ch = 0AX THEN INC(line); col := 1 ELSE INC(col) END
+END ReadCh;
+
+PROCEDURE GetSym(VAR R: Files.Rider; VAR ch: CHAR; VAR s: ARRAY OF CHAR;
+    VAR line, col: INTEGER);
+VAR i: INTEGER;
+BEGIN
+  WHILE ~R.eof & (ch <= ' ') DO ReadCh(R, ch, line, col) END;
+  i := 0;
+  IF ~R.eof THEN
+    IF ch = '(' THEN
+      ReadCh(R, ch, line, col);
+      IF ch = '*' THEN ReadCh(R, ch, line, col); SkipComment(R, ch, s)
+      ELSE s[i] := ch; INC(i)
+      END
+    END;
+    IF ('A' <= CAP(ch)) & (CAP(ch) <= 'Z') OR (ch = '_') THEN
+      WHILE ~R.eof &
+            (('A' <= CAP(ch)) & (CAP(ch) <= 'Z') OR
+             ('0' <= ch) & (ch <= '9') OR (ch = '_')) DO
+        IF i < LEN(s) - 1 THEN s[i] := ch; INC(i) END;
+        ReadCh(R, ch, line, col)
+      END
+    ELSE
+      WHILE ~R.eof & (ch > ' ') &
+            ~(('A' <= CAP(ch)) & (CAP(ch) <= 'Z') OR
+              ('0' <= ch) & (ch <= '9') OR (ch = '_')) DO
+        IF i < LEN(s) - 1 THEN s[i] := ch; INC(i) END;
+        ReadCh(R, ch, line, col)
+      END
+    END
+  END;
+  s[i] := 0X
+END GetSym;
+
+PROCEDURE CompileAll*(modules: StrList; graph: BOOLEAN;
+    VAR exename: ARRAY OF CHAR; onError: ErrorHandler): BOOLEAN;
+VAR p, last: StrList;
+  ok: BOOLEAN;
+BEGIN exename[0] := 0X;
+  IF modules # NIL THEN
+    ok := TRUE; p := modules;
+    WHILE ok & (p.next # NIL) DO
+      IF ModuleExists(p.fname) THEN
+        IF ~Compile(p.fname, '', FALSE, onError) THEN ok := FALSE END
+      ELSIF ~IsSysModule(p.s) THEN ok := FALSE
+      END;
+      p := p.next
+    END;
+    IF ok THEN
+      IF ModuleExists(p.fname) THEN
+        IF ~Compile(p.fname, '', TRUE, onError) THEN ok := FALSE END
+      END;
+      ok := ok & Link(p.fname, p.s, graph, modules, exename, onError);
+    END
+  ELSE ok := FALSE
+  END ;
+RETURN ok END CompileAll;
+
+(** Returns true if the two module names are equal.
+  modname is the identifier that comes after the keyword MODULE.
+  filebase is the file name without the extension; on Windows it
+  is allowed to be written in different case. *)
+PROCEDURE EqualModuleNames(modname, filebase: ARRAY OF CHAR): BOOLEAN;
+BEGIN IF Config.isWindows THEN Strings.Cap(modname); Strings.Cap(filebase) END;
+RETURN modname = filebase END EqualModuleNames;
+
+PROCEDURE GetImportedModules(IN fname, modname: ARRAY OF CHAR;
+    VAR line, col: INTEGER; VAR res: INTEGER): StrList;
+VAR F: Files.File;
+  R: Files.Rider;
+  top, p: StrList;
+  ch: CHAR;
+  mod, s, fname2: ARRAY 256 OF CHAR;
+  exit: BOOLEAN;
+BEGIN res := 401; NEW(top); top.next := NIL; p := top;
+  F := Files.Old(fname);
+  IF F = NIL THEN res := 400
+  ELSE Files.Set(R, F, 0); Files.ReadChar(R, ch);
+    line := 1; col := 1; GetSym(R, ch, s, line, col);
+    IF s = 'MODULE' THEN GetSym(R, ch, s, line, col);
+      IF EqualModuleNames(modname, s) THEN
+        res := 0;
+        GetSym(R, ch, s, line, col);
+        IF s = ';' THEN GetSym(R, ch, s, line, col); res := 0;
+          IF s = 'IMPORT' THEN GetSym(R, ch, s, line, col); exit := FALSE;
+            WHILE ~exit & ('A' <= CAP(s[0])) & (CAP(s[0]) <= 'Z') DO
+              mod := s; GetSym(R, ch, s, line, col); fname2[0] := 0X;
+              IF s = ':=' THEN GetSym(R, ch, s, line, col);
+                mod := s; GetSym(R, ch, s, line, col)
+              END;
+              IF IsSysModule(mod) OR FindModule(mod, fname2) THEN
+                NEW(p.next); p := p.next; p.next := NIL;
+                p.s := mod$; p.fname := fname2$
+              END;
+              IF s = ',' THEN GetSym(R, ch, s, line, col)
+              ELSE exit := FALSE
+              END
+            END
+          END
+        END
+      END
+    END
+  END ;
+RETURN top.next END GetImportedModules;
+
+PROCEDURE AddUniqueToList(what: StrList; VAR where: StrList);
+VAR p, q, nextP: StrList;
+BEGIN
+  IF where = NIL THEN where := what
+  ELSE
+    p := what;
+    WHILE p # NIL DO
+      nextP := p.next;
+      IF where.s # p.s THEN
+        q := where;
+        WHILE (q.next # NIL) & (q.next.s # p.s) DO q := q.next END;
+        IF q.next = NIL THEN q.next := p; p.next := NIL END
+      END;
+      p := nextP
+    END
+  END
+END AddUniqueToList;
+
+PROCEDURE UsedModuleList*(IN modname, fname: ARRAY OF CHAR;
+    VAR errFname: ARRAY OF CHAR;
+    VAR errLine, errCol: INTEGER; VAR res: INTEGER): StrList;
+VAR L, list, list2, p: StrList;
+BEGIN L := NIL; res := 0(*OK*);
+  IF ~IsSysModule(modname) THEN
+    list := GetImportedModules(fname, modname, errLine, errCol, res);
+    p := list;
+    IF res = 0 THEN
+      WHILE (res = 0) & (p # NIL) DO
+        list2 := UsedModuleList(p.s, p.fname, errFname, errLine, errCol, res);
+        AddUniqueToList(list2, L);
+        p := p.next
+      END
+    ELSE Strings.Copy(fname, errFname)
+    END
+  END;
+  IF res = 0 THEN
+    NEW(p); p.s := modname$; p.fname := fname$; p.next := NIL;
+    AddUniqueToList(p, L)
+  END ;
+RETURN L END UsedModuleList;
+
+PROCEDURE ImportsGraph*(p: StrList): BOOLEAN;
+BEGIN WHILE (p # NIL) & (p.s # 'Graph') DO Out.Char('>');Out.String(p.s); Out.Ln; p := p.next END ;
+RETURN p # NIL END ImportsGraph;
+
+BEGIN
+  ResetSysModules
+END Builder.

+ 6 - 1
src/Config_linux.Mod

@@ -1,4 +1,9 @@
 MODULE Config;
+IMPORT Platform;
 CONST
-  isWindows* = FALSE;
+  isWindows* = Platform.Windows;
+  stdPath* = 'Programs/';
+
+  version* = '1.1.0-alpha.5';
+  year* = 2022;
 END Config.

+ 6 - 1
src/Config_win32.Mod

@@ -1,4 +1,9 @@
 MODULE Config;
+IMPORT Platform;
 CONST
-  isWindows* = TRUE;
+  isWindows* = Platform.Windows;
+  stdPath* = 'Programs/';
+
+  version* = '1.1.0-alpha.5';
+  year* = 2022;
 END Config.

+ 3 - 4
src/Editor.Mod

@@ -16,10 +16,9 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
 *)
-IMPORT OV, T := TermBox, Text := EditorText,
+IMPORT OV, T := TermBox, Text := EditorText, Config,
   Int, Strings, FoStrings, StrList, Dir, Out;
 CONST
-  stdPath* = 'Programs/';
   dotChar = 0B7X; (* To higlight spaces *)
 
   (* Direction of Selection *)
@@ -180,7 +179,7 @@ BEGIN w := c.parent(FileDialog); s := w.edtFilename.caption$;
         Strings.Append(s, full);
         IF Match(w.home, full, 0) THEN
           Strings.Delete(full, 0, Strings.Length(w.home));
-          Strings.Insert(stdPath, 0, full)
+          Strings.Insert(Config.stdPath, 0, full)
         END;
         OV.CloseCurWindow(c);
         IF w.onFileOk # NIL THEN w.onFileOk(c, full) END
@@ -258,7 +257,7 @@ BEGIN OV.InitWindow(c); c.do := fileDialogMethod; c.type := type;
     IF c.home[L] = '\' THEN c.home[L] := '/' END; INC(L)
   END; (* L = length of c.home; append / if it is not in the end *)
   IF c.home[L - 1] # '/' THEN c.home[L] := '/'; INC(L); c.home[L] := 0X END;
-  Strings.Append(stdPath, c.home); c.path := c.home$;
+  Strings.Append(Config.stdPath, c.home); c.path := c.home$;
 
   (* ColumnList *)
   c.colFiles := OV.NewColumnList();

+ 2 - 2
src/FoStrings.Mod

@@ -43,9 +43,9 @@ PROCEDURE MakeErrorStr*(err: INTEGER; VAR s: ARRAY OF CHAR);
 VAR z: ARRAY 256 OF CHAR;
 BEGIN
   GetErrorStr(err, z);
-  s := 'Ошибка #';
+  s := '#';
   Int.Append(err, s);
-  Strings.Append(': ', s);
+  Strings.Append(' ', s);
   Strings.Append(z, s)
 END MakeErrorStr;
 

+ 74 - 0
src/Fob.Mod

@@ -0,0 +1,74 @@
+MODULE Fob;
+(* Copyright 2017-2022 Arthur Yefimov
+
+This file is part of Free Oberon.
+
+Free Oberon is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Free Oberon is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT FoStrings, Builder, Config, Args, Strings, Out, Kernel;
+
+PROCEDURE Usage;
+VAR s: ARRAY 256 OF CHAR;
+BEGIN
+  Out.String('Free Oberon Compiler version ');
+  Out.String(Config.version); Out.Ln;
+  Out.String('Copyright (c) 2017-'); Out.Int(Config.year, 0);
+  Out.String(' by Arthur Yefimov and others.'); Out.Ln;
+  Out.String('Fob uses Ofront+ and GCC (MinGW).'); Out.Ln; Out.Ln;
+  Out.String('Usage:'); Out.Ln; Out.String('  ');
+  Args.Get(0, s); Out.String(s);
+
+  Out.String(' sourceFile'); Out.Ln; Out.Ln;
+  Out.String('Please specify a single file name - the main module source');
+  Out.Ln
+(*Out.String(' [options] MainModuleSourceFile'); Out.Ln; Out.Ln;
+  Out.String('Options:'); Out.Ln;
+  Out.String('  -o file     Name of output executable file'); Out.Ln*)
+END Usage;
+
+PROCEDURE BuildErrorCallback(fname: ARRAY OF CHAR; col, line, error: INTEGER;
+    msg: ARRAY OF CHAR);
+BEGIN
+  IF fname[0] # 0X THEN
+    Out.String(fname); Out.Char(':'); Out.Int(line, 0);
+    Out.Char(':'); Out.Int(col, 0); Out.String(': error: ')
+  ELSE Out.String('error: ')
+  END;
+  Out.String(msg); Out.Ln
+END BuildErrorCallback;
+
+PROCEDURE Do;
+VAR modules: Builder.StrList;
+  mainFname, modname, exename, errFname, s: ARRAY 256 OF CHAR;
+  errLine, errCol, res: INTEGER;
+  graph: BOOLEAN;
+BEGIN
+  Args.Get(1, mainFname);
+  FoStrings.SetLang('en');
+  Builder.SetWorkDir(mainFname);
+  Builder.GetModuleName(mainFname, modname);
+  modules := Builder.UsedModuleList(modname, mainFname,
+    errFname, errLine, errCol, res);
+  IF res = 0 THEN
+    graph := Builder.ImportsGraph(modules);
+    IF Builder.CompileAll(modules, graph, exename, BuildErrorCallback) THEN END
+  ELSE (*res = 400-file not found or 401-file contains wrong module name*)
+    FoStrings.MakeErrorStr(res, s);
+    BuildErrorCallback(errFname, 1, 1, 401, s)
+  END
+END Do;
+
+BEGIN
+  IF Args.Count = 0 THEN Usage ELSE Do END
+END Fob.

+ 30 - 441
src/FreeOberon.Mod

@@ -16,12 +16,9 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
 *)
-IMPORT T := TermBox, Files, Args, Utf8,
+IMPORT T := TermBox, Files, Args, Utf8, Builder,
        OV, Editor, Term, FoStrings, Config, Strings, Int, Out, Kernel;
 CONST
-  version* = '1.1.0-alpha.5';
-  year = 2022;
-
   (* Direction of Selection *)
   dirLeft  = 0;
   dirRight = 1;
@@ -53,12 +50,6 @@ CONST
   defLang = 'en';
 
 TYPE
-  StrList = POINTER TO StrListDesc;
-  StrListDesc = RECORD
-    s: ARRAY 256 OF CHAR; (* Module name *)
-    fname: ARRAY 256 OF CHAR; (* Filename of module source *)
-    next: StrList
-  END;
   Fnames = ARRAY 32, 256 OF CHAR;
 
 VAR
@@ -66,10 +57,6 @@ VAR
   inputBuf: ARRAY 16300 OF CHAR; (* Saves entered chars before Enter pressed *)
   inputBufLen: INTEGER;
   programFinished: BOOLEAN;
-  tempWindowed: BOOLEAN; (* TRUE if editor windowed while program is running *)
-  needWindowed: BOOLEAN;
-  sysModules: StrList;
-  workDir: ARRAY 256 OF CHAR; (* Directory of main file of compiled program *)
   app: OV.App;
 
   curX, curY: INTEGER; (* Cursor position *)
@@ -97,33 +84,6 @@ PROCEDURE ShowError(s: ARRAY OF CHAR);
 BEGIN Editor.SetMsg(app.windows(Editor.Editor), s)
 END ShowError;
 
-(* !TODO move out, rewrite *)
-PROCEDURE StringsFindNext*(what, where: ARRAY OF CHAR;
-    begin: INTEGER; VAR found: BOOLEAN; VAR resultPos: INTEGER);
-VAR i: INTEGER;
-BEGIN
-  IF begin < Strings.Length(where) THEN i := 0;
-    LOOP
-      IF what[i] = 0X THEN
-        (* reached end of what *)
-        found := TRUE; resultPos := begin - i;
-        EXIT
-      ELSIF where[begin] = 0X THEN
-        (* end of string (but not of what) *)
-        found := FALSE;
-        EXIT
-      ELSIF where[begin] = what[i] THEN
-        (* characters identic, compare next one *)
-        INC(begin); INC(i)
-      ELSE
-        (* difference found: reset indices and restart *)
-        DEC(begin, i - 1); i := 0
-      END
-    END
-  ELSE found := FALSE
-  END
-END StringsFindNext;
-
 PROCEDURE FileNew(c: OV.Control);
 VAR e: Editor.Editor;
   p, br: OV.Control;
@@ -152,8 +112,8 @@ END FileNew;
 
 PROCEDURE FnameToCaption(IN fname: ARRAY OF CHAR; VAR caption: ARRAY OF CHAR);
 BEGIN
-  IF Strings.Pos(Editor.stdPath, fname, 0) = 0 THEN
-    Strings.Extract(fname, Strings.Length(Editor.stdPath),
+  IF Strings.Pos(Config.stdPath, fname, 0) = 0 THEN
+    Strings.Extract(fname, Strings.Length(Config.stdPath),
       LEN(caption), caption)
   ELSE caption := fname$
   END
@@ -183,20 +143,10 @@ BEGIN
   WHILE (e # NIL) & (e.fname # fname) DO
     IF e.next = f THEN e := NIL ELSE e := e.next(Editor.Editor) END
   END;
-  IF e = NIL THEN DoOpenFile(fname)
-  ELSE app.windows := e; OV.SetFocus(e)
-  END;
+  IF e = NIL THEN DoOpenFile(fname) ELSE app.windows := e; OV.SetFocus(e) END;
   OV.DrawApp(app)
 END FocusOrOpenFile;
 
-PROCEDURE ReadInt(IN s: ARRAY OF CHAR; VAR i, res: INTEGER);
-BEGIN res := 0;
-  WHILE ('0' <= s[i]) & (s[i] <= '9') DO
-    res := res * 10 + ORD(s[i]) - ORD('0');
-    INC(i)
-  END
-END ReadInt;
-
 PROCEDURE ScrollScreen;
 VAR x, y, tW, tH: INTEGER;
   ch: CHAR;
@@ -258,41 +208,6 @@ BEGIN
   terminalNeedRedraw := TRUE
 END Backspace;
 
-PROCEDURE ParseErrors(VAR s: ARRAY OF CHAR; fname: ARRAY OF CHAR;
-    VAR line, col, error: INTEGER);
-VAR i, j, st, len, skip: INTEGER; found: BOOLEAN;
-BEGIN fname[0] := 0X; line := 1; col := 1;
-  StringsFindNext(' translating ', s, 0, found, i);
-  IF found THEN INC(i, 13); j := 0; (* Read module name *)
-    WHILE (j < LEN(fname) - 1) & (s[i] > ' ') DO
-      fname[j] := s[i]; INC(i); INC(j)
-    END;
-    fname[j] := 0X;
-    (* Remove everything up to the following line *)
-    WHILE (s[i] # 0X) & (s[i] # 0AX) DO INC(i) END;
-    Strings.Delete(s, 0, i + 1); i := 0;
-    (* Read line and column numbers, i.e. '10:23' *)
-    WHILE (s[i] # 0X) & (s[i] <= ' ') DO INC(i) END;
-    IF ('0' <= s[i]) & (s[i] <= '9') THEN
-      ReadInt(s, i, line);
-      IF s[i] = ':' THEN INC(i) END;
-      IF ('0' <= s[i]) & (s[i] <= '9') THEN
-        ReadInt(s, i, col)
-      END;
-      WHILE (s[i] # 0X) & (s[i] # 'e') DO INC(i) END;
-      IF (s[i + 1] = 'r') & (s[i + 2] = 'r') & (s[i + 3] = ' ') THEN
-        INC(i, 4); ReadInt(s, i, error)
-      ELSE error := 0
-      END;
-      FoStrings.MakeErrorStr(error, s)
-    END
-  ELSE StringsFindNext('): ', s, 0, found, i); (* In case of gcc error *)
-    IF found THEN Strings.Delete(s, 0, i + 3) END;
-    i := 0; WHILE (s[i] # 0X) & (s[i] # 0AX) & (s[i] # 0DX) DO INC(i) END;
-    s[i] := 0X
-  END
-END ParseErrors;
-
 PROCEDURE PollProgram;
 VAR len, i: INTEGER;
     err: INTEGER;
@@ -329,7 +244,6 @@ BEGIN
     IF Term.ProcessFinished(err) THEN
       Read(TRUE); (* Read everything until pipe is empty *)
       programFinished := TRUE;
-      IF tempWindowed THEN T.SwitchToFS END;
       IF err = 0 THEN s := ' '; FoStrings.Append('pressAnyKeyToReturnToIde', s)
       ELSE s := ' '; FoStrings.Append('runtimeError', s);
         Strings.Append(' ', s); Int.Append(err, s)
@@ -415,193 +329,6 @@ BEGIN quit := FALSE;
   IF ~terminalMouseShown THEN T.ShowMouse END
 END RunTerminal;
 
-PROCEDURE IsSysModule(IN name: ARRAY OF CHAR): BOOLEAN;
-VAR p: StrList;
-BEGIN p := sysModules;
-  WHILE (p # NIL) & (p.s # name) DO p := p.next END ;
-RETURN p # NIL END IsSysModule;
-
-PROCEDURE ModuleExists(IN fname: ARRAY OF CHAR): BOOLEAN;
-VAR F: Files.File;
-  exists: BOOLEAN;
-BEGIN F := Files.Old(fname); exists := F # NIL;
-  IF F # NIL THEN Files.Close(F) END ;
-RETURN exists END ModuleExists;
-
-PROCEDURE SetWorkDir(IN fname: ARRAY OF CHAR);
-VAR i: INTEGER;
-BEGIN i := Strings.Length(fname);
-  WHILE (i # -1) & (fname[i] # '/') & (fname[i] # '\') DO DEC(i) END;
-  IF i # -1 THEN Strings.Extract(fname, 0, i + 1, workDir)
-  ELSE workDir[0] := 0X
-  END
-END SetWorkDir;
-
-PROCEDURE SplitModName(IN s: ARRAY OF CHAR;
-    VAR m1, m2: ARRAY OF CHAR): BOOLEAN;
-VAR i: INTEGER;
-BEGIN i := 1;
-  WHILE (s[i] # 0X) & ~(('A' <= s[i]) & (s[i] <= 'Z') &
-                        ~(('A' <= s[i - 1]) & (s[i - 1] <= 'Z')))
-  DO INC(i)
-  END;
-  IF s[i] # 0X THEN
-    Strings.Extract(s, 0, i, m1);
-    Strings.Extract(s, i, LEN(m2), m2)
-  END ;
-RETURN s[i] # 0X END SplitModName;
-
-PROCEDURE FindModule(IN mod: ARRAY OF CHAR; VAR fname: ARRAY OF CHAR): BOOLEAN;
-VAR ok: BOOLEAN;
-  s, m1, m2: ARRAY 256 OF CHAR;
-BEGIN ok := FALSE; (* Try 'Programs/A/GuiButtons.Mod' *)
-  s := workDir$; Strings.Append(mod, s); Strings.Append('.Mod', s);
-  IF ModuleExists(s) THEN fname := s$; ok := TRUE
-  ELSIF SplitModName(mod, m1, m2) THEN (* Try 'Programs/A/Gui/Buttons.Mod' *)
-    s := workDir$; Strings.Append(m1, s); Strings.Append('/', s);
-    Strings.Append(m2, s); Strings.Append('.Mod', s);
-    IF ModuleExists(s) THEN fname := s$; ok := TRUE END
-  END ;
-RETURN ok END FindModule;
-
-PROCEDURE RunCommand(IN fname, mod: ARRAY OF CHAR;
-    link, graph, main: BOOLEAN; list: StrList): BOOLEAN;
-CONST bufLen = 20480;
-VAR buf: ARRAY bufLen OF SHORTCHAR;
-  e: Editor.Editor;
-  p: StrList;
-  len, err, line, col, error: INTEGER;
-  command: ARRAY 32 OF CHAR;
-  q: ARRAY 1024 OF SHORTCHAR;
-  z: ARRAY 1024 OF CHAR;
-  cmd: ARRAY 1024 OF CHAR;
-  s, sN: ARRAY 80 OF CHAR;
-  tW, tH: INTEGER;
-  success: BOOLEAN;
-BEGIN
-  T.Size(tW, tH);
-  IF ~link THEN command := 'compile'
-  ELSIF graph THEN command := 'link_graph'
-  ELSE command := 'link_console'
-  END;
-  IF Config.isWindows THEN
-    IF Term.SearchPath('cmd.exe', q) # 0 THEN
-      Utf8.Decode(q, cmd);
-      Strings.Insert('"', 0, cmd);
-      Strings.Append('" /C Data\bin\', cmd);
-      Strings.Append(command, cmd);
-      Strings.Append('.bat ', cmd)
-    ELSE T.Print(0, tH - 1, -1, 'Could not find cmd.exe', 15, 4) (*!FIXME*)
-    END
-  ELSE (* Linux *)
-    cmd := 'Data/bin/'; Strings.Append(command, cmd);
-    Strings.Append('.sh ', cmd)
-  END;
-
-  IF Strings.Pos(Editor.stdPath, fname, 0) = 0 THEN
-    Strings.Extract(fname, Strings.Length(Editor.stdPath), LEN(s), s)
-  ELSE s := fname$
-  END;
-  Strings.Append(s, cmd);
-
-  IF main THEN Strings.Append(' -m', cmd)
-  ELSIF link & (list # NIL) THEN
-    p := list;
-    WHILE p.next # NIL DO
-      IF ModuleExists(p.fname) THEN
-        Strings.Append(' ', cmd); Strings.Append(p.s, cmd);
-        Strings.Append('.c', cmd)
-      END;
-      p := p.next
-    END
-  END;
-  Utf8.Encode(cmd, q);
-  success := (Term.RunProcess(q, buf, bufLen, len, err) # 0) &
-             (err = 0);
-  IF ~success THEN
-    s := ' Command returned '; Int.Append(err, s);
-    Strings.Append(' exit status ', s);
-    IF (len > 0) & (len < bufLen) THEN
-      IF buf[len - 1] = 0AX THEN buf[len - 1] := 0X
-      ELSE buf[len] := 0X
-      END;
-      Utf8.Decode(buf, z);
-      ParseErrors(z, fname, line, col, error);
-      FocusOrOpenFile(fname);
-      e := app.windows(Editor.Editor);
-      IF (col = 1) & (line # 1) THEN
-        e.text.MoveToLineCol(line - 1, 256, e.h - 2)
-      ELSE e.text.MoveToLineCol(line, col, e.h - 2)
-      END;
-      Editor.PrintText(app.windows(Editor.Editor))
-    ELSIF link THEN FoStrings.GetErrorStr(422, z)
-    ELSE FoStrings.GetErrorStr(421, z)
-    END;
-    IF z[0] = 0X THEN ShowError(s) ELSE ShowError(z) END
-  END ;
-RETURN success END RunCommand;
-
-PROCEDURE Compile(IN fname, mod: ARRAY OF CHAR; main: BOOLEAN): BOOLEAN;
-BEGIN RETURN RunCommand(fname, mod, FALSE, FALSE, main, NIL)
-END Compile;
-
-PROCEDURE Link(IN fname, mod: ARRAY OF CHAR;
-    graph: BOOLEAN; list: StrList; VAR exename: ARRAY OF CHAR): BOOLEAN;
-VAR ok: BOOLEAN;
-  s: ARRAY 2048 OF CHAR;
-  res: INTEGER;
-BEGIN ok := RunCommand(fname, mod, TRUE, graph, FALSE, list);
-  IF ok THEN (* Move executable file if workDir is non-standard *)
-    s := mod$; IF Config.isWindows THEN Strings.Append('.exe', s) END;
-    exename := 'bin/'; Strings.Append(s, exename);
-    IF workDir # Editor.stdPath THEN
-      Strings.Insert(workDir, 0, s);
-      Files.Rename(exename, s, res);
-      IF res = 0 THEN exename := s$ END
-    END
-  END ;
-RETURN ok END Link;
-
-PROCEDURE ResetSysModules;
-
-  PROCEDURE Add(s: ARRAY OF CHAR);
-  VAR p: StrList;
-  BEGIN NEW(p); p.s := s$; p.fname[0] := 0X;
-    p.next := sysModules; sysModules := p
-  END Add;
-
-BEGIN sysModules := NIL;
-  Add('SYSTEM');   Add('Texts');    Add('Files');   Add('Strings');
-  Add('In');       Add('Out');      Add('Math');    Add('MathL');
-  Add('Modules');  Add('Platform'); Add('Oberon');  Add('Reals');
-  Add('VT100');    Add('Graph');    Add('TermBox'); Add('Term');
-  Add('Allegro5'); Add('Dir');      Add('Int');     Add('Random')
-END ResetSysModules;
-
-PROCEDURE CompileAll(modules: StrList; graph: BOOLEAN;
-    VAR exename: ARRAY OF CHAR): BOOLEAN;
-VAR p, last: StrList;
-  ok: BOOLEAN;
-BEGIN exename[0] := 0X;
-  IF modules # NIL THEN
-    ok := TRUE; p := modules;
-    WHILE ok & (p.next # NIL) DO
-      IF ModuleExists(p.fname) THEN
-        IF ~Compile(p.fname, '', FALSE) THEN ok := FALSE END
-      ELSIF ~IsSysModule(p.s) THEN ok := FALSE
-      END;
-      p := p.next
-    END;
-    IF ok THEN
-      IF ModuleExists(p.fname) THEN
-        IF ~Compile(p.fname, '', TRUE) THEN ok := FALSE END
-      END;
-      ok := ok & Link(p.fname, p.s, graph, modules, exename);
-    END
-  ELSE ok := FALSE
-  END ;
-RETURN ok END CompileAll;
-
 PROCEDURE RunProgram(IN prg: ARRAY OF CHAR);
 VAR dir, err: ARRAY 256 OF CHAR;
   s, d: ARRAY 2048 OF SHORTCHAR;
@@ -697,179 +424,43 @@ VAR w: OV.Window;
 BEGIN
 END OptionsLanguage;
 
-PROCEDURE SkipComment(VAR R: Files.Rider; VAR ch: CHAR; VAR s: ARRAY OF CHAR);
-VAR last: CHAR;
-BEGIN last := ch; Files.ReadChar(R, ch);
-  WHILE ~R.eof & ((last # '*') OR (ch # ')')) DO
-    IF (last = '(') & (ch = '*') THEN SkipComment(R, ch, s) END;
-    last := ch; Files.ReadChar(R, ch)
-  END;
-  IF ~R.eof THEN Files.ReadChar(R, ch) END;
-  WHILE ~R.eof & (ch <= ' ') DO Files.ReadChar(R, ch) END
-END SkipComment;
-
-PROCEDURE ReadCh(VAR R: Files.Rider; VAR ch: CHAR; VAR line, col: INTEGER);
-BEGIN Files.ReadChar(R, ch);
-  IF ch = 0AX THEN INC(line); col := 1 ELSE INC(col) END
-END ReadCh;
-
-PROCEDURE GetSym(VAR R: Files.Rider; VAR ch: CHAR; VAR s: ARRAY OF CHAR;
-    VAR line, col: INTEGER);
-VAR i: INTEGER;
+PROCEDURE BuildErrorCallback(IN fname: ARRAY OF CHAR;
+    col, line, error: INTEGER; IN msg: ARRAY OF CHAR);
+VAR e: Editor.Editor;
 BEGIN
-  WHILE ~R.eof & (ch <= ' ') DO ReadCh(R, ch, line, col) END;
-  i := 0;
-  IF ~R.eof THEN
-    IF ch = '(' THEN
-      ReadCh(R, ch, line, col);
-      IF ch = '*' THEN ReadCh(R, ch, line, col); SkipComment(R, ch, s)
-      ELSE s[i] := ch; INC(i)
+  IF fname[0] # 0X THEN
+    FocusOrOpenFile(fname);
+    e := app.windows(Editor.Editor);
+    IF (col >= 1) & (line >= 1) THEN
+      IF (col = 1) & (line # 1) THEN
+        e.text.MoveToLineCol(line - 1, 256, e.h - 2)
+      ELSE e.text.MoveToLineCol(line, col, e.h - 2)
       END
     END;
-    IF ('A' <= CAP(ch)) & (CAP(ch) <= 'Z') OR (ch = '_') THEN
-      WHILE ~R.eof &
-            (('A' <= CAP(ch)) & (CAP(ch) <= 'Z') OR
-             ('0' <= ch) & (ch <= '9') OR (ch = '_')) DO
-        IF i < LEN(s) - 1 THEN s[i] := ch; INC(i) END;
-        ReadCh(R, ch, line, col)
-      END
-    ELSE
-      WHILE ~R.eof & (ch > ' ') &
-            ~(('A' <= CAP(ch)) & (CAP(ch) <= 'Z') OR
-              ('0' <= ch) & (ch <= '9') OR (ch = '_')) DO
-        IF i < LEN(s) - 1 THEN s[i] := ch; INC(i) END;
-        ReadCh(R, ch, line, col)
-      END
-    END
-  END;
-  s[i] := 0X
-END GetSym;
-
-(** Returns true if the two module names are equal.
-  modname is the identifier that comes after the keyword MODULE.
-  filebase is the file name without the extension; on Windows it
-  is allowed to be written in different case. *)
-PROCEDURE EqualModuleNames(modname, filebase: ARRAY OF CHAR): BOOLEAN;
-BEGIN IF Config.isWindows THEN Strings.Cap(modname); Strings.Cap(filebase) END;
-RETURN modname = filebase END EqualModuleNames;
-
-PROCEDURE GetImportedModules(IN fname, modname: ARRAY OF CHAR;
-    VAR ok: BOOLEAN; VAR line, col: INTEGER): StrList;
-VAR F: Files.File;
-  R: Files.Rider;
-  top, p: StrList;
-  ch: CHAR;
-  mod, s, fname2: ARRAY 256 OF CHAR;
-  exit: BOOLEAN;
-BEGIN ok := FALSE; NEW(top); top.next := NIL; p := top;
-  F := Files.Old(fname);
-  IF F # NIL THEN
-    Files.Set(R, F, 0); Files.ReadChar(R, ch);
-    line := 1; col := 1; GetSym(R, ch, s, line, col);
-    IF s = 'MODULE' THEN GetSym(R, ch, s, line, col);
-      IF EqualModuleNames(modname, s) THEN
-        GetSym(R, ch, s, line, col);
-        IF s = ';' THEN GetSym(R, ch, s, line, col); ok := TRUE;
-          IF s = 'IMPORT' THEN GetSym(R, ch, s, line, col); exit := FALSE;
-            WHILE ~exit & ('A' <= CAP(s[0])) & (CAP(s[0]) <= 'Z') DO
-              mod := s; GetSym(R, ch, s, line, col); fname2[0] := 0X;
-              IF s = ':=' THEN GetSym(R, ch, s, line, col);
-                mod := s; GetSym(R, ch, s, line, col)
-              END;
-              IF IsSysModule(mod) OR FindModule(mod, fname2) THEN
-                NEW(p.next); p := p.next; p.next := NIL;
-                p.s := mod$; p.fname := fname2$
-              END;
-              IF s = ',' THEN GetSym(R, ch, s, line, col)
-              ELSE exit := FALSE
-              END
-            END
-          END
-        END
-      END
-    END
-  END ;
-RETURN top.next END GetImportedModules;
-
-PROCEDURE AddUniqueToList(what: StrList; VAR where: StrList);
-VAR p, q, nextP: StrList;
-BEGIN
-  IF where = NIL THEN where := what
-  ELSE
-    p := what;
-    WHILE p # NIL DO
-      nextP := p.next;
-      IF where.s # p.s THEN
-        q := where;
-        WHILE (q.next # NIL) & (q.next.s # p.s) DO q := q.next END;
-        IF q.next = NIL THEN q.next := p; p.next := NIL END
-      END;
-      p := nextP
-    END
-  END
-END AddUniqueToList;
-
-PROCEDURE UsedModuleList(IN modname, fname: ARRAY OF CHAR;
-    VAR ok: BOOLEAN; VAR errFname: ARRAY OF CHAR;
-    VAR errLine, errCol: INTEGER): StrList;
-VAR res, list, list2, p: StrList;
-BEGIN res := NIL; ok := TRUE;
-  IF ~IsSysModule(modname) THEN
-    list := GetImportedModules(fname, modname, ok, errLine, errCol); p := list;
-    IF ok THEN
-      WHILE ok & (p # NIL) DO
-        list2 := UsedModuleList(p.s, p.fname, ok, errFname, errLine, errCol);
-        AddUniqueToList(list2, res);
-        p := p.next
-      END
-    ELSE Strings.Copy(fname, errFname)
-    END
-  END;
-  IF ok THEN
-    NEW(p); p.s := modname$; p.fname := fname$; p.next := NIL;
-    AddUniqueToList(p, res)
-  END ;
-RETURN res END UsedModuleList;
-
-PROCEDURE ImportsGraph(p: StrList): BOOLEAN;
-BEGIN WHILE (p # NIL) & (p.s # 'Graph') DO p := p.next END ;
-RETURN p # NIL END ImportsGraph;
-
-(* "Module.Mod" -> "Module" *)
-PROCEDURE GetModuleName(IN fname: ARRAY OF CHAR; VAR modname: ARRAY OF CHAR);
-VAR i, j: INTEGER;
-BEGIN i := 0; j := 0;
-  WHILE fname[i] # 0X DO INC(i) END; DEC(i);
-  WHILE (i # -1) & (fname[i] # '/') DO DEC(i) END; INC(i);
-  WHILE (fname[i] # 0X) & (fname[i] # '.') DO
-    modname[j] := fname[i]; INC(i); INC(j)
+    Editor.PrintText(app.windows(Editor.Editor))
   END;
-  modname[j] := 0X
-END GetModuleName;
+  ShowError(msg)
+END BuildErrorCallback;
 
 PROCEDURE OnBuild(c: OV.Control);
 VAR w: OV.Window;
-  graph, ok: BOOLEAN;
+  graph: BOOLEAN;
   mainFname, modname, exename, errFname, s: ARRAY 256 OF CHAR;
-  errLine, errCol: INTEGER;
-  modules: StrList;
+  errLine, errCol, res: INTEGER;
+  modules: Builder.StrList;
   e: Editor.Editor;
 BEGIN w := c.app.windows;
   IF (w # NIL) & (w IS Editor.Editor) THEN
     IF Editor.TextChanged(w(Editor.Editor)) THEN FileSave(c) END;
     IF w(Editor.Editor).fname[0] # 0X THEN
       mainFname := w(Editor.Editor).fname$;
-      SetWorkDir(mainFname);
-      GetModuleName(mainFname, modname);
-      modules := UsedModuleList(modname, mainFname, ok,
-        errFname, errLine, errCol);
-      IF ok THEN
-        graph := ImportsGraph(modules);
-        needWindowed := graph;
-        IF CompileAll(modules, graph, exename) THEN
-          tempWindowed := needWindowed & T.IsFS();
-          tempWindowed := FALSE; (*!FIXME Test on Linux and then remove tempWindowed alltogeter*)
-          IF tempWindowed THEN T.SwitchToWindow END;
+      Builder.SetWorkDir(mainFname);
+      Builder.GetModuleName(mainFname, modname);
+      modules := Builder.UsedModuleList(modname, mainFname,
+        errFname, errLine, errCol, res);
+      IF res = 0 THEN
+        graph := Builder.ImportsGraph(modules);
+        IF Builder.CompileAll(modules, graph, exename, BuildErrorCallback) THEN
           RunProgram(exename)
         END
       ELSE
@@ -900,12 +491,12 @@ BEGIN w := OV.NewWindow(); w.modal := TRUE;
   L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
 
   FoStrings.Get('version', s); Strings.Append(' ', s);
-  Strings.Append(version, s);
+  Strings.Append(Config.version, s);
   L := OV.NewLabel(s); L.align := OV.center;
   L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
 
   FoStrings.Get('copyright', s); Strings.Append(' 2017-', s);
-  Int.Append(year, s); Strings.Append(' ', s);
+  Int.Append(Config.year, s); Strings.Append(' ', s);
   FoStrings.Append('copyrightBy', s);
   
   L := OV.NewLabel(s); L.align := OV.center;
@@ -1243,7 +834,7 @@ BEGIN
     IF (L < 4) OR (Strings.Pos('.Mod', s, L - 4) = -1) THEN
       Strings.Append('.Mod', s)
     END;
-    Strings.Insert(Editor.stdPath, 0, s);
+    Strings.Insert(Config.stdPath, 0, s);
   END
 END ParseFileNameArg;
 
@@ -1308,8 +899,6 @@ BEGIN
   T.Init;
   IF T.Done THEN
     InitIDE;
-    needWindowed := TRUE;
-    ResetSysModules;
     OpenFiles(fnames);
     success := TRUE
   ELSE Out.String('Terminal init failed.'); Out.Ln

+ 33 - 1
src/Graph.Mod

@@ -239,7 +239,6 @@ TYPE
     dx*, dy*, dz*, dw*: INTEGER;
     button*: INTEGER;
     buttons*: SET; (* What mouse buttons are pressed *)
-    down*: BOOLEAN;
     count*: LONGINT; (* Timer counter *)
     key*: INTEGER; (* Physical key code *)
     ch*: CHAR; (* Typed character for event.type = char *)
@@ -710,6 +709,39 @@ BEGIN
     FLT(x2) + 0.5, FLT(y2) + 0.5, SYSTEM.VAL(Al.Color, color), 1.0)
 END Rect;
 
+PROCEDURE CircleF*(x, y, r: REAL; color: Color);
+BEGIN
+  Al.draw_circle(x, y, r, SYSTEM.VAL(Al.Color, color), 1.0)
+END CircleF;
+
+PROCEDURE Circle*(x, y, r: INTEGER; color: Color);
+BEGIN
+  Al.draw_circle(FLT(x) + 0.5, FLT(y) + 0.5, FLT(r),
+    SYSTEM.VAL(Al.Color, color), 1.0)
+END Circle;
+
+PROCEDURE ThickCircleF*(x, y, r: REAL; color: Color; thickness: REAL);
+BEGIN
+  Al.draw_circle(x, y, r, SYSTEM.VAL(Al.Color, color), thickness)
+END ThickCircleF;
+
+PROCEDURE ThickCircle*(x, y, r: INTEGER; color: Color; thickness: INTEGER);
+BEGIN
+  Al.draw_circle(FLT(x) + 0.5, FLT(y) + 0.5, FLT(r),
+    SYSTEM.VAL(Al.Color, color), FLT(thickness))
+END ThickCircle;
+
+PROCEDURE FillCircleF*(x, y, r: REAL; color: Color);
+BEGIN
+  Al.draw_filled_circle(x, y, r, SYSTEM.VAL(Al.Color, color))
+END FillCircleF;
+
+PROCEDURE FillCircle*(x, y, r: INTEGER; color: Color);
+BEGIN
+  Al.draw_filled_circle(FLT(x) + 0.5, FLT(y) + 0.5, FLT(r),
+    SYSTEM.VAL(Al.Color, color))
+END FillCircle;
+
 PROCEDURE NewBitmap*(w, h: INTEGER): Bitmap;
 VAR b: Bitmap;
 BEGIN NEW(b);

+ 19 - 6
src/make.sh

@@ -1,5 +1,6 @@
 #/bin/bash
-PROG="FreeOberon"
+PROG1="FreeOberon"
+PROG2="fob"
 OFRDIR="../Data/bin/OfrontPlus/Target/Linux_amd64"
 
 PATH="$OFRDIR:$PATH"
@@ -54,8 +55,12 @@ $OFR -Cw EditorText.Mod &&
 
 $OFR -Cw Editor.Mod &&
 
+$OFR -Cw Builder.Mod &&
+
 $OFR -Cwm FreeOberon.Mod &&
 
+$OFR -7wm Fob.Mod &&
+
 
 
 $CCFULL -c Utf8.c &&
@@ -77,14 +82,22 @@ $AR -crs ../Data/bin/libFreeOberon.a \
   Utf8.o Strings.o Reals.o Int.o In.o Out.o Args.o Files.o Texts.o Random.o \
   StrList.o Dir.o Graph.o TermBox.o &&
 
-$CCFULL -o ../$PROG \
+$CCFULL -o ../$PROG1 \
   Graph.c TermBox.c \
-  Config.c term/term_linux.c \
-  Term.c OV.c FoStrings.c EditorText.c Editor.c \
-  $PROG.c \
+  Term.c term/term_linux.c \
+  Config.c OV.c FoStrings.c EditorText.c Editor.c Builder.c \
+  FreeOberon.c \
   ../Data/bin/libFreeOberon.a \
   $OFRDIR/Lib/libOfront.a \
   $(pkg-config \
     allegro_primitives-5 allegro_image-5 allegro_audio-5 \
     allegro_acodec-5 allegro_font-5 allegro_dialog-5 \
-    allegro-5 --libs --cflags)
+    allegro-5 --libs --cflags) &&
+
+$CCFULL -o ../$PROG2 \
+  FoStrings.c Builder.c \
+  Term.c term/term_linux.c \
+  Config.c Fob.c \
+  ../Data/bin/libFreeOberon.a \
+  $OFRDIR/Lib/libOfront.a
+

+ 2 - 0
src/pack_linux.sh

@@ -25,6 +25,8 @@ echo      BEGIN PACK
 
 [ -d "$FOLDER" ] && mv $FOLDER $BAKFOLDER
 mkdir -p $FOLDER/bin
+cp ../bin/fungame.in $FOLDER/bin/
+cp ../bin/TEXT.DAT $FOLDER/bin/
 
 cp ../*.{md,sh} $FOLDER/
 cp ../LICENSE $FOLDER/