Quad EC C Source Code Analysis

From NARS2000
Jump to: navigation, search

⎕EC - C Source Code Analysis:

File <qf_ec.c> (supporting APL System Function System Function ⎕EC - Execute Controlled) located in NARS trunk folder is a model example for returning a nested vector to APL from a System Function call. Remember per your technical knowledge of APL: ⎕EC returns a 3-element nested vector to APL.

Executive Summary - Conceptual C Programmers Preliminary Overview/Preview

Variable lpYYRes upon completion of ⎕EC contains the entire 3-element nested vector result of the call to <qf_ec.c>, specifically, from monadic function SysFnMonEC_EM_YY. lpYYRes also contains a pointer to an object <hGlbRes> that is created by other code in the same file.


That global memory handle <hGlbRes> points (via lpMemRes = MyGlobalLock (hGlbRes)) to a VARARRAY_HEADER followed by the actual data (a three-element vector). The header contains various fields such as Array Type (ARRAY_NESTED), the NELM (3), the Rank (1), the dimension (3), followed by the data. As it is a nested array that gets returned, the 3-elements of the data are all pointers to a SYMENTRY (for immediate values -- BOOL, INT, FLT, and CHAR) or a global memory handle for all other items, e.g. Global Numeric, APA, HETERO (mixed array-datatype e.g. integer, char/unicode string, float double, etc.), any non-immediate array, etc.


In a sense, the internal structure of arrays in NARS C code are nested just as APL arrays themselves are nested. Nesting is ubiquitous in NARS/APL2, etc.. Think about C coding source layouts and program flow from the standpoint of "How would you design the internal structures for an APL interpreter?" The C source code would, wherever possible, parallel the APL code which APL users view and understand.


⎕EC is somewhat more complex than other APL system functions in that ⎕EC executes its right argument under controlled circumstances - this also holds true for the C code itself - complexity in the returned data structure, i.e. ⎕EC mirrors APL variable structuring.


Subordinate execution of ⎕EC's right argument returns an <LPPL_YYSTYPE> variable-type pointer(ptr) just as it's umbrella overarching function ⎕EC does. Many APL prime functions such as rho, take, iota, etc. as coded in C - also return nested arguments while many APL System Functions merely return scalars.


Variable name <lpYYRes2> in file <qf_ec.c>'s C source code indicates it is a secondary result (re the execution of ⎕EC's right argument).


Independent of how actual execution of the APL user's argument proceeded, after executing that right arg, the Exit Type is converted into an integer Return Code (as that's the form of the first output item - the first element in ⎕EC's output vector, in ⎕EC's result vector). That first item is a simple numeric scalar, but since the result of ⎕EC is a three-element nested vector, <qf_ec.c>/SysFnMonEC_EM_YY must convert the Return Code into a LPSYMENTRY via <MakeSymEntry_EM>.


In particular - (file <qf_ec.c> C source code extract just below):

    // Save the return code in the FIRST ELEMENT of ⎕EC - C's index origin is zero hence ref. to lpMemRes[0]
    lpMemRes[0] = MakeSymEntry_EM (IMMTYPE_INT,     // Immediate type
                      &aplLongestRC,    // Ptr to immediate value
                       lptkFunc);       // Ptr to function token

Technically, ⎕EC's second output element is the value of ⎕ET, usually a 2-element vector of zeroes, at present.


What happens next depends upon whether or not the APL user's right argument execution succeeded:


If execution succeeded: ⎕EC returns the value or string value of the executed result as the third element in its output vector.


If execution failed: ⎕EC returns the error message associated with that failure, a unicode string - as ⎕EC's third output element.

Comparison Structure Configurations - Different Returned Outputs

1. ⎕T (file <qf_t.c> code extract) initial structure configuration for returned solitary scalar number:

    // Allocate a new YYRes
    lpYYRes = YYAlloc ();
    // Fill in the result token
    lpYYRes->tkToken.tkFlags.TknType   = TKT_VARIMMED;
    lpYYRes->tkToken.tkFlags.ImmType   = IMMTYPE_FLOAT;    // Or use IMMTYPE_INT  for an integer
    // lpYYRes->tkToken.tkFlags.NoDisplay = FALSE;         // Already zero from YYAlloc
    lpYYRes->tkToken.tkData.tkFloat    = aplFloatSec;      // DataType APLFLOAT aplFloatSec  Or use  DataType APLINT with tkData.tkInteger
    lpYYRes->tkToken.tkCharIndex       = lptkFunc->tkCharIndex;


2. ⎕EC (file <qf_ec.c> code extract) initial structure configuration for returned 3-element nested vector:

    // Allocate a new YYRes
    lpYYRes = YYAlloc ();                                  // Setup line # 1 for lpYYRes
    // Fill in the result token
    lpYYRes->tkToken.tkFlags.TknType   = TKT_VARARRAY;     // Setup line # 2 for lpYYRes
    // lpYYRes->tkToken.tkFlags.ImmType   = IMMTYPE_ERROR; // Already zero from YYAlloc
    // lpYYRes->tkToken.tkFlags.NoDisplay = FALSE;         // Already zero from YYAlloc
    lpYYRes->tkToken.tkData.tkGlbData  = MakePtrTypeGlb (hGlbRes);   // Setup line # 3 for lpYYRes
    lpYYRes->tkToken.tkCharIndex       = lptkFunc->tkCharIndex;      // Setup line # 4 for lpYYRes

Differences: Note TKT_VARIMMED for ⎕T vs. TKT_VARARRAY for ⎕EC. Also note ⎕EC's use of hGlbRes for nested returned data, re: lpYYRes->tkToken.tkData.tkGlbData = MakePtrTypeGlb (hGlbRes); Nesting occurs in ⎕EC output through the use of TKT_VARARRAY as token flags token type and through use of nested memory pointer MakePtrTypeGlb (hGlbRes) in var lpYYRes via the token's global data. From a purely structural perspective, excluding 1)pre-setup work in ⎕EC, 2)comment lines and 3)post-op processing C code, only 4 lines of C code were needed (above) to setup (not calculate) return result variable lpYYRes for ⎕EC. See also tkToken Sample Uses Table.


3. ⎕TS (file <qf_ts.c> code extract) more substantive structure configuration for a returned plain-vanilla / homogeneous 7-element unmixed vector:

#define lpMemData   ((LPAPLINT) lpMemRes)    // The following seven vector elements returned are all integers
    lpMemData[0] = SystemTime.wYear;
    lpMemData[1] = SystemTime.wMonth;
    lpMemData[2] = SystemTime.wDay;
    lpMemData[3] = SystemTime.wHour;
    lpMemData[4] = SystemTime.wMinute;
    lpMemData[5] = SystemTime.wSecond;
    lpMemData[6] = SystemTime.wMilliseconds;
#undef  lpMemData

    // Memory ptr no longer needed, allow unlocking/freeing hGlbRes(technically freed upon returning from ⎕TS), reset lpMemRes pointer to null
    MyGlobalUnlock (hGlbRes); lpMemRes = NULL;

    // Allocate a new YYRes
    lpYYRes = YYAlloc ();

    // Fill in the result token
    lpYYRes->tkToken.tkFlags.TknType   = TKT_VARARRAY;
    //  lpYYRes->tkToken.tkFlags.ImmType   = IMMTYPE_ERROR; // Already zero from YYAlloc
    //  lpYYRes->tkToken.tkFlags.NoDisplay = FALSE;         // Already zero from YYAlloc
    lpYYRes->tkToken.tkData.tkGlbData  = MakePtrTypeGlb (hGlbRes);
    lpYYRes->tkToken.tkCharIndex       = lptkFunc->tkCharIndex;

    return lpYYRes;

The above return information C coding for ⎕TS is included because setup for ⎕TS's integers output vector is relatively straightforward, i.e. less complex than a nested vector(⎕EC) but not much more complex than a scalar (⎕T).


4. ⎕DR (file <qf_dr.c> code extract) detailed structure configuration for returned string (CHAR_ARRAY) char/string vector (extracted from function SysFnDR_Show_EM_YY):

    // Variable declarations (type varname), needed for C code extracted just below:
    APLUINT           ByteRes;          // # bytes in the result
    LPPL_YYSTYPE      lpYYRes;          // Ptr to the result
    APLNELM           aplNELMRes;       // Result NELM
    APLCHAR           wszTemp[512];     // Temporary APLCHARs Unicode string variable
    HGLOBAL           hGlbRes;          // Result    ...
    LPVOID            lpMemRes;         // Ptr to result global memory

    // Get the result NELM
    aplNELMRes = lstrlenW (wszTemp);

    // Calculate space needed for the result
    ByteRes = CalcArraySize (ARRAY_CHAR, aplNELMRes, 1);

    // Check for overflow
    if (ByteRes NE (APLU3264) ByteRes)
        goto WSFULL_EXIT;

    // Allocate space for the result
    hGlbRes = DbgGlobalAlloc (GHND, (APLU3264) ByteRes);
    if (hGlbRes EQ NULL)
        goto WSFULL_EXIT;

    // Lock the memory to get a ptr to it
    lpMemRes = MyGlobalLock (hGlbRes);

#define lpHeader        ((LPVARARRAY_HEADER) lpMemRes)
    // Fill in the header
    lpHeader->Sig.nature = VARARRAY_HEADER_SIGNATURE;
    lpHeader->ArrType    = ARRAY_CHAR;
////lpHeader->PermNdx    = PERMNDX_NONE;    // Already zero from GHND
////lpHeader->SysVar     = FALSE;           // Already zero from GHND
    lpHeader->RefCnt     = 1;
    lpHeader->NELM       = aplNELMRes;
    lpHeader->Rank       = 1;
#undef  lpHeader

    // Save the dimension in the result
    *VarArrayBaseToDim (lpMemRes) = aplNELMRes;

    // Point to the data (APLAPA struct)
    lpMemRes = VarArrayDataFmBase (lpMemRes);

    // Copy text to the result
    CopyMemory (lpMemRes, wszTemp, (APLU3264) BytesIn (ARRAY_CHAR, aplNELMRes));

    // No longer need this ptr
    MyGlobalUnlock (hGlbRes); lpMemRes = NULL;

    // Allocate a new YYRes
    lpYYRes = YYAlloc ();

    // Fill in the result token
    lpYYRes->tkToken.tkFlags.TknType   = TKT_VARARRAY;
////lpYYRes->tkToken.tkFlags.ImmType   = IMMTYPE_ERROR; // Already zero from YYAlloc
////lpYYRes->tkToken.tkFlags.NoDisplay = FALSE;         // Already zero from YYAlloc
    lpYYRes->tkToken.tkData.tkGlbData  = MakePtrTypeGlb (hGlbRes);
    lpYYRes->tkToken.tkCharIndex       = lptkFunc->tkCharIndex;

    return lpYYRes;