Implement #embed directive (C23).

This is a fairly straightforward implementation, which works fine for small files but can be slow or run out of memory for sufficiently large ones. This should be good enough for most realistic use cases on the IIGS, but it could be refined in the future if desired.

Also, it behaves more like the version specified in C++26 in that embed parameters are always macro-expanded, rather than following the somewhat complicated C23 rules about macro expansion. GCC and Clang also seem to work similarly, even in C23 mode.
This commit is contained in:
Stephen Heumann committed 2025-06-28 13:31:14 -05:00
1 parent 13f2487f28
commit 3803d28f08
5 files changed
+608 -17

No files matched your search

+2
View File
@@ -997,6 +997,8 @@ var
if kind = preprocessorExpression then
if token.name^ = 'defined' then begin
{handle the preprocessor 'defined' function}
if prohibitDefined then
Error(161);
expandMacros := false;
NextToken;
sp^.token.kind := intconst;
+516 -17
View File
@@ -84,6 +84,7 @@ var
token: tokenType; {next token to process}
doingFakeFile: boolean; {processing tokens from fake "file" in memory?}
keywordMask: integer; {mask of keyword categories to enable}
prohibitDefined: boolean; {prohibit defined preprocessor operator}
{#pragma ignore flags}
{--------------------}
@@ -108,6 +109,15 @@ procedure DoDefaultsDotH;
{ Handle the defaults.h file }
function DoEmbed (doingHashEmbed: boolean): integer;
{ #embed NAME pp-tokens(opt) }
{ __has_embed ( NAME pp-tokens(opt) ) }
{ }
{ parameter: }
{ doingHashEmbed - true for #embed, false for __has_embed }
procedure Error (err: integer);
{ flag an error }
@@ -211,6 +221,12 @@ const
c23keyword = 2;
orcacKeyword = 128;
{embed status values}
{(matching __has_embed results)}
embedNotFound = 0;
embedFound = 1;
embedEmpty = 2;
{misc}
{----}
defaultName = '13:ORCACDefs:Defaults.h'; {default include file name}
@@ -236,6 +252,11 @@ type
disp: longint; {disp of next character to process}
end;
closeOSDCB = record {Close DCB}
pcount: integer;
refNum: integer;
end;
getFileInfoOSDCB = record
pcount: integer;
pathName: gsosInStringPtr;
@@ -252,6 +273,34 @@ type
resourceBlocks: longint;
end;
openOSDCB = record {Open DCB}
pcount: integer;
refNum: integer;
pathName: gsosInStringPtr;
requestAccess: integer;
resourceNumber: integer;
access: integer;
fileType: integer;
auxType: longint;
storageType: integer;
createDateTime: timeField;
modDateTime: timeField;
optionList: optionListPtr;
dataEOF: longint;
blocksUsed: longint;
resourceEOF: longint;
resourceBlocks: longint;
end;
readWriteOSDCB = record {WriteGS DCB}
pcount: integer;
refNum: integer;
dataBuffer: ptr;
requestCount: longint;
transferCount: longint;
cachePriority: integer;
end;
expandDevicesDCBGS = record
pcount: integer;
inName: gsosInStringPtr;
@@ -278,6 +327,7 @@ var
dateStr: longStringPtr; {macro date string}
doingCommandLine: boolean; {are we processing the cc= command line?}
doingDigitSequence: boolean; {do we want a digit sequence (for #line)?}
doingEmbed: boolean; {doing #embed directive?}
doingPPExpression: boolean; {are we processing a preprocessor expression?}
doingStringOrCharacter: boolean; {used to suppress comments in strings}
errors: array[1..maxErr] of errorType; {errors in this line}
@@ -316,8 +366,30 @@ var
lintErrors: set of 1..maxLint; {lint error codes}
spaceStr: string[2]; {string ' ' (used in stringization)}
quoteStr: string[2]; {string '"' (used in stringization)}
comma: char; {comma character (used for #embed)}
numericConstants: set of tokenClass; {token classes for numeric constants}
{---------------------------------------------------------------}
{GS memory manager}
{-----------------}
procedure DisposeHandle (theHandle: handle); tool ($02, $10);
function NewHandle (blockSize: longint; userID, memAttributes: integer;
memLocation: ptr): handle; tool($02, $09);
{GS/OS calls}
{------------}
procedure CloseGS (var parms: closeOSDCB); prodos ($2014);
procedure GetFileInfoGS (var parms: getFileInfoOSDCB); prodos ($2006);
procedure OpenGS (var parms: openOSDCB); prodos ($2010);
procedure ReadGS (var parms: readWriteOSDCB); prodos ($2012);
{-- External procedures; see expression evaluator for notes ----}
procedure EndInclude (chPtr: ptr); extern;
@@ -369,9 +441,6 @@ procedure FlagPragmas (pragma: pragmas); extern;
{ 1. From Header.pas }
procedure GetFileInfoGS (var parms: getFileInfoOSDCB); prodos ($2006);
procedure StartInclude (name: gsosOutStringPtr); extern;
{ Marks the start of an include file }
@@ -414,6 +483,10 @@ function CnvULLX (val: longlong): extended; extern;
{ parameters: }
{ val - the unsigned long long value }
function ult (x,y: longint): integer; extern;
{ unsigned 32-bit comparison }
{-- Scanner support --------------------------------------------}
procedure CheckDelimiters (var name: pString);
@@ -833,6 +906,9 @@ if list or (numErr <> 0) then begin
201: msg := @'variably modified type is not allowed here';
202: msg := @'goto or switch enters scope of identifier with variably modified type';
203: msg := @'static rounding direction is not supported';
204: msg := @'error reading embedded file';
205: msg := @'duplicate #embed parameter';
206: msg := @'unknown #embed parameter';
end; {case}
if extraStr <> nil then begin
extraStr^ := concat(msg^,extraStr^);
@@ -2287,12 +2363,14 @@ saveNumber := false; {stop saving numeric strings}
end; {Expand}
function GetFileName (mustExist: boolean): boolean;
function GetFileName (mustExist, requireSrc, requireEOL: boolean): boolean;
{ Read a file name from a directive line }
{ }
{ parameters: }
{ mustExist - should we look for an existing file? }
{ requireSrc - must the file have SRC file type? }
{ requireEOL - must the file name be followed by EOL? }
{ }
{ Returns true if successful, false if not. }
{ }
@@ -2346,11 +2424,13 @@ var
var
lname: pString; {local copy of name}
fileType: integer; {file type}
begin {GetLibraryName}
lname := concat('13:ORCACDefs:', name);
Expand(lname);
if GetFileType(lname) = SRC then begin
fileType := GetFileType(lname);
if (fileType = SRC) or (not requireSrc and (fileType <> -1)) then begin
name := lname;
GetLibraryName := true;
end {if}
@@ -2371,11 +2451,13 @@ var
var
lname: pstring; {work string}
pp: pathRecordPtr; {used to trace the path list}
fileType: integer; {file type}
begin {GetLocalName}
lname := name;
Expand(lname);
if GetFileType(lname) = SRC then begin
fileType := GetFileType(lname);
if (fileType = SRC) or (not requireSrc and (fileType <> -1)) then begin
GetLocalName := true;
name := lname;
end {if}
@@ -2384,7 +2466,8 @@ var
pp := pathList;
while pp <> nil do begin
lname := concat(pp^.path^, name);
if GetFileType(lname) = SRC then begin
fileType := GetFileType(lname);
if (fileType = SRC) or (not requireSrc and (fileType <> -1)) then begin
GetLocalName := true;
name := lname;
Expand(name);
@@ -2517,11 +2600,12 @@ else begin
end; {else}
while charKinds[ord(ch)] = ch_white {finish processing the current line}
do NextCh;
if charKinds[ord(ch)] <> ch_eol then {check for extra stuff on the line}
begin
Error(11);
GetFileName := false;
end; {if}
if requireEOL then
if charKinds[ord(ch)] <> ch_eol then {check for extra stuff on the line}
begin
Error(11);
GetFileName := false;
end; {if}
gettingFileName := false; {not in GetFileName}
end; {GetFileName}
@@ -2575,7 +2659,7 @@ if default then begin {get the file name}
gotName := true;
end {if}
else
gotName := GetFileName(true);
gotName := GetFileName(true, true, true);
if gotName then begin {read the file name from the line}
OpenFile := true; {we opened it}
@@ -2620,6 +2704,401 @@ else
end; {OpenFile}
function DoEmbed {doingHashEmbed: boolean): integer};
{ #embed NAME pp-tokens(opt) }
{ __has_embed ( NAME pp-tokens(opt) ) }
{ }
{ parameter: }
{ doingHashEmbed - true for #embed, false for __has_embed }
label 1;
const
eofEncountered = $4C; {GS/OS error code}
var
haveFile: boolean;
pathname: gsosInString; {GS/OS style name}
giRec: getFileInfoOSDCB; {GetFileInfo record}
opRec: openOSDCB; {OpenGS record}
rdRec: readWriteOSDCB; {ReadGS record}
clRec: closeOSDCB; {CloseGS record}
readSize: longint; {size of data to be read from file}
bufHandle: handle; {buffer to hold data read from file}
dataPtr: ptr; {work pointer into data buffer}
i: longint; {index variable}
ch: byte; {value of a byte read from file}
prefix: tokenListRecordPtr; {tokens from prefix parameter}
suffix: tokenListRecordPtr; {tokens from suffix parameter}
ifEmpty: tokenListRecordPtr; {tokens from if_empty parameter}
limit: longint; {value from limit parameter}
numString: packed array [0..3] of char; {string for one byte value}
procedure DisposeTokens(tokens: tokenListRecordPtr);
{ Dispose a token sequence }
var
tTokens: tokenListRecordPtr;
begin {DisposeTokens}
while tokens <> nil do begin
tTokens := tokens^.next;
dispose(tokens);
tokens := tTokens;
end; {while}
end; {DisposeTokens}
procedure EmbedParameters;
{ handle embed parameters }
var
prefixName: stringPtr;
paramName: stringPtr;
hasArguments: boolean;
tokens: tokenListRecordPtr;
hasPrefix,hasSuffix,hasIfEmpty,hasLimit: boolean;
procedure MakeIdentToken;
{ Make token into an ident token. It must be an identifier, }
{ typedef name, or reserved word to begin with. }
begin {MakeIdentToken}
if token.class = reservedWord then begin
token.name := @reservedWords[token.kind];
token.kind := ident;
token.class := identifier;
end {if}
else if token.kind = typedef then
token.kind := ident;
end; {MakeIdentToken}
function BalancedTokens: tokenListRecordPtr;
{ Parse a balanced token sequence (where all parentheses, }
{ brackets, and braces are balanced and properly nested). }
{ The sequence ends with an unmatched ). }
type
delimiterRecPtr = ^delimiterRec;
delimiterRec = record
endToken: tokenEnum;
next: delimiterRecPtr;
end;
var
nestedDelimiters: delimiterRecPtr; {stack of nested delimiters}
tDelimiters: delimiterRecPtr; {work pointer}
done: boolean; {done with loop?}
tokens: tokenListRecordPtr; {the tokens found}
tTokens: tokenListRecordPtr; {work pointer}
begin {BalancedTokens}
nestedDelimiters := nil;
tokens := nil;
done := false;
repeat
if token.kind in [lparench,lbracech,lbrackch] then begin
new(tDelimiters);
tDelimiters^.next := nestedDelimiters;
case token.kind of
lparench: tDelimiters^.endToken := rparench;
lbracech: tDelimiters^.endToken := rbracech;
lbrackch: tDelimiters^.endToken := rbrackch;
end; {case}
nestedDelimiters := tDelimiters;
end {if}
else if token.kind in [rparench,rbracech,rbrackch] then begin
if nestedDelimiters = nil then begin
if token.kind <> rparench then
Error(194);
done := true;
end {if}
else if nestedDelimiters^.endToken = token.kind then begin
tDelimiters := nestedDelimiters;
nestedDelimiters := nestedDelimiters^.next;
dispose(tDelimiters);
end {if}
else
Error(194);
end; {else if}
if not done then begin
new(tTokens);
tTokens^.next := tokens;
tTokens^.token := token;
tTokens^.tokenStart := tokenStart;
tTokens^.tokenEnd := tokenEnd;
tokens := tTokens;
NextToken;
end; {if}
until done or (token.kind = eofsy);
while nestedDelimiters <> nil do begin
tDelimiters := nestedDelimiters;
nestedDelimiters := nestedDelimiters^.next;
dispose(tDelimiters);
end; {while}
BalancedTokens := tokens;
end; {BalancedTokens}
procedure TokensParameter(var tokens: tokenListRecordPtr);
{ Process a parameter that takes a token sequence argument }
begin {TokensParameter}
if hasArguments then begin
NextToken;
tokens := BalancedTokens;
NextToken;
end {if}
else begin
tokens := nil;
Error(13);
end; {else}
end; {TokensParameter}
procedure NumericParameter(var value: longint);
{ Process a parameter that takes a numeric argument }
begin {NumericParameter}
if hasArguments then begin
prohibitDefined := true;
NextToken;
Expression(preprocessorExpression, [eolsy]);
value := expressionValue; {TODO adjustments}
if value < 0 then
if expressionType^.kind = scalarType then
if not (expressionType^.baseType in [cgULong,cgUQuad]) then
Error(98);
prohibitDefined := false;
NextToken;
end {if}
else begin
value := 0;
Error(13);
end; {else}
end; {NumericParameter}
procedure UnknownParameter;
{ Process an unknown parameter }
var
tokens: tokenListRecordPtr;
begin {UnknownParameter}
if doingHashEmbed then
Error(206);
haveFile := false;
if hasArguments then begin
TokensParameter(tokens);
DisposeTokens(tokens);
end; {if}
end; {UnknownParameter}
begin {EmbedParameters}
hasPrefix := false;
hasSuffix := false;
hasIfEmpty := false;
hasLimit := false;
while token.class in [reservedWord,identifier] do begin
MakeIdentToken;
prefixName := nil;
new(paramName);
paramName^ := token.name^;
NextToken;
if token.kind = coloncolonsy then begin
NextToken;
if token.class in [reservedWord,identifier] then begin
MakeIdentToken;
prefixName := paramName;
new(paramName);
paramName^ := token.name^;
NextToken;
end {if}
else
Error(9);
end; {if}
hasArguments := token.kind = lparench;
if prefixName = nil then begin
if ((paramName^ = 'if_empty') or (paramName^ = '__if_empty__'))
then begin
if hasIfEmpty then
Error(205);
hasIfEmpty := true;
TokensParameter(ifEmpty);
end {if}
else if ((paramName^ = 'prefix') or (paramName^ = '__prefix__'))
then begin
if hasPrefix then
Error(205);
hasPrefix := true;
TokensParameter(prefix);
end {else if}
else if ((paramName^ = 'suffix') or (paramName^ = '__suffix__'))
then begin
if hasSuffix then
Error(205);
hasSuffix := true;
TokensParameter(suffix);
end {else if}
else if ((paramName^ = 'limit') or (paramName^ = '__limit__'))
then begin
if hasLimit then
Error(205);
hasLimit := true;
NumericParameter(limit);
end {else if}
else
UnknownParameter;
end {if}
else
UnknownParameter; {else}
dispose(paramName);
if prefixName <> nil then
dispose(prefixName);
end; {while}
end; {EmbedParameters}
procedure EmbedTokens(tokens: tokenListRecordPtr);
{ Insert a token sequence into the token stream }
begin {EmbedTokens}
while tokens <> nil do begin
if token.kind <> eolsy then
PutBackToken(token, false, false);
token := tokens^.token;
tokenStart := tokens^.tokenStart;
tokenEnd := tokens^.tokenEnd;
tokens := tokens^.next;
end; {while}
end; {EmbedTokens}
begin {DoEmbed}
prefix := nil;
suffix := nil;
ifEmpty := nil;
limit := -1;
haveFile := GetFileName(true, false, false); {get file name}
pathname.theString := workString;
pathname.size := length(workString);
NextToken;
EmbedParameters; {process embed parameters}
if doingHashEmbed then begin
if token.kind <> eolsy then {check for extra stuff on the line}
Error(11);
end; {else}
if haveFile then begin
giRec.pcount := 9; {get file length}
giRec.pathName := @pathname;
giRec.optionList := nil;
GetFileInfoGS(giRec);
if toolerror <> 0 then
haveFile := false
else
if ult(giRec.dataEOF, limit) <> 0 then
readSize := giRec.dataEOF
else
readSize := limit;
if doingHashEmbed then begin {embed the data}
if readSize <> 0 then begin
bufHandle := NewHandle(readSize, UserID, $C000, nil);
if toolerror <> 0 then
TermError(5);
opRec.pCount := 3;
opRec.pathname := @pathname;
opRec.requestAccess := 1;
OpenGS(opRec);
if toolerror <> 0 then begin
haveFile := false;
goto 1;
end; {if}
rdRec.pCount := 4;
rdRec.refNum := opRec.refNum;
rdRec.dataBuffer := bufHandle^;
rdRec.requestCount := readSize;
ReadGS(rdRec);
if (toolerror <> 0) and (toolError <> eofEncountered) then
haveFile := false;
readSize := rdRec.transferCount;
clRec.pCount := 1;
clRec.refnum := opRec.refNum;
CloseGS(clRec);
if haveFile and (readSize <> 0) then begin
EmbedTokens(suffix);
i := readSize - 1;
while i >= 0 do begin
dataPtr := pointer(ord4(bufHandle^) + i);
ch := dataPtr^;
if token.kind <> eolsy then
PutBackToken(token, false, false);
token.kind := intconst;
token.class := intConstant;
token.ival := ch;
if saveNumber then begin
numString := cnvis(ch);
token.numString := pointer(GMalloc(length(numString)+1));
CopyString(token.numString, @numString);
tokenStart := @token.numString^[1];
tokenEnd := pointer(
ord4(@token.numString^[length(token.numString^)]) + 1);
end {if}
else
token.numString := nil;
if i <> 0 then begin
PutBackToken(token, false, false);
token.kind := commach;
token.class := reservedSymbol;
tokenStart := @comma;
tokenEnd := pointer(ord4(@comma) + 1);
end; {if}
i := i - 1;
end; {for}
EmbedTokens(prefix);
end {if}
else
EmbedTokens(ifEmpty);
1: DisposeHandle(bufHandle);
end {if}
else
EmbedTokens(ifEmpty);
end {if}
end; {if}
DisposeTokens(prefix);
DisposeTokens(suffix);
DisposeTokens(ifEmpty);
if not haveFile then
DoEmbed := embedNotFound
else if readSize = 0 then
DoEmbed := embedEmpty
else
DoEmbed := embedFound;
end; {DoEmbed}
procedure PreProcess;
{ Handle preprocessor commands }
@@ -3269,7 +3748,7 @@ var
{ #pragma keep FILENAME }
begin {DoKeep}
if GetFileName(false) then begin {read the file name}
if GetFileName(false, false, true) then begin {read the file name}
FlagPragmas(p_keep);
if not ignoreSymbols then
if pragmaKeepFile = nil then begin
@@ -3475,6 +3954,7 @@ var
begin {PreProcess}
preprocessing := true;
doingEmbed := false;
lSuppressMacroExpansions := suppressMacroExpansions; {inhibit token printing}
suppressMacroExpansions := true;
lReportEOL := reportEOL; {we need to see eol's}
@@ -3542,6 +4022,14 @@ if ch in ['a','d','e','i','l','p','u','w'] then begin
if tskipping then goto 2;
DoError(true);
goto 2;
end {else if}
else if (token.name^ = 'embed')
and ((cStd >= c23) or not strictMode) then begin
if tskipping then goto 2;
if DoEmbed(true) = embedNotFound then
Error(204);
doingEmbed := true;
goto 2;
end; {else if}
'i':
if token.name^ = 'if' then begin
@@ -3898,8 +4386,9 @@ if not tSkipping then
expandMacros := false; {skip to the end of the line}
flagOverflows := false;
skipping := tSkipping;
while not (token.kind in [eolsy,eofsy]) do
NextToken;
if not doingEmbed then
while not (token.kind in [eolsy,eofsy]) do
NextToken;
flagOverflows := true;
expandMacros := true;
reportEOL := lReportEOL; {restore flags}
@@ -4685,6 +5174,7 @@ customDefaultName := nil; {no custom default name}
pragmaKeepFile := nil; {no #pragma keep file so far}
doingFakeFile := false; {not doing a fake file}
doingDigitSequence := false; {not expecting a digit sequence}
prohibitDefined := false; {not prohibiting defined}
preprocessing := false; {not preprocessing}
cStd := c17; {default to C17}
strictMode := false; {...with extensions}
@@ -4694,6 +5184,7 @@ strictMode := false; {...with extensions}
lintErrors :=
[51,104,105,110,124,125,128,129,130,147,151,152,153,154,155,170,185,186];
comma := ','; {used for #embed}
spaceStr := ' '; {strings used in stringization}
quoteStr := '"';
{set of classes for numeric constants}
@@ -5611,7 +6102,11 @@ while charKinds[ord(ch)] in [illegal,ch_white,ch_eol,ch_pound] do begin
if charKinds[ord(ch)] = ch_pound then begin
if lastWasReturn or (token.kind = eolsy) then begin
NextCh; {skip the '#' char}
PreProcess {call the preprocessor}
PreProcess; {call the preprocessor}
if doingEmbed then begin
doingEmbed := false;
goto 2;
end; {if}
end {if}
else
goto 7;
@@ -5827,6 +6322,10 @@ case charKinds[ord(ch)] of
token.kind := poundch; {%: digraph}
if lLastWasReturn then begin
PreProcess;
if doingEmbed then begin
doingEmbed := false;
goto 2;
end; {if}
goto 5;
end;
end;
+1
View File
@@ -2,3 +2,4 @@
{1} c23misc.c
{1} c23tf.c
{1} c23vla.c
{1} c23embed.c
+65
View File
@@ -0,0 +1,65 @@
/*
* Test #embed directive (C23).
*/
#include <stdio.h>
#include <string.h>
#define stringize(...) #__VA_ARGS__
#ifdef __ORCAC__
char s0[] = {
#embed <c23embed.c>
,0};
#endif
char s1[] = {
#embed "c23embed.c"
,0};
char s2[] = {
#embed "c23embed.c" __limit__(2)
,0};
char s3[] = {
#embed "c23embed.c" limit(2) prefix('a','b',) suffix(,'c','d') if_empty('x')
,0};
char s4[] = {
#embed "c23embed.c" limit(0) prefix('a','b',) suffix(,'c','d') if_empty('x')
,0};
#define embedfile "c23embed.c"
#define thelimit limit(2ull)
char s5[] = {
#embed embedfile thelimit
,0};
long f(int a, int b) {
return a*1000L + b;
}
int main(void) {
if (strcmp(s2, "/*") != 0)
goto Fail;
if (strcmp(s3, "ab/*cd") != 0)
goto Fail;
if (strcmp(s4, "x") != 0)
goto Fail;
if (strcmp(s5, "/*") != 0)
goto Fail;
if (f(
#embed "c23embed.c" limit(2)
) != 47042)
goto Fail;
printf ("Passed Conformance Test c23embed\n");
return 0;
Fail:
printf ("Failed Conformance Test c23embed\n");
}
+24
View File
@@ -120,6 +120,30 @@ Functions can be defined as taking variable length array parameters (including m
Note that ORCA/C does not allow local variables to have variable length array types. This is an optional feature in the C23 standard, which ORCA/C does not implement. As an alternative, it is possible to declare a pointer to a variable length array type and then allocate space for the array with malloc() based on the desired size as determined at run time.
15. The #embed preprocessor directive is now supported. It can have one of these forms:
#embed "filename" [parameters]
#embed <filename> [parameters]
The #embed directive embeds the bytes from the specified file into the C source file as a comma separated list of integer constants. For example, if there is a file "testfile" that contains only the word "hello", then the directive
#embed "testfile"
will expand to the token sequence 104,101,108,108,111 because those numbers correspond to the ASCII values of the characters 'h', 'e', 'l', 'l', and 'o'.
The two forms of the #embed directive search for a file in the same way as the #include directive would, except that the file is not required to have the SRC file type. Thus, the form with "filename" looks in the current directory first, while the form with <filename> looks in the ORCACDefs directory first. As with #include, the search path can be modified with #pragma path.
Either form of the #embed directive may also optionally include one or more parameter clauses of the following forms after the file name:
limit(expression)
if_empty(tokens)
prefix(tokens)
suffix(tokens)
The limit clause takes an integer constant expression which limits the number of bytes to embed from the file. The if_empty clause contains an alternate token sequence that will be used if no bytes can be embedded from the file, either because it was empty or because limit(0) was specified. The prefix and suffix clauses specify extra tokens that will be included before or after those embedded from the file, provided that at least one byte is embedded from the file. In the token sequences in the if_empty, prefix, and suffix clauses, any parentheses, brackets, or braces must be balanced and properly nested.
The #embed directive is primarily useful for embedding the contents of small files into the C program. For example, it might be used to embed a small image into the program by using the embedded token sequence from an image file to initialize an array. Note that embedding large files may cause ORCA/C to run out of memory or encounter other limitations; other approaches such as using resources are generally better in those cases.
Library Updates
---------------