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.
Note: This task is about creating a compiled executable. Some operating systems allow a script to start with e.g. "#!" and the name of the interpreter to run it. This is not. what is reuired, as this would mean shipping the source.

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:

#include <stdio.h>
#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" );
}

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
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
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:

print( ( "Hello, World!", newline ) )

The generated _hw_a68.c would contain:

#include <stdio.h>
#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" );
}

AWK

Translation of: ALGOL 68
# create an executable to run a program with awk
# a C program is created that writes the source to a temporary file and
# 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
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:

BEGIN \
{

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

} # BEGIN

_hw_awk.c would contani:

#include <stdio.h>
#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" );
}

J

I think this task is a duplicate of another task. But it's also about the host operating system.

#!/usr/bin/ijconsole
echo 'hello world'
exit 0

This is a basic example of how to implement a J shell script. This example assumes that J's jconsole was installed at /usr/local/bin/jconsole (note that java also has a jconsole which might be installed in /usr/bin/ -- so on debian and ubuntu which avoid such conflicts but which also do not install to /usr/local/bin the J implementation installs /usr/bin/ijconsole which is what would be needed in the hashbang line). Also, many J installs do not put j executables in a directory in $PATH. So the usual trick of using /usr/bin/env tends to not be useful both because the executable names and locations may vary. Conceptually this means that if you want to distribute a J program you probably need to also distribute a copy of J along with it. Fortunately, J is gpl'd and reasonably small, so that should not be too much of a burden. Also, unix executables must be made executable before they can run...

Also, under Windows, the hashbang line would be ignored, and unnecessary. For windows, there's registry entries tell windows what interpreter to use for a specific extension. So you could eliminate the first line in the above example, but you'd have to do some other work (including picking and using a specific extension).

Meanwhile, we could do something similar with compiled C. Here's a minimal example which builds and runs under Linux:

/* github jsoftware/jsource 903-release-b */
/* link with -ldl */
#include <dlfcn.h>
#include "j.h"
int main() {
    void* jdll= dlopen("./libj.so", RTLD_LAZY);
    JST* jt= ((JInitType)dlsym(jdll, "JInit"))();
    JDoType jdo= dlsym(jdll,"JDo");
    jdo(jt, (C*)"('Hello, World!',10{a.) 1!:2<'/proc/self/fd/1'");
    exit(0);
}

(The "j engine" is a shared object / dylib / dll / ... the details depend on the host operating system.)

jq

This example is incorrect. Please fix the code and remove this message.

Details: The task says "So, instead of supplying the source for the program, we just want to produce an executable for that specific program." - using #! requires the source be supplied.
Please look at some of the other samples.

This entry confines itself to the task (creating an executable) in computing environments that support the "shebang" technique. A bash shell, for example, would suffice.

The trick to using jq in the shebang line is NOT to specify an argument for the -f option.

Assuming the jq program is on the PATH, a suitable shebang line for a script that reads from stdin would be:

#!/usr/bin/env jq -f

Alternatively, the full pathname can be specified, e.g. /usr/local/bin/jq -f

Other jq options can also be specified.

The file with the shebang line and program must of course be made executable (e.g. by running `chmod +x FILENAME`).

gojq, the Go implementation of jq, also supports the shebang technique, but in the case of gojq, the -f option should be specified last.

Variations and further details are given in the jq FAQ.

Julia

using StaticCompiler, StaticTools

hello() = println(c"Hello world!") # the c"" syntax defines a static C type string
compile_executable(hello, (), "./") # can now run this as executable "hello"
Output:
$ ./hello
Hello world!

Nim

Nim is normally a compiled language which can produce either native code using C, C++ or Objective C as intermediate language, or a JavaScript program. It is not a language designed to be interpreted, but there exists a powerful subset named Nimscript which is interpretable.

So we have chosen to write a Nim program which launch the “nim” program to evaluate a Nimscript program. There are two ways to ask “nim” to interpret: either using the “e” command rather than the “c” command or using the “--eval” option.

# Using the "e" command.

import std/os

const TempFile = "/tmp/hello.nim"
const Program = """echo "Hello World!""""

# Write program into temporary file.
let outFile = open(TempFile, fmWrite)
outFile.writeLine Program
outFile.close()

# Lauch "nim" to execute the program.
# "--hints:off" suppresses the hint messages.
discard execShellCmd("nim e --hints:off " & TempFile)

# Remove temporary file.
removeFile(TempFile)
# Using the '--eval" option.

import std/[os, strutils]

const Program = """echo "Hello World!""""

# As the program is provided in the command line, there is no need
# to create a temporary file.

# Lauch "nim" to execute the program.
# "--hints:off" suppresses the hint messages.
discard execShellCmd("""nim --hints:off --eval:'$#'""" % Program)
Output:
Hello World!

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.
There is a standard get_interpreter() routine which yeilds a full directory path and name, that should be used in preference to a hard-coded "p" or whateverer.
A format directive can also be used to force cross-compilation, see p32.exu or p64.exu and phixzip.exw for a trivial example of that, in real-world use.
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 (that's a joke, btw).

Despite the 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. Plus of course the "-c" means you would not also have to ship/install an interpreter along with the so-called "executable".

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.

#include <stdio.h>
#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;
}

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