Create an executable for a program in an interpreted language

From Rosetta Code
Create an executable for a program in an interpreted language is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Languages can be implemented by interpreters or compilers. Some languages are implemented by both techniques.

If a language is implemented via a compiler, the developer (usually) writes one or more source files in the language, submits them to the compiler and (all being well) receives a compiled executable in return. That executable only does what the specific program does.
If the language is implemented via an interpreter, the decveloper (usually) writes one or more source files and invokes an interpreter to execute the program. Generally "object code" as such, is not created.

Suppose we want to have it both ways and we want to "compile" the interpreted code and get an executable that will only do what the specific interpreted program does i.e.., not a general interpreter.

So, instead of supplying the source for the program, we just want to produce an executable for that specific program. The purpose of this task is to show how it could be done for your language.

One method of doing this would be to create a program in a language for which there is a compiler available (C for example) that contanss the source of the program to be interpreted, writes it to a temporary file and calls the interpreter to run it and then deletes the temporary file afterwards.

Alternatively, if the interpreter provides itself as an API and allows the source to be specified by other means - as a string perhaps, the need to write a temporary file would be avoided.

For example, let's assume we need an executable that will run the Hello World program in the interpreter only language I. The I source might be:

    print "Hello, World!"


then (assuming the I interpreter is invoked with the command "InterpretI" and a suitable file for the temporary source is /tmp/t) a suitable C source might be: <lang c>#include <stdio.h>

  1. include <errno.h>

static void w( char * line, FILE * tf ) {fputs( line, tf );

if( errno != 0 ){ perror( "temp" );exit( 2 ); }

} int main( int argc, char ** argv ) {FILE * tf = fopen( "/tmp/t", "w" );

if( tf == NULL ){ perror( "temp" );exit( 1 ); }
w( "    print \"Hello, World!\"\n", tf );
fclose( tf );
system( "InterpretI /tmp/t" );
remove( "/tmp/t" );

} </lang> If your language has other ways of doing this e.g., the interpreter provides an API that would let you specify the code via a string instead of storing it in a file, demonstrate how this would be done.
If the implementation provides a standard utility or option to do this, show the commands necessary to do it.

Note, this task is about showing how to achieve the goal, blurb is good but code or commands are required.

if your language can only be compiled (unlikely as that may seem - I've heard that there are C interpreters, for example), you could omit it from the task, write an interpreter :) or ...

If you do generate a source, it need not be in C - it can be in any language for which a compiler is available (perhaps your language).

Whether or not the executable can be cross-compiled for a platform other than the one your program runs on is assumed to be a property of the compiler you are going to use to create the executable.


ALGOL 68

There have been several implementations of Algol 68, both compiled and interpreted. A popular implementation currently available for Windows and Linux is ALGOL 68G which is an interpreter (though under Linux a hybrid part compilation, part interpreted mode is available). This example follows the suggestion in the task description and creates a C source that will write the Algol 68 source and run the interpreter.

Works with: ALGOL 68G version Any - tested with release 2.8.3.win32

<lang algol68>IF # create an executable to run a program with Algol 68G #

   # a C program is created that writes the source to a temporary file and  #
   # then calls a the Algol 68G interpreter to run it                       #
   # get the temporary file path, the path to the interpreter, the source   #
   # file and the path for the generated C source                           #
   STRING temp file path, algol 68g path, a68 source, c source;
   print( ( "Temporary file path                      (target system) : " ) );
   read( ( temp file path, newline ) );
   print( ( "Command to run the Algol 68G interpreter (target system) : " ) );
   read( ( algol 68g path, newline ) );
   print( ( "Algol 68 source                            (this system) : " ) );
   read( ( a68 source, newline ) );
   print( ( "C source to generate                       (this system) : " ) );
   read( ( c source, newline ) );
   FILE input source file, output source file;
   open( input source file, a68 source, stand in channel ) /= 0

THEN

   # failed to open the Algol 68 source file                                #
   print( (  "Unable to open """ + a68 source + """", newline ) )

ELIF IF open( output source file, c source, stand out channel ) = 0

    THEN
        # opened OK - file already exists and will be overwritten           #
        FALSE
    ELSE
        # failed to open the file - try creating a new file                 #
        establish( output source file, c source, stand out channel ) /= 0
    FI

THEN

   # failed to open the C source file                                       #
   print( ( "Unable to open """ + c source + """", newline ) );
   close( input source file )

ELSE

   # files opened OK                                                        #
   # returns line with any " or \ characters preceded by \                  #
   PROC add escapes = ( STRING line )STRING:
        BEGIN
           [ 1 : ( ( UPB line + 1 ) - LWB line ) * 2 ]CHAR output line;
           INT o pos := 0;
           FOR l pos FROM LWB line TO UPB line DO
               CHAR c = line[ l pos ];
               IF c = "\" OR c = """" THEN
                   # must escape this character in C                        #
                   output line[ o pos +:= 1 ] := "\"
               FI;
               output line[ o pos +:= 1 ] := c
           OD;
           output line[ 1 : o pos ]
        END # add escapes # ;
   # writes line to the output source file                                  #
   PROC emit = ( STRING line )VOID:
        put( output source file, ( line, newline ) );
   BOOL at eof := FALSE;
   # set the EOF handler for the input file                                 #
   on logical file end( input source file
                      , ( REF FILE f )BOOL:
                          BEGIN
                              # note that we reached EOF on the latest read #
                              at eof := TRUE;
                              # return TRUE so processing can continue      #
                              TRUE
                          END
                        );
   # include headers, output routine and the start of the main routine      #
   emit( "#include <stdio.h>" );
   emit( "#include <errno.h>" );
   emit( "static void w( char * line, FILE * tf )" );
   emit( "{fputs( line, tf );" );
   emit( " if( errno != 0 ){ perror( ""temp"" );exit( 2 ); }" );
   emit( "}" );
   emit( "int main( int argc, char ** argv )" );
   emit( "{FILE * tf = fopen( """
       + add escapes( temp file path )
       + """, ""w"" );"
       );
   emit( " if( tf == NULL ){ perror( ""temp"" );exit( 1 ); }" );
   # output code to write the Algol 68 source to the temporary file         #
   WHILE STRING line;
         get( input source file, ( line, newline ) );
         NOT at eof
   DO
       IF line /= "" THEN
           # line is not empty                                              #
           emit( " w( """ + add escapes( line ) + "\n"", tf );" )
       FI
   OD;
   close( input source file );
   # code to close the temporary file and interpret it then delete it       #
   emit( " fclose( tf );" );
   emit( " system( """
       + add escapes( algol 68g path )
       + " "
       + add escapes( temp file path )
       + """ );"
       );
   emit( " remove( """ + add escapes( temp file path ) + """ );" );
   emit( "}" );
   close( output source file );
   print( ( newline
          , c source
          , " generated"
          , newline
          , "This can now be compiled for the target system "
          , "(possibly via cross-compilation)"
          , newline
          , "using a suitable C compiler"
          , newline
          )
        )

FI</lang>

Output:

Assuming /tmp/t is a temporary file that could be written to by the generated C program and that the command to run ALGOL 68G is a68g, a sample run of the program might be:

Temporary file path                      (target system) : /tmp/t
Command to run the Algol 68G interpreter (target system) : a68g
Algol 68 source                            (this system) : hw.a68
C source to generate                       (this system) : _hw_a68.c

_hw_a68.c generated
This can now be compiled for the target system (possibly via cross-compilation)
using a suitable C compiler

If hw.a68 contains: <lang algol68>print( ( "Hello, World!", newline ) ) </lang>

The generated _hw_a68.c would contain: <lang c>#include <stdio.h>

  1. include <errno.h>

static void w( char * line, FILE * tf ) {fputs( line, tf );

if( errno != 0 ){ perror( "temp" );exit( 2 ); }

} int main( int argc, char ** argv ) {FILE * tf = fopen( "/tmp/t", "w" );

if( tf == NULL ){ perror( "temp" );exit( 1 ); }
w( "print( ( \"Hello, World!\", newline ) )\n", tf );
fclose( tf );
system( "a68g /tmp/t" );
remove( "/tmp/t" );

}</lang>

AWK

Translation of: ALGOL 68

<lang awk># create an executable to run a program with awk

  1. a C program is created that writes the source to a temporary file and
  2. then calls a the Awk interpreter to run it

BEGIN \ {

   FALSE = 0;
   TRUE  = 1;
   # get the temporary file path, the path to the interpreter, the source
   # file and the path for the generated C source
   printf( "Temporary file path                (target system) : " );
   getline tempPath;
   printf( "Command to run the Awk interpreter (target system) : " );
   getline awkInterpreter;
   printf( "Awk source                           (this system) : " );
   getline awkSource;
   printf( "C source to generate                 (this system) : " );
   getline cSource;
   atEof   = FALSE;
   ioError = FALSE;
   # include headers, output routine and the start of the main routine
   printf( "" ) > cSource;
   emit( "#include <stdio.h>" );
   emit( "#include <errno.h>" );
   emit( "static void w( char * line, FILE * tf )" );
   emit( "{fputs( line, tf );" );
   emit( " if( errno != 0 ){ perror( \"temp\" );exit( 2 ); }" );
   emit( "}" );
   emit( "int main( int argc, char ** argv )" );
   emit( "{FILE * tf = fopen( \"" addEscapes( tempPath ) "\", \"w\" );" );
   emit( " if( tf == NULL ){ perror( \"temp\" );exit( 1 ); }" );
   # output code to write the Awk source to the temporary file
   do
   {
       line = readLine( awkSource );
       sub( / *$/, "", line );
       if( ! atEof && line != "" )
       {
           emit( " w( \"" addEscapes( line ) "\\n\", tf );" )
       } # if ! atEof
   }
   while( ! atEof );
   close( awkSource );
   # code to close the temporary file and interpret it then delete it
   emit( " fclose( tf );" );
   emit( " system( \"" addEscapes( awkInterpreter ) \
         " " addEscapes( tempPath ) "\" );"         \
       );
   emit( " remove( \"" addEscapes( tempPath ) "\" );" );
   emit( "}" );
   close( cSource );
   if( ! ioError )
   {
       printf( "\n%s c source generated\n", cSource );
       printf( "This can now be compiled for the target system "     \
               "(possibly via cross-compilation)\n"                  \
               "using a suitable C compiler\n"                       );
   } # if ! ioError

} # BEGIN


function addEscapes( str, result ) {

   result = str;
   gsub( /[\\]/, "\\\\", result );
   gsub( /"/,    "\\\"", result );

return result; } # addEscapes


function emit( line ) {

   printf( "%s\n", line ) >> cSource;

} # emit


function readLine( fName, ioStat,

                                                                      result )

{

   iostat = ( getline result < fName );
   if( iostat < 1 )
   {
       atEof  = TRUE;
       result = "";
       if( iostat < 0 )
       {
           ioError = TRUE;
           printf( "*** Error reading: %s\n", fName );
       } # if iostat < 0
   } # if iostat < 1


return result; } # readLine</lang>

Output:

Assuming /tmp/t is a temporary file that could be written to by the generated C program and that the command to run the awk interpregter is awk -f, a sample run of the program might be:

Temporary file path                (target system) : /tmp/t
Command to run the Awk interpreter (target system) : awk -f
Awk source                           (this system) : hw.awk
C source to generate                 (this system) : _hw_awk.c

_hw_awk.c c source generated
This can now be compiled for the target system (possibly via cross-compilation)
using a suitable C compiler

Assuming hw.awk contains: <lang awk>BEGIN \ {

   printf( "Hello, World!\n" );

} # BEGIN</lang>

_hw_awk.c would contani: <lang c>#include <stdio.h>

  1. include <errno.h>

static void w( char * line, FILE * tf ) {fputs( line, tf );

if( errno != 0 ){ perror( "temp" );exit( 2 ); }

} int main( int argc, char ** argv ) {FILE * tf = fopen( "/tmp/t", "w" );

if( tf == NULL ){ perror( "temp" );exit( 1 ); }
w( "BEGIN \\\n", tf );
w( "{\n", tf );
w( "    printf( \"Hello, World!\\n\" );\n", tf );
w( "} # BEGIN\n", tf );
fclose( tf );
system( "awk -f /tmp/t" );
remove( "/tmp/t" );

}</lang>

Phix

Phix is a hybrid compiler/interpreter, so this functionality is already baked in.

If "p test" interprets something then "p -c test" produces an executable, and, unless you also specify "-norun" on the command line, invokes it immediately.
Additionally pwa/p2js can be used to convert "test" into JavaScript, not yet via a command line, you'll have to manually select the file or paste the source code in, bar some programs being explictily marked as incompatible via "without javascript_semantics", for instance because they perform disk i/o or similar that is not permitted from within a web browser. However one thing that cannot do is create an "executable" that will run in a web browser.

Despite the completely misinformed protestations in the task description, there is no code that could possibly add any value to this entry, though perhaps if you are sufficiently desperate for such, you could always go and look at Rosetta_Code/Run_examples#Phix, since that writes out text downloaded from the rosettacode site and runs (interprets) it, in at least five different programming languages already.

Wren

Wren source code is always compiled first into an intermediate bytecode using a single-pass compiler which is part of its virtual machine (VM). The VM then interprets the bytecode at runtime with respect to the underlying platform which can be Linux, MacOS, Windows or (in theory at least) anything else for which a standard C99 compiler is available.

Note that, for various reasons, it is not currently possible to intercept the bytecode stream prior to interpretation which would enable a native code AOT compiler to be written for the language and, whilst a JIT compiler would be theoretically possible, Wren's inventor thought that this would severely compromise the essential simplicity of the VM.

Wren is designed for embedding and, technically, even Wren-cli - which enables Wren scripts to be run from the command line - is just a host application for the Wren VM which uses the cross-platform library, libuv, to provide additional functionality (mainly I/O) not provided by Wren's standard library itself.

If we don't need this additional functionality, we can instead write a simple C host, link it to the Wren VM library and then use the latter to compile and run some Wren source code which we can embed in the C program itself. In fact, the VM is so small that its source could also be embedded directly into the C host though we won't do that here.

So the following C program (countdown.c) is perhaps the nearest we can get to the spirit of this task.

<lang C>#include <stdio.h>

  1. include "wren.h"

static void writeFn(WrenVM* vm, const char* text) {

   printf("%s", text);

}

int main(int argc, char **argv) {

   WrenConfiguration config;
   wrenInitConfiguration(&config);
   config.writeFn = &writeFn;
   WrenVM* vm = wrenNewVM(&config);
   const char* module = "main";
   char *script = "for (i in 5..0) System.print(i)"; /* Wren source code */
   WrenInterpretResult result = wrenInterpret(vm, module, script);
   wrenFreeVM(vm);
   return 0;

}</lang>

We can now compile this code (using GCC on Linux) and run it, obtaining the expected output as follows:

Output:
$ gcc countdown.c -o countdown -lwren -lm
$./countdown
5
4
3
2
1
0