[r13063]
by
mikeaubury
dotnet: 30 of the 32 demo programs now compile, plus the d4 application
Standalone demo programs compiling clean went 6 -> 30 of 33. The 33rd,
mkstore4, fails the C toolchain too, so it is a broken demo rather than
a gap here. d4, the eight-module application, stays at zero.
The 2009 C# backend's names were still being emitted throughout -
IfxExecute, IfxResult, IfxBinding, IfxCursorType, BuildIfxCursor,
IfxException, IfxSetError, IfxPrepare, IfxToday, IfxAscii, the
IfxDateTimeQual and IfxIntervalQual enums, and a dozen more. 120 lines
of the generator now name the runtime that exists.
Generator fixes:
- assign_wrapper handled CHAR and DECIMAL and fell through for
everything else, so a SMALLINT target took whatever int-typed
expression was assigned to it. 4GL narrows without comment; C#
refuses to. That alone was 58 of the errors.
- A 4GL literal with a decimal point is a DECIMAL, not a float, and
it was emitted bare - which C# reads as a double, so lv_price *
1.15 would not compile against a DECIMAL. It now carries the m
suffix.
- CONSTRUCT emitted "new UI.Construct(this, ...)", treating UI as a
type; it is a property, and Construct is a factory on it.
- FIELD_TOUCHED and NOT FIELD_TOUCHED are expressions, emitted inside
an if condition, but each wrote a trailing semicolon - a syntax
error that stopped the file parsing and hid every other error in
it.
- The thisApp prefix rule from r13062 was too broad in one direction
and too narrow in the other. Inside a report, Fgl.Length must not
be prefixed - it is a static - but SQL.ASqlExecute must be, because
SQL is a property on the module. Asking whether the name carries a
qualifier cannot tell those apart, so the runtime's static classes
are now named explicitly.
Runtime: MATCHES and NOT MATCHES (Informix's pattern language, not a
regular expression), CURRENT, EXIT PROGRAM, STARTLOG and ERRORLOG,
SHOWHELP, SHOW/HIDE OPTION ALL, OPTIONS with a text value, the full
window attribute set, INPUT ARRAY and DISPLAY ARRAY with their screen
and program arrays, BEFORE/AFTER FIELD over several fields at once,
FIELD_TOUCHED, PRINT of a non-text value, and FormField accepting a
nullable subscript - ARR_CURR lands in a SMALLINT and 4GL widens it
freely.
AsBool now returns bool rather than bool?, but only the cast on
FglModule: it is what the generator emits for IF lv_int THEN, and 4GL
treats NULL as false in a condition. Fgl.AsBool stays nullable, because
a NULL column read out of a row really is null and the difference
matters there.
Still open: ORDER BY reports (report4 and report6, 21 errors each).
That path builds a temp table and re-reads it through a second cursor,
and its generated code refers to a startReportOrdered that is never
emitted, to cursors that are never declared, and reaches the session
from a static context. It is one coherent piece of work rather than a
scatter of small gaps.
Fidelity: 27 semantics, 9 UI messages, 30 USING, the 66-line report and
5 end-to-end programs all agree with C. 203 runtime tests pass.
|
2026-09-02 17:42:28
|
Tree
|
[r13062]
by
mikeaubury
dotnet: the whole d4 demo application now compiles
d4 is the Informix demo app that ships with Aubit - eight modules,
globals in one of them, cross-module calls, forms, menus, input
arrays and two reports. It now generates and compiles to .NET with no
errors. That was 501 errors across its two biggest modules alone when
this started.
Nothing needed to change about how modules combine: 4GL has one flat
function namespace per program, and the generator already emits every
module as "public partial class Simple", so the modules of one program
merge into one class and cross-module calls and globals resolve on
their own. What was missing was the prototype collection - fglproto
takes the .dat module definitions, and the whole program's modules have
to go in together, or a call into another module cannot be typed. Note
that fglproto wants the .dat, not the .4gl, and that a .glb has no
module definition to load, so it has to be left out or the run aborts.
Generator fixes:
- Inside a report, every call was prefixed with thisApp. on the basis
that a report is a nested class and its module's functions live on
the outer one. That was applied before checking whether the name had
already been rewritten to a runtime static, so a builtin came out as
thisApp.Fgl.Length(...), which resolves to nothing. The prefix is
now skipped for an already-qualified name.
- IS NULL and IS NOT NULL were emitted bare, so they only resolved at
module level, not from inside a report. Both now name the runtime.
- INPUT ARRAY binds the SHAPE of a row - the column types and widths,
with the values coming from the array handed to the dialog - but it
emitted the same Append names the value form uses, and the two
collide on arity: AppendString(3) is a CHAR(3) column in one and the
value 3 in the other. The shape form is Declare* now.
Runtime: MESSAGE, ERROR, FGLIdent, DRAW BOX, ARR_CURR, SCR_LINE,
ARR_COUNT, SET_COUNT, STATUS, INT_FLAG; the scroll-cursor fetches
(FIRST, LAST, NEXT, PREVIOUS, ABSOLUTE, RELATIVE); IsNotNull; column
declarations on ASqlBinding; PAUSE with a message.
FglBool is new, and it is worth explaining. 4GL has no boolean type: a
comparison yields an INTEGER, and the language uses it both ways -
IF lv_a = lv_b THEN ... -- a condition
LET lv_flag = lv_a = lv_b -- a value, 0 or 1
C# will take neither an int as a condition nor a bool as a number, so
a comparison now returns a type that converts implicitly to both and
renders as "0"/"1". The first attempt simply returned bool, which
compiled everywhere and was wrong: the types probe caught it printing
"False" where C prints "0". That is exactly the kind of thing these
probes exist to catch, and it would have been invisible in a review.
Standalone demo programs compiling clean: 13 of 33, up from 6. The
largest remaining group is SMALLINT: the generator maps it to short?
but arithmetic on it yields int, and it does not insert a narrowing
cast, which is about 58 of the ~130 errors left.
Fidelity: 27 semantics, 9 UI messages, 30 USING, the 66-line report and
5 end-to-end programs all agree with C. 203 runtime tests pass.
|
2026-09-02 17:26:44
|
Tree
|
[r13061]
by
mikeaubury
dotnet: the UI API generated code targets
The last of the three APIs a generated module refers to. 22 UI entry
points and 7 dialog types, all built on the existing IUiChannel and its
message records.
Every 4GL interactive statement lowers to the same shape:
using (var d = UI.CreateX(...)) {
...configure...
while (d.Run()) { switch (d.GetEventID()) { case 3: ... } }
}
so UILib_Dialog owns that loop and each dialog says only how its own
clauses resolve. Clause ids are allocated by the generator as it walks
the source; -1 means the client did something no clause claimed, which
the generated switch falls through on - the same as 4GL, where an
unhandled key does nothing.
UILib_Menu COMMAND, BEFORE MENU, NEXT/SHOW/HIDE OPTION - which
address an option by its text, as 4GL does
UILib_Prompt PROMPT ... FOR
UILib_Input INPUT, with the BEFORE/AFTER FIELD clauses
UILib_InputArray INPUT ARRAY, plus ARR_CURR and SCR_LINE
UILib_Construct CONSTRUCT, returning the WHERE fragment
UILib_DisplayArray, UILib_keylist, FormField, FglWindowAttribute
The two INPUT bindings are the whole data path: one fills InBind from
the program's variables, the other reads RSet back into them. RSet is
an ASqlResult, the same type a FETCH returns, so an INPUT reads its
fields back with the same GetData calls - which is right, because in
4GL a screen record and a table row are the same thing. FglRow gained
a constructor from a UI event's field values to make that work.
UI is an instance property on FglModule, like SQL, so the channel and
the dialog contexts belong to the session.
DISPLAYTO was wrong and is now right. The generated protocol stub
carried its values as one opaque string; the real message sends the
field list and the values as two parallel arrays, neither
null-terminated, with a numeric ATTRIBUTE of -1 when the statement
named none, and values tagged with the 4GL type code the client
formats by. It was found by reading lib/libui/ui_json/json.c and then
confirmed against the live C runtime - the first version of this was
modelled on uilib.c, which is a different backend and disagrees.
It is the single most common thing a generated program sends - 83 call
sites in the demo corpus - so the UI probe now covers it: two fields,
two 4GL types, and the blank padding a CHAR keeps on its way to the
form. The two streams agree byte for byte.
A protocol guard test earned its keep here, failing the moment two
records claimed the DISPLAYTO verb.
Fidelity: 27 semantics, 9 UI messages, 30 USING, the 66-line report and
5 end-to-end programs all agree with C. 203 runtime tests pass.
|
2026-09-02 17:12:44
|
Tree
|
[r13060]
by
mikeaubury
configure: stop the usage-report prompt killing configure on a headless build
The tty probe in AUBIT_USAGE_REPORT was written as
if test -c /dev/tty && { exec 9</dev/tty; } 2>/dev/null; then
A brace group is not a subshell - it runs in the current shell - so a
failing exec redirection terminates that shell. /dev/tty is exactly the
case where that bites: the device node exists, so test -c passes, but
opening it fails whenever the process has no controlling terminal.
So on every scripted install - Ansible, CI, a docker build - configure
printed its summary, died silently at that line, and exited 1 with no
error message. The macro's own comment promises it "can never make
configure fail", which is what it was trying to do by redirecting
stderr; the redirect hides the message but not the death.
Attempting the open in a subshell contains the failure:
if test -c /dev/tty && ( exec 9</dev/tty ) 2>/dev/null; then
Verified with setsid and no stdin: configure now reaches "Run 'make' to
build." and exits 0, and still exits 0 with a terminal present.
Also add a fourth path to the informix/esql/decimal.h probe. The three
existing ones all assume a distribution include directory - pgsql/,
postgresql/, pg/ - but with a versioned prefix such as PGDG's
/usr/pgsql-18 the headers sit directly under the include directory, so
the path relative to -I is a bare informix/esql/decimal.h. That is also
the path the HAVE_PG_INFORMIX_ESQL_DECIMAL_H branch of
a4gl_esql_postgres.h actually includes, so the check now matches what
the code does.
Worth noting for anyone reading a config.log: these header probes
reporting "no", with gcc's "fatal error: ... No such file or directory"
underneath, is what a negative AC_CHECK_HEADER looks like. It is the
test doing its job, not a build failure - and nothing in a PostgreSQL
build consumes those defines anyway, since a4gl_esql_postgres.h is
included only by lib/libsql/esqlc, which configure does not build.
|
2026-09-02 17:01:52
|
Tree
|
[r13059]
by
mikeaubury
dotnet: the SQL and report APIs generated code actually targets
A generated module referred to a SQL and report API that had never been
written on the .NET side: SQL.*, ASqlCursor, ASqlBinding, ASqlResult,
ASqlException, sqlca, BaseFGLReport, and the aggCount/aggSum family.
Compilation stopped at the first missing type, which hid the rest - so
what looked like two errors was ninety-one once the first two were
stubbed out.
Runtime, all built on the existing FglSql/FglCursor/FglRow layer:
ASqlBinding an ordered value list - the generator uses the same
construct for SQL host variables, DISPLAY and INPUT
ASqlResult one row, with GetData in both the by-ordinal and the
out-parameter form the generator emits
ASqlCursor a declared cursor, with the four SCROLL/WITH HOLD
combinations and the insert cursor
ASql the throwing face of FglSql: generated code wraps every
statement in a try/catch built from the WHENEVER state,
which only works if the operation raises
BaseFGLReport the page engine, driven through events rather than
inheritance so one module can declare several reports
agg* COUNT, SUM, AVG, MIN, MAX and PERCENT
SQL and sqlca are instance properties on FglModule rather than statics,
so the connection and status area stay on the session and two programs
can run in one process.
Generator fixes found while doing it:
- DECLARE SCROLL CURSOR WITH HOLD emitted ScrollCursor and plain
SCROLL CURSOR emitted ScrollCursorWithHold - the two branches were
swapped. The non-scroll branch below was already right.
- PAGENO and LINENO were emitted bare, referring to identifiers that
were never declared. They only mean anything inside a report, so
they now name the report's driver.
- USING emitted a bare Using(), unreachable from a nested report
class; it now lowers to the runtime's formatter, which also grew a
date-mask path since USING applies to DATE as well as to numbers.
- START REPORT ... WITH PAGE LENGTH / MARGINS lowered to setter calls
on the report object that were never emitted. They are now, and
they ignore the -1 the generator passes for an option the source
did not give - otherwise a report that set margins in its OUTPUT
section had them wiped the moment it started.
- WITH TOP OF PAGE called setTopMargin. Copy-paste.
Two page-layout details came out of diffing against the C runtime
rather than from reading it: the page trailer sits against the bottom
margin rather than directly under the last body line, and its height
has to be measured before the first page is laid out or the body takes
one row too many and the break lands in the wrong place. The trailer is
run into a buffer for both.
A 4GL report - page header, ON EVERY ROW, PAGE TRAILER with PAGENO, ON
LAST ROW with COUNT(*) and SUM(), USING masks, margins and page length
- now produces byte-identical output through both toolchains across
three pages, and is added to the end-to-end probe as report.4gl.
Fidelity: 27 semantics, 8 UI, 30 USING, the 66-line report, and 5
end-to-end programs all agree with C. 203 runtime tests pass.
|
2026-09-02 16:44:01
|
Tree
|
[r13058]
by
mikeaubury
lex_cs: emit the report driver margins as valid C#
print_output_rep_normal() emitted the five REPORT margin assignments as a
bare 'Driver.topMargin=' followed by real_print_expr(), with no
terminator - and printc() puts each fragment on its own indented line
unless newlines are suppressed, so the generated source came out as
Driver.topMargin=
1
Driver.bottomMargin=
1
Driver.rightMargin was also missing its '=' entirely. Every REPORT with
an OUTPUT section therefore failed to compile: 36 errors in the one
demo program that has one, and it was the only thing still stopping it.
Factored the repetition into print_margin_assign(), which wraps the
expression in set_nonewlines()/clr_nonewlines() so each assignment is
one statement on one line. Also dropped a duplicated Driver.outputLoc
emission - it was written twice, the second copy without the stdout
else-branch.
d4_orders.4gl now generates and compiles clean. Fidelity probes still
agree with the C runtime (27 semantics, 8 UI, 30 USING, 66-line report,
4 end-to-end programs) and the 203 runtime tests pass.
|
2026-09-02 14:49:07
|
Tree
|
[r13057]
by
mikeaubury
configure: restore --with-pg-all, and find PostgreSQL where RHEL puts it
Reported: PostgreSQL is no longer detected from SVN, while the released
tarball detects it and builds. Alma Linux 8, PostgreSQL 18, invoked as
./configure --with-pg-all=/usr/pgsql-18
--with-pg-all was a shorthand in the old configure.in that pointed every
PostgreSQL component at one prefix. The rewrite dropped it, and autoconf
only WARNS about an unrecognised --with-... - so configure completed,
the option did nothing, and PostgreSQL silently went missing. It is
restored here, setting --with-postgresql and --with-ecpg unless those
were named explicitly.
This did not show up on Debian or Ubuntu because libpq.pc is on the
default pkg-config path there, so detection succeeded anyway and masked
the ignored option. On RHEL, Alma and Rocky the PGDG packages install
under /usr/pgsql-NN with nothing on the default PATH or PKG_CONFIG_PATH,
so both pkg-config and a bare pg_config come up empty.
So detection also gained a fallback chain: pkg-config, then pg_config on
PATH, then pg_config under /usr/pgsql-*, /usr/local/pgsql-* and /opt/*
newest first, then a direct search for libpq-fe.h in the usual include
directories. Whatever answers is then verified by compiling against
libpq-fe.h and linking PQconnectdb, so a stale pg_config reports "not
found" rather than breaking the build later.
Verified: --with-pg-all against a /usr/pgsql-18 style prefix gives
-I<prefix>/include and -L<prefix>/lib -lpq; and with pkg-config unable
to see libpq, the pg_config fallback still finds it.
|
2026-09-02 14:21:45
|
Tree
|
[r13056]
by
mikeaubury
Update the vendored SQLite3 ODBC driver from 2007 to 0.99991
The vendored copy dated from 13 May 2007 (r9422) and segfaulted on any
statement carrying a bind parameter - which is nearly every statement a
4GL program issues - so SELECT ... WHERE id = lv_id or INSERT with
variables crashed while all-literal statements worked.
Two causes, both long since fixed upstream:
It built its SQL by textual substitution, calling
sqlite3_vmprintf(s->query, (char *) params) - an array of char * cast to
a va_list. That is valid on i386, where a va_list is effectively a
pointer to the stack arguments, and invalid on x86-64, where it is a
struct of register save area offsets. GCC 14 rejects the cast outright,
which is why this file was already built with the diagnostic switched
off. Upstream replaced the whole mechanism with sqlite3_bind_*() on
prepared statements years ago.
Its header was also pinned to sqlite3-local.h, which declares SQLite
3.0.8 (2004), while the plug-in links the system libsqlite3 3.45.1.
Anything added since 3.0.8 - sqlite3_prepare_v2, sqlite3_malloc - was
implicitly declared and had its returned pointer truncated on a 64 bit
build.
Upstream's sqlite3odbc.c and .h replace both files, keeping the small
Aubit preamble (the A4GL_debug shim and #define AUBIT). Three build
flags follow upstream's renames: WITHOUT_DRIVERMGR became
USE_DLOPEN_FOR_GPPS, WITHOUT_WINTERFACE avoids needing unixodbc-dev, and
the in-tree ODBC header gains SQL_C_WCHAR, a standard constant it
predates.
Parameterised statements, cursors and quote escaping now all work.
|
2026-09-02 13:17:26
|
Tree
|
[r13055]
by
mikeaubury
Generator: unblock report modules and fix generated label names
Measured against fgldemo - 41 modules, 5900 lines - rather than guessed
at. Two fixes, both found by trying to compile it:
binding.o was never in the plug-in's link line, and binding.c is in any
case wrapped entirely in #ifdef CRAP, so A4GL_dtype_sz was never
compiled even though compile_cs_sql.c calls it. Every REPORT module
failed at dlopen with an undefined symbol. The guard now closes before
the dtparts table, leaving decode_datetime and A4GL_dtype_sz live - the
only two the caller needs - and the two SPRINTF macros in them, whose
header is also inside the disabled part, are plain snprintf now.
Generated labels used %d on a value that can be negative, producing
continue_input_-908174256, which is not a C# identifier. Now %u.
fgldemo now generates 39 of 41 modules, up from 36. The two that do not
are LOAD/UNLOAD, which are assertion stubs in the generator, and
DROP DATABASE, which Aubit refuses everywhere and is not a C# issue.
|
2026-09-02 11:58:39
|
Tree
|
[r13054]
by
mikeaubury
.NET: 4GL's implicit conversions, display widths, and a README
4GL allows LET lv_char = lv_int and converts on the way; C# does not. The
generator already resolves this at compile time, emitting an explicit
conversion only where the declared types differ - LET b = a between two
INTEGERs still emits a plain assignment. This adds the conversions it
emits: AsChar, AsString, AsInt, AsSmallInt, AsBigInt, AsDecimal, AsFloat,
AsSmallFloat, AsBool, AsDate, AsDateTime, AsMonthSpan and AsTimeSpan.
They are hand-written rather than System.Convert, which is wrong in the
three places that matter and has a test spelling it out: Convert rounds
7.9 to 8 where 4GL truncates to 7, turns NULL into 0 where 4GL keeps
NULL, and throws on "abc" where 4GL yields NULL.
DISPLAY now passes each value's width, not just a DECIMAL's precision.
4GL prints an INTEGER in 11 columns, a SMALLINT in 6, a FLOAT in 14 - and
a NULL still occupies its field, which a boxed null cannot know on its
own. CHAR is deliberately excluded: it is already padded at assignment,
and widening it again would defeat CLIPPED.
A fifth end-to-end program, convert.4gl, covers thirteen cross-type
assignments including truncation of negatives and NULL propagation. All
four end-to-end programs and the other four probes pass.
Adds dotnet/README.md.
|
2026-09-02 11:36:42
|
Tree
|