Skip to content

Instantly share code, notes, and snippets.

@sedme0
Created February 26, 2021 02:20
Show Gist options
  • Save sedme0/2e3e050319c6a02e7a6142ae0a5a817e to your computer and use it in GitHub Desktop.
Save sedme0/2e3e050319c6a02e7a6142ae0a5a817e to your computer and use it in GitHub Desktop.
Multi-language Hello World
#!/bin/sh
# -----------------------------------------------------------------------------
# The purpose of this project is to create a Hello World program where each
# letter of "Hello world" is provided by a different programming language. This
# is achieved by writing a function in each language that returns the proper
# character. Each function is also to emit output of its own to stdout stating
# that it is responsible for its assigned letter. I have chosen to write the
# entire project in a shell script because I thought it would be interesting,
# which also happens to be the reason I chose to write the project at all.
# -----------------------------------------------------------------------------
# Last modified February 25, 2021
# -----------------------------------------------------------------------------
PROJECTNAME=multiHelloWorld
# -----------------------------------------------------------------------------
# C begins here
# -----------------------------------------------------------------------------
cat << EOF | gcc -xc -c -o LetterH.o -
#include <stdio.h>
char getLetterH() {
printf("The letter H is brought to you by C.\n");
return 'H';
}
EOF
# -----------------------------------------------------------------------------
# C ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Rust starts here
# -----------------------------------------------------------------------------
# Rust has to compile to a dynamic library, since as a static library, it creates linking errors. I should probably figure that out.
cat << EOF | rustc --crate-type cdylib -o libLetterE.so -
#[no_mangle]
pub extern "C" fn getLetterE() -> u8 {
println!("The letter E is brought to you by Rust.");
return b'e';
}
EOF
# -----------------------------------------------------------------------------
# Rust ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Fortran starts here
# -----------------------------------------------------------------------------
# -std-f2003 specifies Fortran 2003 because that's the version where the C bind thing was added
# -ffree-form specifies that it uses free form layout. Normally, this is inferred from file extension, but we're loading from stdin, so there is no file extension
# sed is used to give the function its capital letters back because Fortran is case-insensitive, and I want the function to match the style of the others
# I could use objcopy, and it would probably be more proper, but for such a small thing, it doesn't matter
cat << EOF | gfortran -xf95 -std=f2003 -fimplicit-none -S -ffree-form -o /dev/stdout - | sed 's/getletterl/getLetterL/g' | gcc -xassembler -c -o LetterL.o -
function getLetterL() bind(C)
character :: getLetterL
write (*, '(a)') 'The letter L is brought to you by Fortran.'
getLetterL = 'l'
end function getLetterL
EOF
# -----------------------------------------------------------------------------
# Fortran ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# x86 Assembly starts here
# -----------------------------------------------------------------------------
cat << EOF > LetterO.asm # nasm doesn't take stdin for some reason
global getLetterO
section .data
greeting db "The letter O is brought to you by x86 Assembly.", 0xa, 0
len equ \$ - greeting
section .text
displayGreeting: ; This is a separate function so it looks nicer if you decompile it
mov edx, len ; greeting length
mov ecx, greeting ; greeting contents
mov ebx, 1 ; stdout
mov eax, 4 ; sys_write
int 0x80 ; call kernel
ret
getLetterO:
call displayGreeting
mov eax, 0x6F ; Returns the letter o
ret
EOF
nasm -f elf64 -o LetterO.o LetterO.asm
rm -f LetterO.asm
# -----------------------------------------------------------------------------
# x86 Assembly ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# C# starts here
# -----------------------------------------------------------------------------
# Microsoft's C# compiler is used instead of the Mono's because Mono's doesn't take stdin
# -nologo suppresses the copyright message on Microsoft's C# compiler. Probably doesn't do anything with the mono compiler.
cat << EOF | csc -nologo -target:library -out:LetterW.dll -
using System;
public class LetterW {
public static char getLetterW() {
Console.WriteLine("The letter W is brought to you by C#.");
return('w');
}
}
EOF
# The following links contain useful information about this: https://www.mono-project.com/docs/advanced/embedding/
# http://docs.go-mono.com/?link=xhtml:deploy/mono-api-embedding.html https://github.com/mono/mono/tree/master/samples/embed
# The Mono libraries are written in/for C, so that's the language I'm using for it. It's probably possible to use them with any language with C bindings.
# -w is used to suppress warnings. One of the warnings is caused by the mono libraries, another one is because this is sloppily written.
cat << EOF | gcc -xc -c $(pkg-config --cflags mono-2) -w -o LetterW.o -
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
char getLetterW() {
// Set up the environment
// Note: This could be set up as a macro for more complex projects
MonoDomain *domain = mono_jit_init("LetterWDomain");
MonoAssembly *assembly = mono_domain_assembly_open(domain, "LetterW.dll");
MonoImage* image = mono_assembly_get_image(assembly);
mono_config_parse("/etc/mono/config"); // If this isn't done, then C# system libraries won't work
// Set up the method
MonoMethodDesc* getLetterWDesc = mono_method_desc_new("LetterW:getLetterW()", NULL);
MonoMethod* getLetterWMethod = mono_method_desc_search_in_image(getLetterWDesc, image);
MonoObject *result = mono_runtime_invoke(getLetterWMethod, NULL, NULL, NULL);
return *(int *)mono_object_unbox(result);
}
EOF
# -----------------------------------------------------------------------------
# C# ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Python starts here
# -----------------------------------------------------------------------------
# For more info see https://docs.python.org/3/c-api/
cat << EOF | gcc -xc -c $(pkg-config --cflags python3-embed) -w -o LetterR.o -
#include <Python.h>
char getLetterR() {
Py_Initialize();
PyObject *myModuleString = PyUnicode_FromString("__main__");
PyObject *myModule = PyImport_Import(myModuleString);
PyRun_SimpleString("def getLetterR():\n print(\"The letter R is brought to you by Python.\")\n return('r')");
PyObject *func = PyObject_GetAttrString(myModule, "getLetterR");
PyObject *result = PyObject_CallObject(func, NULL);
char returnChar = *PyUnicode_AsUTF8(result);
Py_Finalize();
return returnChar;
}
EOF
# -----------------------------------------------------------------------------
# Python ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Go starts here
# -----------------------------------------------------------------------------
cat << EOF > LetterD.go # go build doesn't accept stdin for some reason
package main
import (
"C"
"fmt"
)
//export getLetterD
func getLetterD() byte {
fmt.Println("The letter D is brought to you by Go.")
return byte('d')
}
func main() {} // There needs to be a main because golang says so arbitrarily
EOF
go build -buildmode=c-archive LetterD.go
mv LetterD.a libLetterD.a
rm -f LetterD.go
# -----------------------------------------------------------------------------
# Go ends here
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# C++ starts here
# -----------------------------------------------------------------------------
# -xc++ specifies that it should compile c++, since there's no filename to infer from
cat << EOF | g++ -xc++ -c -Wno-narrowing -o ${PROJECTNAME}_main.o -
#include <iostream>
using std::cout;
using std::endl;
extern "C" char getLetterH(); // C
extern "C" char getLetterE(); // Rust
extern "C" char getLetterL(); // Fortran
extern "C" char getLetterO(); // Assembly
extern "C" char getLetterW(); // C#
extern "C" char getLetterR(); // Python
#include "LetterD.h" // Go
int main(void) {
char helloWorld[] = {getLetterH(), getLetterE(), getLetterL(), getLetterL(), getLetterO(), ' ', getLetterW(), getLetterO(), getLetterR(), getLetterL(), getLetterD(), '\0'};
// cout << getLetterH() << getLetterE() << getLetterL() << getLetterL() << getLetterO() << ' ' << getLetterW() << getLetterO() << getLetterR() << getLetterL() << getLetterD() << endl; // This causes a segmentation fault
cout << helloWorld << endl;
}
EOF
rm -f LetterD.h
# -----------------------------------------------------------------------------
# C++ ends here
# -----------------------------------------------------------------------------
# -Wl,-rpath,. directs the dynamic linker to look in the same directory as the executable at runtime
# -no-pie is something that I read on the internet that I needed to fix the assembly stuff not linking, and it worked.
# -pthread is needed because Go uses threads
# -lgfortran ensures that the standard fortran libraries are linked
# -L. searches the current directory for libraries
g++ -o $PROJECTNAME -Wl,-rpath,. -no-pie -pthread ${PROJECTNAME}_main.o LetterH.o LetterL.o LetterO.o LetterW.o LetterR.o $(pkg-config --libs mono-2 python3-embed) -lgfortran -lLetterE -lLetterD -L.
rm -f LetterH.o LetterL.o LetterO.o LetterW.o LetterR.o libLetterD.a ${PROJECTNAME}_main.o # Clear temp files only needed for linking
./$PROJECTNAME
rm -f $PROJECTNAME libLetterE.so LetterW.dll # Clear temp files needed during execution
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment