Learn and Compare

Learn and Compare is a project to gain basic competency in a number of programming languages, demonstrate basic programming patterns in each, and compare them for utility, productivity, aesthetics, etc.

Program Console Web GUI TOPS-20
C#Common LispPascalFortranCPerlREXXPythonRuby JavaScriptRuby on Rails TCL/TK Fortran
1. Verbum: Print text message.
using System;
namespace VerbumS
{
  public class VerbumS
  {
    public static void Main (string[] args)
    {
      Console.WriteLine ("In principio erat Verbum");
    }
  }
}
	  
(format t "In principio erat Verbum~%")
	  
program verbum;
begin
  writeln ('In principio erat Verbum');
end.
	  
      program verbum
      write (*,*) 'In principio erat Verbum'
      stop
      end
	  
#include <stdio.h>
int main ()
{
  printf ("In principio erat Verbum\n");
  return 0;
}
	  
print "In principio erat Verbum\n";
	  
/* REXX */
say "In principio erat Verbum"
	  
print ("In principio erat Verbum")
	  
puts "In principio erat Verbum"
	  
<!DOCTYPE html>
<html> <body> <script>
document.write('In principio erat Verbum');
</script> </body> </html> 
	  
	  
pack [button .b -text "In principio erat Verbum" -command exit]	      
	  
      program verbum
      write (5,*) 'In principio erat Verbum'
      stop
      end
	  
2. Arginp: Print string from argument or input.
using System;
namespace ArginpS
{
    public class ArginpS
    {
	public static void Main (string[] args)
	{
	    string buffer;
	    if (args.Length < 1)
	    {
		Console.Write("> ");
		buffer = Console.ReadLine();
	    }
	    else
	    {
		buffer = String.Join(" ", args);
	    }
	    Console.WriteLine(buffer);
	}
    }
}
	  
(format t "~A~%"
	(or
	 #+CLISP (car *args*)
	 #+SBCL (cadr *posix-argv*)
	 (progn
	   (princ "> ")
	   (finish-output)
	   (read-line))))
	  
program arginp;
var s : string;
begin
   if paramcount () > 0 then  writeln (paramstr (1))
   else
   begin
      write ('> ');
      readln (s);
      writeln (s);
   end;
end.
	  
      program arginp
      character*80 arg
      integer len, stat
      call get_command_argument (1, arg, len, stat)
      if (arg.eq.' ') then
         write (*,10)
10       format (' > ',$)
         read (*,*) arg
      end if
      write (*,*) arg
      stop
      end
      
	  
#include <stdio.h>
#include <string.h>
#define MAXLEN 80
int main (int argc, char *argv[])
{
  char buffer[MAXLEN+1];
  buffer[MAXLEN] = '\0';
  if (argc > 1) strncpy (buffer, argv[1], MAXLEN);
  else
  {
    printf ("> ");
    fgets (buffer, MAXLEN, stdin);
    if (strlen (buffer) < MAXLEN) buffer[strlen (buffer)-1] = '\0';
  }
  printf ("%s\n", buffer);
  return 0;
}
	  
if ($#ARGV >= 0) { print join (' ', @ARGV), "\n"; }
else
{
    print "> ";
    $input = <>;
    print $input;
}
	  
/* REXX */
if arg() > 0 then say arg(1)
else do
  ok = charout(, '> ')
  parse pull inp
  say inp
end
	  
import sys
if len (sys.argv) > 1:
  print (sys.argv[1])
else:
  inp = input ("> ")
  print (inp)
	  
if ARGV.length > 0
  puts ARGV[0]
else
  print "> "
  inp = gets
  puts inp
end
	  
<!DOCTYPE html>
<html><body><script>
const url = new URL (window.location);
if (url.searchParams.get ('input') != null) {
   document.write (url.searchParams.get ('input'));
} else {
   document.write (
      '<form><label for="input">&gt; </label>'
         + '<input type="text" id="input" name="input"></form>'
   );
}
</script></body></html> 
	  
	  
	  
      program arginp
*     FORTRAN-20 does not support passing command line arguments to user
*     programs.
      character*80 inp
      write (5,10)
10    format (x,'> ',$)
      read (5,20) inp
20    format (a80)
      write (5,*) inp
      stop
      end
	  
3. Numrom: Convert number to Roman numeral.
using System;
namespace NumromS
{
    public class NumromS
    {
	public static void Main(string[] args)
	{
	    int[] SymVal = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
	    string[] Symbol = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
	    int num;
	    string romnum = "";
	    num = (args.Length > 0) ? Convert.ToInt32(args[0]) : 0;
	    if (num < 1 || num > 4999)
		throw new ArgumentOutOfRangeException($"Invalid input: {num}; Argument must be in range 1-4999");
	    for (int i = 0; i < SymVal.Length; ++ i)
	    {
		while (num >= SymVal[i])
		{
		    romnum += Symbol[i];
		    num -= SymVal[i];
		}
	    }
	    Console.WriteLine(romnum);
	}
    }
}
	  
(setq rn-symbol
      (make-array 13 :element-type 'string
		  :initial-contents '("M" "CM" "D" "CD" "C" "XC" "L" "XL" "X" "IX" "V" "IV" "I")))
(setq rn-value
      (make-array 13 :element-type 'integer
		  :initial-contents '(1000 900 500 400 100 90 50 40 10 9 5 4 1)))

(let* ((number-string (or
		       #+CLISP (car *args*)
		       #+SBCL (cadr *posix-argv*)
		       (progn
			 (princ "> ")
			 (finish-output)
			 (read-line))))
       (number (ignore-errors (parse-integer number-string)))
       (romnum ""))
  (if (not number)
      (error "Invalid input: ~s" number-string))
  (if (or (< number 1) (> number 4999))
      (error "Number out of range (1-4999): ~s" number))
  (dotimes (s 13)
    (let ((sym (aref rn-symbol s))
          (val (aref rn-value s)))
      (loop while (>= number val) do
	    (setq romnum (concatenate 'string romnum sym))
	    (setq number (- number val)))))
  (format t "~a~%" romnum))
	  
program numrom;
{$mode objfpc}
uses sysutils;
const
  SYMBOL_CNT = 13;
  SYMBOL_VALUE: array [1 .. SYMBOL_CNT] of integer =
    (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1);
  SYMBOL: array [1 .. SYMBOL_CNT] of string =
    ('M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I');
var
  i: integer;
  number: longint;
  number_str, romnum: string;
begin
  if paramCount () > 0 then
  begin
    number_str := paramStr (1);
    try
      number := strtoint (number_str);
    except
      on e: econverterror do
      begin
        writeln ('Invalid input: ', number_str);
        exit;
      end;
    end;
  end
  else  number := 0;
  if (number > 0) and (number <= 4999) then
  begin
    romnum := '';
    for i := 1 to SYMBOL_CNT do
    begin
      while number >= SYMBOL_VALUE[i] do
      begin
        romnum := romnum + SYMBOL[i];
        number := number - SYMBOL_VALUE[i];
      end;
    end;
    writeln (romnum);
  end
  else writeln ('Input out-of-range (1-4999): ', number);
end.
	  
      program numrom
      implicit none
      integer len, stat, num, i, p, symval(13), symlen(13)
      character*3 symbol(13)
      character*16 romnum
      character*80 arg
      data (symval(i),i=1,13) /1000,900,500,400,100,90,50,40,10,9,5,4,1/
      data (symbol(i),i=1,13) /'M','CM','D','CD','C','XC','L','XL','X',
     +                         'IX','V','IV','I'/
      data (symlen(i),i=1,13) /1,2,1,2,1,2,1,2,1,2,1,2,1/
      call get_command_argument (1, arg, len, stat)
      if (arg.eq.' ') then
         write (*,10)
 10      format (x,'> ',$)
         read (*,20) num
 20      format (i4)
      else
         read (arg,*) num
      end if
      p = 1
      if (num.lt.1 .or. num.gt.4999) then
         write (*,*) 'Invalid input: ',num
      else
         do i=1,13
            do while (num.ge.symval(i))
               romnum(p:p+symlen(i)) = symbol(i)
               p = p + symlen(i)
               num = num - symval(i)
            end do
         end do
      end if
      write (*,*) romnum
      stop
      end
	  
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SYMCNT 13
#define MAXLEN 16
int main (int argc, char *argv[])
{
  int symval[SYMCNT] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
  char symbol[SYMCNT][3] = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
  int num, i;
  char romnum[MAXLEN+1] = "";
  num = (argc > 1) ? atoi (argv[1]) : 0;
  if (argc <= 1 || strlen (argv[1]) > 4 || num < 1 || num > 4999)
  {
    printf ("Invalid input: %s\n", argv[1]);
    exit (EXIT_FAILURE);
  }
  for (i = 0; i < SYMCNT; ++ i)
  {
    while (num >= symval[i])
    {
      strcat (romnum, symbol[i]);
      num -= symval[i];
    }
  }
  printf ("%s\n", romnum);
  return 0;
}
	  
$num = $#ARGV == 0 ? $ARGV[0] : 0;
die "Invalid input: $ARGV[0]\n" if ($num =~ /\D/ || $num < 1 || $num > 4999);
@valsym = (1000, "M", 900, "CM", 500, "D", 400, "CD", 100, "C", 90, "XC",
  50, "L", 40, "XL", 10, "X", 9, "IX", 5, "V", 4, "IV", 1, "I");
$romnum = "";
while ($#valsym > -1)
{
    $val = shift @valsym;
    $sym = shift @valsym;
    while ($num >= $val)
    {
	$romnum .= $sym;
	$num -= $val;
    }
}
print "$romnum\n";
	  
/* REXX */
num = arg(1)
if verify(num, '0123456789') > 0 | num < 1 | num > 4999 then do
  say 'Invalid input:' num
  exit
end
valsym = 1000 M 900 CM 500 D 400 CD 100 C 90 XC 50 L 40 XL 10 X 9 IX 5 V 4 IV 1 I
romnum = ''
do while words(valsym) > 0
  parse var valsym val sym valsym
  do while num >= val
    romnum = romnum || sym
    num = num - val
  end
end
say romnum
	  
import sys
import re
if len (sys.argv) > 1:
    arg = sys.argv[1]
else:
    arg = input ("> ")
isNum = re.search ('^[0-9]+$', arg)
if isNum:
    num = int (arg)
if not isNum or num < 1 or num > 4999:
    sys.exit ("Invalid input: " + arg)
symval = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
symbol = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
romnum = ""
for i in range (len (symval)):
    while num >= symval[i]:
        romnum += symbol[i]
        num -= symval[i]
print (romnum)
	  
num = ARGV.length > 0 ? ARGV[0].to_i : 0
abort ("Invalid input: #{ARGV[0]}") if num < 1 || num > 4999
symval = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
symbol = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
romnum = ""
symval.each_with_index do |val, i|
  while num >= val
    romnum.concat(symbol[i])
    num -= val
  end
end
puts romnum
	  
<!DOCTYPE html>
<html><body><script>
const symVal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
const symbol = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'];
const url = new URL (window.location);
let number = url.searchParams.get ('input');
let romNum = "";
if (number == null) {
    document.write (
	'<form><label for="input">&gt; </label>'
	    + '<input type="text" id="input" name="input"></form>'
    );
} else if (isNaN (number) || parseInt (number) != number
	   || number < 1 || number > 4999) {
    document.write ('Invalid input: ' + number);
} else {
    for (let i = 0; i < symVal.length; ++ i) {
	while (number >= symVal[i]) {
	    romNum += symbol[i];
	    number -= symVal[i];
	}
    }
    document.write (romNum);
}
</script></body></html> 
	  
	  
	  
      program numrom
      implicit none
      integer num, i, p, symval(13), symlen(13)
      character*3 symbol(13)
      character*16 romnum
      data (symval(i),i=1,13) /1000,900,500,400,100,90,50,40,10,9,5,4,1/
      data (symbol(i),i=1,13) /'M','CM','D','CD','C','XC','L','XL','X',
     +                         'IX','V','IV','I'/
      data (symlen(i),i=1,13) /1,2,1,2,1,2,1,2,1,2,1,2,1/
      p = 1
      write (5,10)
10    format (x,'> ',$)
      read (5,20) num
20    format (i4)
      if (num.lt.1 .or. num.gt.4999) then
        write (5,*) 'Invalid input: ',num
      else
        do i=1,13
          do while (num.ge.symval(i))
            romnum(p:p+symlen(i)) = symbol(i)
            p = p + symlen(i)
            num = num - symval(i)
          end do
        end do
      end if
      write (5,*) romnum
      stop
      end
	  
4. Romnum: Convert Roman numeral to number.
	  
	  
program romnum;
uses sysutils;
const
   SYMBOL_CNT					      = 7;
   SYMBOL					      = 'MDCLXVI';
   SYMBOL_VALUE: array [1..SYMBOL_CNT] of integer     = 
      (1000, 500, 100, 50, 10, 5, 1);
var
   number, value, prev_value, p, s	 : integer;
   roman_numeral : string;
begin
   if ParamCount > 0 then  roman_numeral := UpperCase (ParamStr (1))
   else
   begin
      writeln ('Usage: romnum ROMAN_NUMERAL');
      exit;
   end;
   number := 0;
   prev_value := SYMBOL_VALUE[1];
   for p := 1 to Length (roman_numeral) do
   begin
      for s := 1 to SYMBOL_CNT do
	 if roman_numeral[p] = SYMBOL[s] then
	 begin
	    value := SYMBOL_VALUE[s];
	    number := number + value;
	    if value > prev_value then number := number - 2 * prev_value;
	    prev_value := value;
	    break;
	 end;
   end;
   writeln (number);
end.
	  
	  
	  
$romnum = $#ARGV == 0 ? uc $ARGV[0] : "";
die "Invalid input: $ARGV[0]\n" if (length ($romnum) == 0 || $romnum =~ /[^MDCLXVI]/);
%symval = (M => 1000, D => 500, C => 100, L => 50, X => 10, V => 5, I => 1);
$num = 0;
$prevsym = 'M';
while (length ($romnum) > 0)
{
    $symbol = substr $romnum, 0, 1;
    $num += $symval{$symbol};
    if ($symval{$symbol} > $symval{$prevsym})
    {
	$num -= (2 * $symval{$prevsym});
    }
    $prevsym = $symbol;
    $romnum = substr $romnum, 1;
}
print "$num\n";
	  
/* REXX */
parse upper arg romnum
if romnum = '' | verify(romnum, 'MDCLXVI') > 0 then do
  say 'Invalid input:' arg(1)
  exit
end
symval.M = 1000
symval.D = 500
symval.C = 100
symval.L = 50
symval.X = 10
symval.V = 5
symval.I = 1
num = 0
prevsym = M
do length(romnum)
  symbol = left(romnum, 1)
  num = num + symval.symbol
  if symval.symbol > symval.prevsym then
    num = num - 2 * symval.prevsym
  prevsym = symbol
  romnum = substr(romnum, 2)
end
say num
	  
import sys
import re
if len (sys.argv) > 1:
    arg = sys.argv[1]
else:
    arg = input ('> ')
romnum = arg.upper ()
if not re.search ('^[MDCLXVI]+$', romnum):
    sys.exit ('Invalid input: ' + arg)
symval = {'M': 1000, 'D': 500, 'C': 100, 'L': 50, 'X': 10, 'V': 5, 'I': 1}
num = 0
prevsym = 'M'
for p in range (len (romnum)):
    sym = romnum[p]
    num += symval[sym]
    if symval[sym] > symval[prevsym]:
        num -= (2 * symval[prevsym])
    prevsym = sym
print (num)
	  
romnum = ARGV.length > 0 ? ARGV[0].upcase : "0"
abort ("Invalid input: #{ARGV[0]}") if romnum =~ /[^MDCLXVI]/
symval = {"M" => 1000, "D" => 500, "C" => 100, "L" => 50, "X" => 10, "V" => 5, "I" => 1}
num = 0
prevsym = "M"
for p in 0 .. (romnum.length - 1)
  sym = romnum[p]
  num += symval[sym]
  if symval[sym] > symval[prevsym] then num -= (2 * symval[prevsym]) end
  prevsym = sym
end
puts num
	  
<!DOCTYPE html>
<html><body><script>
const symVal = {M:1000, D:500, C:100, L:50, X:10, V:5, I:1};
const url = new URL (window.location);
let romNum = url.searchParams.get ('input');
if (romNum == null) {
    document.write (
	'<form><label for="input">&gt; </label>'
	    + '<input type="text" id="input" name="input"></form>'
    );
} else if (/[^MDCLXVI]/i.test (romNum)) {
    document.write ('Invalid input: ' + romNum);
} else {
    let number = 0;
    let prevSym = 'M';
    for (let symbol of romNum.toUpperCase().split('')) {
	number += symVal[symbol];
	if (symVal[symbol] > symVal[prevSym]) {
	    number -= (2 * symVal[prevSym]);
	}
	prevSym = symbol;
    }
    document.write (number);
}
</script></body></html> 
	  
	  
	  
	  
5. Hodie: Convert date to Roman format.
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
6. Calendarium: Print year calendar in Roman format.
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
7. Luna: Print current phase of Moon.
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  
	  

Performance

  1. Timings taken on SDF MetaArray IV Side B (Linux 4.19.0-17-amd64 #1 SMP Debian 4.19.194-3 (2021-07-18) x86_64 GNU/Linux). Test programs were based on program indicated in column headers with processing enclosed in a 1,000,000-times repeating loop. All programs passed the same argument(s) and all output redirected to /dev/null.

    LanguageVerbum x 1MNumrom x 1M
    immut. list4mut. list5lookup6
    C0.030s0.247s-0.048s
    Pascal0.100s0.463s-0.208s
    Common Lisp70.556s2.480s2.455s0.834
    Perl0.114s9.571s8.831s0.566s
    Rexx (rexx1)0.448s24.506s15.453s3m59.605s
    Rexx (Regina)0.463s23.657s17.366s3m12.283s
    Ruby0.519s5.280s5.331s0.596s
    Python0.574s11.803s13.030s2.937s
    TCL21.601s1.504s
    Bourne shell3.239s
    Bash35.397s
  2. Timings taken on lucy (Linux 5.10.0-21-amd64 #1 SMP Debian 5.10.162-1 (2023-01-21) x86_64 GNU/Linux Intel Atom N475 CPU). Test programs were based on program indicated in column headers with processing enclosed in a 1,000,000-times repeating loop. All programs passed the same argument(s) and all output redirected to /dev/null.

    C programs compiled with gcc 10.2.1 (10.2.1-6 20210110 Debian) on lucy.

    C# programs compiled with mcs Mono C# compiler and executed with Mono 6.8.0.105 (6.8.0.105+dfsg-3.3~deb11u1 Debian) on lucy .

    LanguageVerbum x 1MNumrom x 1M
    immut. list4lookup6
    C0.175s1.306s0.383s
    C#8.787s29.092s13.320s
  3. Timings taken on boxy (Windows 7 Professional SP1 Intel Core i7-3770 CPU). Test programs were based on program indicated in column headers with processing enclosed in a 1,000,000-times repeating loop. All programs passed the same argument(s) and all output redirected to $null.

    LanguageCompilerVerbum x 1MNumrom x 1M
    immut. list4lookup6
    CVisual Studio 20193.371s3.975s3.834s
    gcc 11.2.0 (MSYS2)5.674s5.284s5.163s
    C#Visual Studio 20193.912s4.009s4.112s
    mcs 6.8.0.105 (lucy)6.321s5.695s5.901s
  4. Timings taken on slabby (Windows 10 Home 22H2 Intel Atom x5-Z8550 CPU). Test programs were based on program indicated in column headers with processing enclosed in a 1,000,000-times repeating loop. All programs passed the same argument(s) and all output redirected to $null.

    LanguageCompilerVerbum x 1MNumrom x 1M
    immut. list4lookup6
    CVisual Studio 2019 (boxy)2.789s2.331s2.295s
    C#Visual Studio 2019 (boxy)23.852s27.217s25.172s

Notes

1 Static version of Regina.

2 Controls access to /dev/null internally instead of shell redirection.

3 Uses Bash-specific range operator. Running standard Bourne shell script with Bash takes 11.608s.

4 Immutable list: List of Roman numeral symbols and values processed with index variable.

5 Mutable list: List of Roman numeral symbols processed by discarding members as used.

6 Lookup: Roman numeral looked-up in array of all 5000 possible numerals.

7 Run with Steel Bank Common Lisp on MetaArray (4.19.0-22-amd64 #1 SMP Debian 4.19.260-1 (2022-09-29) x86_64 GNU/Linux).