Bläddra i källkod

Separate Builder and Foc from FreeOberon; Move constants to Config

Arthur Yefimov 3 år sedan
förälder
incheckning
f09234c1bc
12 ändrade filer med 619 tillägg och 456 borttagningar
  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"
 243 "concatenation of module, type, and guarded variable exceeds maximum name length"
 244 "cyclic type definition not allowed"
 244 "cyclic type definition not allowed"
 265 "unsupported string operation"
 265 "unsupported string operation"
+400 "file not found"
 401 "file contains wrong module name"
 401 "file contains wrong module name"
 421 "compilation failed"
 421 "compilation failed"
 422 "linking failed"
 422 "linking failed"

+ 1 - 0
Data/Texts/ru.dat

@@ -187,6 +187,7 @@
 243 "сцепление модуля, типа и защищённой переменной превышает максимальную длину имени"
 243 "сцепление модуля, типа и защищённой переменной превышает максимальную длину имени"
 244 "циклическое определение типа не допускается"
 244 "циклическое определение типа не допускается"
 265 "строковая операция не поддерживается"
 265 "строковая операция не поддерживается"
+400 "файл не найден"
 401 "файл содержит неверное имя модуля"
 401 "файл содержит неверное имя модуля"
 421 "не удалось скомпилировать программу"
 421 "не удалось скомпилировать программу"
 422 "не удалось скомпоновать программу"
 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;
 MODULE Config;
+IMPORT Platform;
 CONST
 CONST
-  isWindows* = FALSE;
+  isWindows* = Platform.Windows;
+  stdPath* = 'Programs/';
+
+  version* = '1.1.0-alpha.5';
+  year* = 2022;
 END Config.
 END Config.

+ 6 - 1
src/Config_win32.Mod

@@ -1,4 +1,9 @@
 MODULE Config;
 MODULE Config;
+IMPORT Platform;
 CONST
 CONST
-  isWindows* = TRUE;
+  isWindows* = Platform.Windows;
+  stdPath* = 'Programs/';
+
+  version* = '1.1.0-alpha.5';
+  year* = 2022;
 END Config.
 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
 You should have received a copy of the GNU General Public License
 along with Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
 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;
   Int, Strings, FoStrings, StrList, Dir, Out;
 CONST
 CONST
-  stdPath* = 'Programs/';
   dotChar = 0B7X; (* To higlight spaces *)
   dotChar = 0B7X; (* To higlight spaces *)
 
 
   (* Direction of Selection *)
   (* Direction of Selection *)
@@ -180,7 +179,7 @@ BEGIN w := c.parent(FileDialog); s := w.edtFilename.caption$;
         Strings.Append(s, full);
         Strings.Append(s, full);
         IF Match(w.home, full, 0) THEN
         IF Match(w.home, full, 0) THEN
           Strings.Delete(full, 0, Strings.Length(w.home));
           Strings.Delete(full, 0, Strings.Length(w.home));
-          Strings.Insert(stdPath, 0, full)
+          Strings.Insert(Config.stdPath, 0, full)
         END;
         END;
         OV.CloseCurWindow(c);
         OV.CloseCurWindow(c);
         IF w.onFileOk # NIL THEN w.onFileOk(c, full) END
         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)
     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 *)
   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;
   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 *)
   (* ColumnList *)
   c.colFiles := OV.NewColumnList();
   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;
 VAR z: ARRAY 256 OF CHAR;
 BEGIN
 BEGIN
   GetErrorStr(err, z);
   GetErrorStr(err, z);
-  s := 'Ошибка #';
+  s := '#';
   Int.Append(err, s);
   Int.Append(err, s);
-  Strings.Append(': ', s);
+  Strings.Append(' ', s);
   Strings.Append(z, s)
   Strings.Append(z, s)
 END MakeErrorStr;
 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
 You should have received a copy of the GNU General Public License
 along with Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
 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;
        OV, Editor, Term, FoStrings, Config, Strings, Int, Out, Kernel;
 CONST
 CONST
-  version* = '1.1.0-alpha.5';
-  year = 2022;
-
   (* Direction of Selection *)
   (* Direction of Selection *)
   dirLeft  = 0;
   dirLeft  = 0;
   dirRight = 1;
   dirRight = 1;
@@ -53,12 +50,6 @@ CONST
   defLang = 'en';
   defLang = 'en';
 
 
 TYPE
 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;
   Fnames = ARRAY 32, 256 OF CHAR;
 
 
 VAR
 VAR
@@ -66,10 +57,6 @@ VAR
   inputBuf: ARRAY 16300 OF CHAR; (* Saves entered chars before Enter pressed *)
   inputBuf: ARRAY 16300 OF CHAR; (* Saves entered chars before Enter pressed *)
   inputBufLen: INTEGER;
   inputBufLen: INTEGER;
   programFinished: BOOLEAN;
   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;
   app: OV.App;
 
 
   curX, curY: INTEGER; (* Cursor position *)
   curX, curY: INTEGER; (* Cursor position *)
@@ -97,33 +84,6 @@ PROCEDURE ShowError(s: ARRAY OF CHAR);
 BEGIN Editor.SetMsg(app.windows(Editor.Editor), s)
 BEGIN Editor.SetMsg(app.windows(Editor.Editor), s)
 END ShowError;
 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);
 PROCEDURE FileNew(c: OV.Control);
 VAR e: Editor.Editor;
 VAR e: Editor.Editor;
   p, br: OV.Control;
   p, br: OV.Control;
@@ -152,8 +112,8 @@ END FileNew;
 
 
 PROCEDURE FnameToCaption(IN fname: ARRAY OF CHAR; VAR caption: ARRAY OF CHAR);
 PROCEDURE FnameToCaption(IN fname: ARRAY OF CHAR; VAR caption: ARRAY OF CHAR);
 BEGIN
 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)
       LEN(caption), caption)
   ELSE caption := fname$
   ELSE caption := fname$
   END
   END
@@ -183,20 +143,10 @@ BEGIN
   WHILE (e # NIL) & (e.fname # fname) DO
   WHILE (e # NIL) & (e.fname # fname) DO
     IF e.next = f THEN e := NIL ELSE e := e.next(Editor.Editor) END
     IF e.next = f THEN e := NIL ELSE e := e.next(Editor.Editor) END
   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)
   OV.DrawApp(app)
 END FocusOrOpenFile;
 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;
 PROCEDURE ScrollScreen;
 VAR x, y, tW, tH: INTEGER;
 VAR x, y, tW, tH: INTEGER;
   ch: CHAR;
   ch: CHAR;
@@ -258,41 +208,6 @@ BEGIN
   terminalNeedRedraw := TRUE
   terminalNeedRedraw := TRUE
 END Backspace;
 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;
 PROCEDURE PollProgram;
 VAR len, i: INTEGER;
 VAR len, i: INTEGER;
     err: INTEGER;
     err: INTEGER;
@@ -329,7 +244,6 @@ BEGIN
     IF Term.ProcessFinished(err) THEN
     IF Term.ProcessFinished(err) THEN
       Read(TRUE); (* Read everything until pipe is empty *)
       Read(TRUE); (* Read everything until pipe is empty *)
       programFinished := TRUE;
       programFinished := TRUE;
-      IF tempWindowed THEN T.SwitchToFS END;
       IF err = 0 THEN s := ' '; FoStrings.Append('pressAnyKeyToReturnToIde', s)
       IF err = 0 THEN s := ' '; FoStrings.Append('pressAnyKeyToReturnToIde', s)
       ELSE s := ' '; FoStrings.Append('runtimeError', s);
       ELSE s := ' '; FoStrings.Append('runtimeError', s);
         Strings.Append(' ', s); Int.Append(err, s)
         Strings.Append(' ', s); Int.Append(err, s)
@@ -415,193 +329,6 @@ BEGIN quit := FALSE;
   IF ~terminalMouseShown THEN T.ShowMouse END
   IF ~terminalMouseShown THEN T.ShowMouse END
 END RunTerminal;
 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);
 PROCEDURE RunProgram(IN prg: ARRAY OF CHAR);
 VAR dir, err: ARRAY 256 OF CHAR;
 VAR dir, err: ARRAY 256 OF CHAR;
   s, d: ARRAY 2048 OF SHORTCHAR;
   s, d: ARRAY 2048 OF SHORTCHAR;
@@ -697,179 +424,43 @@ VAR w: OV.Window;
 BEGIN
 BEGIN
 END OptionsLanguage;
 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
 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
     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;
   END;
-  modname[j] := 0X
-END GetModuleName;
+  ShowError(msg)
+END BuildErrorCallback;
 
 
 PROCEDURE OnBuild(c: OV.Control);
 PROCEDURE OnBuild(c: OV.Control);
 VAR w: OV.Window;
 VAR w: OV.Window;
-  graph, ok: BOOLEAN;
+  graph: BOOLEAN;
   mainFname, modname, exename, errFname, s: ARRAY 256 OF CHAR;
   mainFname, modname, exename, errFname, s: ARRAY 256 OF CHAR;
-  errLine, errCol: INTEGER;
-  modules: StrList;
+  errLine, errCol, res: INTEGER;
+  modules: Builder.StrList;
   e: Editor.Editor;
   e: Editor.Editor;
 BEGIN w := c.app.windows;
 BEGIN w := c.app.windows;
   IF (w # NIL) & (w IS Editor.Editor) THEN
   IF (w # NIL) & (w IS Editor.Editor) THEN
     IF Editor.TextChanged(w(Editor.Editor)) THEN FileSave(c) END;
     IF Editor.TextChanged(w(Editor.Editor)) THEN FileSave(c) END;
     IF w(Editor.Editor).fname[0] # 0X THEN
     IF w(Editor.Editor).fname[0] # 0X THEN
       mainFname := w(Editor.Editor).fname$;
       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)
           RunProgram(exename)
         END
         END
       ELSE
       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);
   L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
 
 
   FoStrings.Get('version', s); Strings.Append(' ', s);
   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 := OV.NewLabel(s); L.align := OV.center;
   L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
   L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
 
 
   FoStrings.Get('copyright', s); Strings.Append(' 2017-', s);
   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);
   FoStrings.Append('copyrightBy', s);
   
   
   L := OV.NewLabel(s); L.align := OV.center;
   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
     IF (L < 4) OR (Strings.Pos('.Mod', s, L - 4) = -1) THEN
       Strings.Append('.Mod', s)
       Strings.Append('.Mod', s)
     END;
     END;
-    Strings.Insert(Editor.stdPath, 0, s);
+    Strings.Insert(Config.stdPath, 0, s);
   END
   END
 END ParseFileNameArg;
 END ParseFileNameArg;
 
 
@@ -1308,8 +899,6 @@ BEGIN
   T.Init;
   T.Init;
   IF T.Done THEN
   IF T.Done THEN
     InitIDE;
     InitIDE;
-    needWindowed := TRUE;
-    ResetSysModules;
     OpenFiles(fnames);
     OpenFiles(fnames);
     success := TRUE
     success := TRUE
   ELSE Out.String('Terminal init failed.'); Out.Ln
   ELSE Out.String('Terminal init failed.'); Out.Ln

+ 33 - 1
src/Graph.Mod

@@ -239,7 +239,6 @@ TYPE
     dx*, dy*, dz*, dw*: INTEGER;
     dx*, dy*, dz*, dw*: INTEGER;
     button*: INTEGER;
     button*: INTEGER;
     buttons*: SET; (* What mouse buttons are pressed *)
     buttons*: SET; (* What mouse buttons are pressed *)
-    down*: BOOLEAN;
     count*: LONGINT; (* Timer counter *)
     count*: LONGINT; (* Timer counter *)
     key*: INTEGER; (* Physical key code *)
     key*: INTEGER; (* Physical key code *)
     ch*: CHAR; (* Typed character for event.type = char *)
     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)
     FLT(x2) + 0.5, FLT(y2) + 0.5, SYSTEM.VAL(Al.Color, color), 1.0)
 END Rect;
 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;
 PROCEDURE NewBitmap*(w, h: INTEGER): Bitmap;
 VAR b: Bitmap;
 VAR b: Bitmap;
 BEGIN NEW(b);
 BEGIN NEW(b);

+ 19 - 6
src/make.sh

@@ -1,5 +1,6 @@
 #/bin/bash
 #/bin/bash
-PROG="FreeOberon"
+PROG1="FreeOberon"
+PROG2="fob"
 OFRDIR="../Data/bin/OfrontPlus/Target/Linux_amd64"
 OFRDIR="../Data/bin/OfrontPlus/Target/Linux_amd64"
 
 
 PATH="$OFRDIR:$PATH"
 PATH="$OFRDIR:$PATH"
@@ -54,8 +55,12 @@ $OFR -Cw EditorText.Mod &&
 
 
 $OFR -Cw Editor.Mod &&
 $OFR -Cw Editor.Mod &&
 
 
+$OFR -Cw Builder.Mod &&
+
 $OFR -Cwm FreeOberon.Mod &&
 $OFR -Cwm FreeOberon.Mod &&
 
 
+$OFR -7wm Fob.Mod &&
+
 
 
 
 
 $CCFULL -c Utf8.c &&
 $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 \
   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 &&
   StrList.o Dir.o Graph.o TermBox.o &&
 
 
-$CCFULL -o ../$PROG \
+$CCFULL -o ../$PROG1 \
   Graph.c TermBox.c \
   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 \
   ../Data/bin/libFreeOberon.a \
   $OFRDIR/Lib/libOfront.a \
   $OFRDIR/Lib/libOfront.a \
   $(pkg-config \
   $(pkg-config \
     allegro_primitives-5 allegro_image-5 allegro_audio-5 \
     allegro_primitives-5 allegro_image-5 allegro_audio-5 \
     allegro_acodec-5 allegro_font-5 allegro_dialog-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
 [ -d "$FOLDER" ] && mv $FOLDER $BAKFOLDER
 mkdir -p $FOLDER/bin
 mkdir -p $FOLDER/bin
+cp ../bin/fungame.in $FOLDER/bin/
+cp ../bin/TEXT.DAT $FOLDER/bin/
 
 
 cp ../*.{md,sh} $FOLDER/
 cp ../*.{md,sh} $FOLDER/
 cp ../LICENSE $FOLDER/
 cp ../LICENSE $FOLDER/