import Nanoquery.IO
import dict

def get_config(fname)
        f = new(File).open(fname)
        lines = split(f.readAll(), "\n")

        values = new(Dict)
        for line in lines
                line = trim(line)
                if len(line) > 0
                        if not (line .startswith. "#") or (line .startswith. ";")
                                tokens = split(line, " ")

                                if len(tokens) = 1
                                        values.add(upper(tokens[0]), true)
                                else
                                        parameters = list()
                                        parameter = ""
                                        for i in range(1, len(tokens) - 1)
                                                parameter += tokens[i] + " "
                                                if parameter .endswith. ", "
                                                        parameter = parameter.substring(0, len(parameter) - 2)
                                                        parameters.append(trim(parameter))
                                                        parameter = ""
                                                end
                                        end
                                        parameters.append(trim(parameter))
                                        if len(parameters) > 1
                                                values.add(upper(tokens[0]), parameters)
                                        else
                                                values.add(upper(tokens[0]), parameters[0])
                                        end
                                end
                        end
                end
        end

        return values
end

println get_config(args[2])


import re, strformat, strutils, tables

var configs: OrderedTable[string, seq[string]]
var parsed: seq[string]

for line in "demo.config".lines():
  let line = line.strip()
  if line != "" and not line.startswith(re"#|;"):
    parsed = line.split(re"\s*=\s*|\s+", 1)
    configs[parsed[0].toLower()] = if len(parsed) > 1: parsed[1].split(re"\s*,\s*") else: @[]

for key in ["fullname", "favouritefruit", "needspeeling", "seedsremoved", "otherfamily"]:
  if not configs.hasKey(key):
    echo(&"{key} = false")
  else:
    case len(configs[key])
    of 0:
      echo(&"{key} = true")
    of 1:
      echo(&"{key} = {configs[key][0]}")
    else:
      for i, v in configs[key].pairs():
        echo(&"{key}({i+1}) = {v}")


#use "topfind"
#require "inifiles"
open Inifiles

let print_field ini (label, field) =
  try
    let v = ini#getval "params" field in
    Printf.printf "%s: %s\n" label v
  with Invalid_element _ ->
    Printf.printf "%s: not defined\n" label

let () =
  let ini = new inifile "./conf.ini" in
  let lst = [
    "Full name", "FULLNAME";
    "likes", "FAVOURITEFRUIT";
    "needs peeling", "NEEDSPEELING";
    "seeds removed", "SEEDSREMOVED";
  ] in
  List.iter (print_field ini) lst;

  let v = ini#getaval "params" "OTHERFAMILY" in
  print_endline "other family:";
  List.iter (Printf.printf "- %s\n") v;
;;


#!/usr/bin/rexx
/*.----------------------------------------------------------------------.*/
/*|readconfig: Read keyword value pairs from a configuration file into   |*/
/*|            Rexx variables.                                           |*/
/*|                                                                      |*/
/*|Usage:                                                                |*/
/*|              .-~/rosetta.conf-.                                      |*/
/*|>>-readconfig-+----------------+------------------------------------><|*/
/*|              |-configfilename-|                                      |*/
/*|              |-- -? ----------|                                      |*/
/*|              |-- -h ----------|                                      |*/
/*|              '- --help -------'                                      |*/
/*|                                                                      |*/
/*|where                                                                 |*/
/*|  configfilename                                                      |*/
/*|        is the name of the configuration file to be processed.  if not|*/
/*|        specified, ~/rosetta.conf is used.                            |*/
/*|                                                                      |*/
/*|All values retrieved from the configuration file are stored in        |*/
/*|compound variables with the stem config.  Variables with multiple     |*/
/*|values have a numeric index appended, and the highest index number    |*/
/*|is stored in the variable with index 0; e.g. if CONFIG.OTHERFAMILY.1  |*/
/*|and CONFIG.OTHERFAMILY.2 have values assigned, CONFIG.OTHERFAMILY.0 = |*/
/*|2.                                                                    |*/
/*|-?, -h or --help all cause this documentation to be displayed.        |*/
/*|                                                                      |*/
/*|This program was tested using Open Object Rexx 4.1.1.  It should work |*/
/*|with most other dialects as well.                                     |*/
/*'----------------------------------------------------------------------'*/
  call usage arg(1)
  trace normal
  signal on any name error

/* Prepare for processing the configuration file. */
  keywords = 'FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY'

/* Set default values for configuration variables here */
  config_single?. = 1
  config. = ''
  config.NEEDSPEELING = 0
  config.SEEDSREMOVED = 1

/* Validate command line inputs. */
  parse arg configfilename

  if length(configfilename) = 0 then
    configfilename = '~/rosetta.conf'

  configfile = stream(configfilename,'COMMAND','QUERY EXISTS')

  if length(configfile) = 0 then
    do
      say configfilename 'was not found.'
      exit 28
    end

  signal on notready                               /* if an I/O error occurs. */

/* Open the configuration file. */
  response = stream(configfile,'COMMAND','OPEN READ SHARED')

/* Parse the contents of the configuration file into variables. */
  do while lines(configfile) > 0
    statement = linein(configfile)

    select
      when left(statement,1) = '#',
         | left(statement,1) = ';',
         | length(strip(statement)) = 0,
      then                                      /* a comment or a blank line. */
        nop                                                       /* skip it. */

      otherwise
        do
          if pos('=',word(statement,1)) > 0,
           | left(word(statement,2),1) = '=',
          then                        /* a keyword/value pair with = between. */
            parse var statement keyword '=' value

          else                             /* a keyword/value pair with no =. */
            parse var statement keyword value

          keyword = translate(strip(keyword))           /* make it uppercase. */
          single? = pos(',',value) = 0 /* a single value, or multiple values? */
          call value 'CONFIG_single?.'keyword,single?          /* remember. */

          if single? then
            do
              if length(value) > 0 then
                call value 'CONFIG.'keyword,strip(value)
            end                      /* strip keeps internal whitespace only. */

          else                            /* store each value with its index. */
            do v = 1 by 1 while length(value) > 0
              parse var value value1 ',' value

              if length(value1) > 0 then
                do
                  call value 'CONFIG.'keyword'.'v,strip(value1)
                  call value 'CONFIG.'keyword'.0',v         /* remember this. */
                end
            end
        end
    end
  end

/* Display the values of the configuration variables. */
  say 'Values associated with configuration file' configfilename':'
  say

  do while words(keywords) > 0
    parse var keywords keyword keywords

    if value('CONFIG_single?.'keyword) then
      say right(keyword,20) '= "'value('CONFIG.'keyword)'"'

    else
      do
        lastv = value('CONFIG.'keyword'.0')

        do v = 1 to lastv
          say right(keyword,20-(length(v)+2))'['v'] = "'value('CONFIG.'keyword'.'v)'"'
        end
      end
  end

  say

notready:                                           /* I/O errors come here. */
  filestatus = stream(configfile,'STATE')

  if filestatus \= 'READY' then
    say 'An I/O error occurred; the file status is' filestatus'.'

  response = stream(configfile,'COMMAND','CLOSE')

error:
/*? = sysdumpvariables() */                    /* see everything Rexx used. */
exit

usage: procedure
  trace normal

  if arg(1) = '-h',
   | arg(1) = '-?',
   | arg(1) = '--help'
  then
    do
      line = '/*|'
      say

      do l = 3 by 1 while line~left(3) = '/*|'
        line = sourceline(l)
        parse var line . '/*|' text '|*/' .
        say text
      end

      say
      exit 0
    end
return


#!/usr/bin/instantfpc

{$if not defined(fpc) or (fpc_fullversion < 20600)}
  {$error FPC 2.6.0 or greater required}
{$endif}

{$mode objfpc}{$H+}

uses
  Classes,SysUtils,gvector,ghashmap;

type
  TStrHashCaseInsensitive = class
    class function hash(s: String; n: Integer): Integer;
  end;

class function TStrHashCaseInsensitive.hash(s: String; n: Integer): Integer;
var
  x: Integer;
  c: Char;
begin
  x := 0;
  for c in UpCase(s) do Inc(x,Ord(c));
  Result := x mod n;
end;

type
  TConfigValues  = specialize TVector<String>;
  TConfigStorage = class(specialize THashMap<String,TConfigValues,TStrHashCaseInsensitive>)
    destructor Destroy; override;
  end;

destructor TConfigStorage.Destroy;
var
  It: TIterator;
begin
  if Size > 0 then begin
    It := Iterator;
    repeat
      It.Value.Free;
    until not It.Next;
    It.Free;
  end;
  inherited Destroy;
end;

var
  ConfigStrings,ConfigValues: TStrings;
  ConfigStorage: TConfigStorage;
  ConfigLine,ConfigName,ConfigValue: String;
  SeparatorPos: Integer;
begin
  ConfigStrings := TStringList.Create;
  ConfigValues  := TStringList.Create;
  ConfigValues.Delimiter := ',';
  ConfigValues.StrictDelimiter := true;
  ConfigStorage := TConfigStorage.Create;

  ConfigStrings.LoadFromFile('config.test');
  for ConfigLine in ConfigStrings do begin
    if Length(ConfigLine) > 0 then begin
      case ConfigLine[1] of
        '#',';': ; // ignore
        else begin
          // look for = first
          SeparatorPos := Pos('=',ConfigLine);
          // if not found, then look for space
          if SeparatorPos = 0 then begin
            SeparatorPos := Pos(' ',ConfigLine);
          end;
          // found space
          if SeparatorPos <> 0 then begin
            ConfigName := UpCase(Copy(ConfigLine,1,SeparatorPos - 1));
            ConfigValues.DelimitedText := Copy(ConfigLine,SeparatorPos + 1,Length(ConfigLine) - SeparatorPos);
          // no = or space found, take the whole line as a key name
          end else begin
            ConfigName := UpCase(Trim(ConfigLine));
          end;
          if not ConfigStorage.Contains(ConfigName) then begin
            ConfigStorage[ConfigName] := TConfigValues.Create;
          end;
          for ConfigValue in ConfigValues do begin
            ConfigStorage[ConfigName].PushBack(Trim(ConfigValue));
          end;
        end;
      end;
    end;
  end;

  WriteLn('FULLNAME = ' + ConfigStorage['FULLNAME'][0]);
  WriteLn('FAVOURITEFRUIT = ' + ConfigStorage['FAVOURITEFRUIT'][0]);
  WriteLn('NEEDSPEELING = ' + BoolToStr(ConfigStorage.Contains('NEEDSPEELING'),true));
  WriteLn('SEEDSREMOVED = ' + BoolToStr(ConfigStorage.Contains('SEEDSREMOVED'),true));
  WriteLn('OTHERFAMILY(1) = ' + ConfigStorage['OTHERFAMILY'][0]);
  WriteLn('OTHERFAMILY(2) = ' + ConfigStorage['OTHERFAMILY'][1]);

  ConfigStorage.Free;
  ConfigValues.Free;
  ConfigStrings.Free;
end.


   * blank lines and lines beginning with # and ; should be ignored.
   * an all uppercase word defines a configuration symbol
   * no tail means a boolean true
   * a tail including a comma means a list
   * any other kind of tail is a string


my $fullname;
my $favouritefruit;
my $needspeeling;
my $seedsremoved;
my @otherfamily;

# configuration file definition.  See read_conf_file below for explanation.
my $conf_definition = {
    'fullname'          => [ 'string', \$fullname ],
    'favouritefruit'    => [ 'string', \$favouritefruit ],
    'needspeeling'      => [ 'boolean', \$needspeeling ],
    'seedsremoved'      => [ 'boolean', \$seedsremoved ],
    'otherfamily'       => [ 'array', \@otherfamily ],
};

my $arg = shift;               # take the configuration file name from the command line
                               # (or first subroutine argument if this were in a sub)
my $file;                      # this is going to be a file handle reference
open $file, $arg or die "Can't open configuration file '$arg': $!";

read_conf_file($file, $conf_definition);

print "fullname = $fullname\n";
print "favouritefruit = $favouritefruit\n";
print "needspeeling = ", ($needspeeling ? 'true' : 'false'), "\n";
print "seedsremoved = ", ($seedsremoved ? 'true' : 'false'), "\n";
for (my $i = 0; $i < @otherfamily; ++$i) {
    print "otherfamily(", $i + 1, ") = ", $otherfamily[$i], "\n";
}

# read_conf_file:  Given a file handle opened for reading and a configuration definition,
# read the file.
# If the configuration file doesn't match the definition, raise an exception with "die".
# The configuration definition is (a reference to) an associative array
# where the keys are the configuration variable names in all lower case
# and the values are references to arrays.
# The first element of each of these arrays is the expected type:  'boolean', 'string', or 'array';
# the second element is a reference to the variable that should be assigned the data.
sub read_conf_file {
    my ($fh, $def) = @_;        # copy parameters

    local $_;                   # avoid interfering with use of $_ in main program
    while (<$fh>) {             # read a line from $fh into $_ until end of file
        next if /^#/;           # skip "#" comments
        next if /^;/;           # skip ";" comments
        next if /^$/;           # skip blank lines
        chomp;                  # strip final newline

        $_ =~ /^\s*(\w+)\s*(.*)$/i or die "Syntax error";
        my $key = $1;
        my $rest = $2;
        $key =~ tr/[A-Z]/[a-z]/; # convert keyword to lower case

        if (!exists $def->{$key}) {
            die "Unknown keyword: '$key'";
        }

        if ($def->{$key}[0] eq 'boolean') {
            if ($rest) {
                die "Syntax error:  extra data following boolean '$key'";
            }
            ${$def->{$key}[1]} = 1;
            next;                # done with this line, go back to "while"
        }

        $rest =~ s/\s*$//;       # drop trailing whitespace
        $rest =~ s/^=\s*//;      # drop equals sign if present

        if ($def->{$key}[0] eq 'string') {
            ${$def->{$key}[1]} = $rest;
        } elsif ($def->{$key}[0] eq 'array') {
            @{$def->{$key}[1]} = split /\s*,\s*/, $rest;
        } else {
            die "Internal error (unknown type in configuration definition)";
        }
    }
}


integer fn = open("RCTEST.INI","r")
sequence lines = get_text(fn,GT_LF_STRIPPED)
close(fn)
constant dini = new_dict()
for i=1 to length(lines) do
    string li = trim(lines[i])
    if length(li)
    and not find(li[1],"#;") then
        integer k = find(' ',li)
        if k!=0 then
            string rest = li[k+1..$]
            li = li[1..k-1] -- (may want upper())
            if find(',',rest) then
                sequence a = split(rest,',')
                for j=1 to length(a) do a[j]=trim(a[j]) end for
                putd(li,a,dini)
            else
                putd(li,rest,dini)
            end if
        else
            putd(li,1,dini) -- ""
        end if
    end if
end for

function visitor(object key, object data, object /*user_data*/)
    ?{key,data}
    return 1
end function
traverse_dict(routine_id("visitor"),0,dini)
?getd("FAVOURITEFRUIT",dini)


def optionValue
    2 get "," find
    if
        " " "-" subst "," " " subst split
        len for
            var i
            i get "-" " " subst
            rot 1 get "(" chain print i print ") = " print swap trim print nl swap
        endfor
        drop drop
    else
        swap 1 get print " = " print swap print nl
    endif
enddef

0 tolist

"rosetta_read.cfg" "r" fopen var file

file 0 > if
    true
    while
        file fgets
        dup -1 != if
            trim
            len 0 > if
                1 get '#' != if
                    " " find var pos
                    pos if
                        1 pos 1 - slice
                        swap len pos - pos 1 + swap slice
                        nip 2 tolist
                    else
                        "" 2 tolist
                    endif
                    0 put
                else
                    drop
                endif
            else
                drop
            endif
            true
        else
            drop
            file fclose
            false
        endif
    endwhile

    len for
        get
        1 get ";" == if 2 get print " = false" print nl
        else 2 get "" == if 1 get print " = true" print nl
            else optionValue
            endif
        endif
        drop
    endfor
endif


<?php

$conf = file_get_contents('parse-conf-file.txt');

// Add an "=" after entry name
$conf = preg_replace('/^([a-z]+)/mi', '$1 =', $conf);

// Replace multiple parameters separated by commas :
//   name = value1, value2
// by multiple lines :
//   name[] = value1
//   name[] = value2
$conf = preg_replace_callback(
    '/^([a-z]+)\s*=((?=.*\,.*).*)$/mi',
    function ($matches) {
        $r = '';
        foreach (explode(',', $matches[2]) AS $val) {
            $r .= $matches[1] . '[] = ' . trim($val) . PHP_EOL;
        }
        return $r;
    },
    $conf
);

// Replace empty values by "true"
$conf = preg_replace('/^([a-z]+)\s*=$/mi', '$1 = true', $conf);

// Parse configuration file
$ini = parse_ini_string($conf);

echo 'Full name       = ', $ini['FULLNAME'], PHP_EOL;
echo 'Favourite fruit = ', $ini['FAVOURITEFRUIT'], PHP_EOL;
echo 'Need spelling   = ', (empty($ini['NEEDSPEELING']) ? 'false' : 'true'), PHP_EOL;
echo 'Seeds removed   = ', (empty($ini['SEEDSREMOVED']) ? 'false' : 'true'), PHP_EOL;
echo 'Other family    = ', print_r($ini['OTHERFAMILY'], true), PHP_EOL;


go =>
  Vars = ["fullname","favouritefruit","needspeeling","seedsremoved","otherfamily"],
  Config = read_config("read_a_configuration_file_config.cfg"),
  foreach(Key in Vars)
    printf("%w = %w\n", Key, Config.get(Key,false))
  end,
  nl.

% Read configuration file
read_config(File) = Config =>
  Config = new_map(),
  Lines = [Line : Line in read_file_lines(File), Line != [], not membchk(Line[1],"#;")],
  foreach(Line in Lines)
    Line := strip(Line),
    once( append(Key,[' '|Value],Line) ; Key = Line, Value = true),
    if find(Value,",",_,_) then
      Value := [strip(Val) : Val in split(Value,",")]
    end,
    Key := strip(to_lowercase(Key)),
    Config.put(Key,Value)
  end.


(de rdConf (File)
   (in File
      (while (read)
         (set @ (or (pack (clip (line))) T)) ) ) )

(rdConf "conf.txt")
(println FULLNAME FAVOURITEFRUIT NEEDSPEELING SEEDSREMOVED OTHERFAMILY)
(bye)


set: procedure options (main);
   declare text character (100) varying;
   declare (fullname, favouritefruit) character (100) varying initial ('');
   declare needspeeling bit (1) static initial ('0'b);
   declare seedsremoved bit (1) static initial ('0'b);
   declare otherfamily(10) character (100) varying;
   declare (i, j) fixed binary;
   declare in file;

   open file (in) title ( '/RD-CON.DAT,TYPE(TEXT),RECSIZE(200)' );

   on endfile (in) go to variables;

   otherfamily = ''; j = 0;

   do forever;
      get file (in) edit (text) (L);
      text = trim(text);

      if length(text) = 0 then iterate;
      if substr(text, 1, 1) = ';' then iterate;
      if substr(text, 1, 1) = '#' then iterate;
      if length(text) >= 9 then
         if substr(text, 1, 9) = 'FULLNAME ' then
            fullname = trim( substr(text, 9) );
      if length(text) >= 15 then
         if substr(text, 1, 15) = 'FAVOURITEFRUIT ' then
            favouritefruit = trim( substr(text, 15) );
      if length(text) >= 12 then
         if text = 'NEEDSPEELING' then needspeeling = '1'b;
      if length(text) >= 12 then
         if text = 'SEEDSREMOVED' then seedsremoved = '1'b;
      if length(text) >= 12 then
       if substr(text, 1, 12) = 'OTHERFAMILY ' then
         do;
                  text = trim(substr(text, 12) );
                  i = index(text, ',');
                  do while (i > 0);
                     j = j + 1;
                     otherfamily(j) = substr(text, 1, i-1);
                     text = trim(substr(text, i+1));
                     i = index(text, ',');
                  end;
                  j = j + 1;
                  otherfamily(j) = trim(text);
         end;
   end;

variables:
      put skip data (fullname);
      put skip data (favouritefruit);
      put skip data (needspeeling);
      put skip data (seedsremoved);
      do i = 1 to j;
         put skip data (otherfamily(i));
      end;

end set;


function Read-ConfigurationFile
{
    [CmdletBinding()]
    Param
    (
        # Path to the configuration file.  Default is "C:\ConfigurationFile.cfg"
        [Parameter(Mandatory=$false, Position=0)]
        [string]
        $Path = "C:\ConfigurationFile.cfg"
    )

    [string]$script:fullName = ""
    [string]$script:favouriteFruit = ""
    [bool]$script:needsPeeling = $false
    [bool]$script:seedsRemoved = $false
    [string[]]$script:otherFamily = @()

    function Get-Value ([string]$Line)
    {
        if ($Line -match "=")
        {
            [string]$value = $Line.Split("=",2).Trim()[1]
        }
        elseif ($Line -match " ")
        {
            [string]$value = $Line.Split(" ",2).Trim()[1]
        }

        $value
    }

    # Process each line in file that is not a comment.
    Get-Content $Path | Select-String -Pattern "^[^#;]" | ForEach-Object {

        [string]$line = $_.Line.Trim()

        if ($line -eq [String]::Empty)
        {
            # do nothing for empty lines
        }
        elseif ($line.ToUpper().StartsWith("FULLNAME"))
        {
            $script:fullName = Get-Value $line
        }
        elseif ($line.ToUpper().StartsWith("FAVOURITEFRUIT"))
        {
            $script:favouriteFruit = Get-Value $line
        }
        elseif ($line.ToUpper().StartsWith("NEEDSPEELING"))
        {
            $script:needsPeeling = $true
        }
        elseif ($line.ToUpper().StartsWith("SEEDSREMOVED"))
        {
            $script:seedsRemoved = $true
        }
        elseif ($line.ToUpper().StartsWith("OTHERFAMILY"))
        {
            $script:otherFamily = (Get-Value $line).Split(',').Trim()
        }
    }

    Write-Verbose -Message ("{0,-15}= {1}" -f "FULLNAME", $script:fullName)
    Write-Verbose -Message ("{0,-15}= {1}" -f "FAVOURITEFRUIT", $script:favouriteFruit)
    Write-Verbose -Message ("{0,-15}= {1}" -f "NEEDSPEELING", $script:needsPeeling)
    Write-Verbose -Message ("{0,-15}= {1}" -f "SEEDSREMOVED", $script:seedsRemoved)
    Write-Verbose -Message ("{0,-15}= {1}" -f "OTHERFAMILY", ($script:otherFamily -join ", "))
}


def readconf(fn):
    ret = {}
    with file(fn) as fp:
        for line in fp:
            # Assume whitespace is ignorable
            line = line.strip()
            if not line or line.startswith('#'): continue

            boolval = True
            # Assume leading ";" means a false boolean
            if line.startswith(';'):
                # Remove one or more leading semicolons
                line = line.lstrip(';')
                # If more than just one word, not a valid boolean
                if len(line.split()) != 1: continue
                boolval = False

            bits = line.split(None, 1)
            if len(bits) == 1:
                # Assume booleans are just one standalone word
                k = bits[0]
                v = boolval
            else:
                # Assume more than one word is a string value
                k, v = bits
            ret[k.lower()] = v
    return ret

if __name__ == '__main__':
    import sys
    conf = readconf(sys.argv[1])
    for k, v in sorted(conf.items()):
        print k, '=', v


#lang racket

(require "options.rkt")

(read-options "options-file")
(define-options fullname favouritefruit needspeeling seedsremoved otherfamily)
(printf "fullname       = ~s\n" fullname)
(printf "favouritefruit = ~s\n" favouritefruit)
(printf "needspeeling   = ~s\n" needspeeling)
(printf "seedsremoved   = ~s\n" seedsremoved)
(printf "otherfamily    = ~s\n" otherfamily)


my $fullname;
my $favouritefruit;
my $needspeeling = False;
my $seedsremoved = False;
my @otherfamily;

grammar ConfFile {
    token TOP {
	:my $*linenum = 0;
	^ <fullline>* [$ || (\N*) { die "Parse failed at $0" } ]
    }

    token fullline {
	<?before .>
	{ ++$*linenum }
	<line>
	[ \n || { die "Parse failed at line $*linenum" } ]
    }

    proto token line() {*}

    token line:misc  { {} (\S+) { die "Unrecognized word: $0" } }

    token line:sym<comment> { ^^ [ ';' | '#' ] \N* }
    token line:sym<blank>   { ^^ \h* $$ }

    token line:sym<fullname>       {:i fullname»       <rest> { $fullname = $<rest>.trim } }
    token line:sym<favouritefruit> {:i favouritefruit» <rest> { $favouritefruit = $<rest>.trim } }
    token line:sym<needspeeling>   {:i needspeeling»    <yes> { $needspeeling = defined $<yes> } }
    token rest { \h* '='? (\N*) }
    token yes { :i \h* '='? \h*
    	[
	    || ([yes|true|1])
	    || [no|false|0]
	    || (<?>)
	] \h*
    }
}

grammar MyConfFile is ConfFile {
    token line:sym<otherfamily>    {:i otherfamily»    <rest> { @otherfamily = $<rest>.split(',')».trim } }
}

MyConfFile.parsefile('file.cfg');

say "fullname: $fullname";
say "favouritefruit: $favouritefruit";
say "needspeeling: $needspeeling";
say "seedsremoved: $seedsremoved";
print "otherfamily: "; say @otherfamily.raku;


type TSettings extends QObject
    FullName as string
    FavouriteFruit as string
    NeedSpelling as integer
    SeedsRemoved as integer
    OtherFamily as QStringlist

    Constructor
        FullName = ""
        FavouriteFruit = ""
        NeedSpelling = 0
        SeedsRemoved = 0
        OtherFamily.clear
    end constructor
end type

Dim Settings as TSettings
dim ConfigList as QStringList
dim x as integer
dim StrLine as string
dim StrPara as string
dim StrData as string

function Trim$(Expr as string) as string
    Result = Rtrim$(Ltrim$(Expr))
end function

Sub ConfigOption(PData as string)
    dim x as integer
    for x = 1 to tally(PData, ",") +1
        Settings.OtherFamily.AddItems Trim$(field$(PData, "," ,x))
    next
end sub

Function ConfigBoolean(PData as string) as integer
    PData = Trim$(PData)
    Result = iif(lcase$(PData)="true" or PData="1" or PData="", 1, 0)
end function

sub ReadSettings
    ConfigList.LoadFromFile("Rosetta.cfg")
    ConfigList.text = REPLACESUBSTR$(ConfigList.text,"="," ")

    for x = 0 to ConfigList.ItemCount -1
        StrLine = Trim$(ConfigList.item(x))
        StrPara = Trim$(field$(StrLine," ",1))
        StrData = Trim$(lTrim$(StrLine - StrPara))

        Select case UCase$(StrPara)
        case "FULLNAME"       : Settings.FullName = StrData
        case "FAVOURITEFRUIT" : Settings.FavouriteFruit = StrData
        case "NEEDSPEELING"   : Settings.NeedSpelling = ConfigBoolean(StrData)
        case "SEEDSREMOVED"   : Settings.SeedsRemoved = ConfigBoolean(StrData)
        case "OTHERFAMILY"    : Call ConfigOption(StrData)
        end select
    next
end sub

Call ReadSettings


Red ["Read a config file"]

remove-each l lines: read/lines %file.conf [any [empty? l #"#" = l/1]]
foreach line lines [
	foo: parse line [collect [keep to [" " | end] skip keep to end]]
	either foo/1 = #";" [set to-word foo/2 false][
		set to-word foo/1 any [
			all [find foo/2 #"," split foo/2 ", "]
			foo/2
			true
		]
	]
]
foreach w [fullname favouritefruit needspeeling seedsremoved otherfamily][
	prin [pad w 15 ": "] probe get w
]


/*REXX program reads a config (configuration) file and assigns  VARs  as found within.  */
signal on syntax;      signal on novalue         /*handle REXX source program errors.   */
parse arg cFID _ .                               /*cFID:  is the CONFIG file to be read.*/
if cFID==''  then cFID='CONFIG.DAT'              /*Not specified?  Then use the default.*/
bad=                                             /*this will contain all the  bad VARs. */
varList=                                         /*  "    "     "     "   "  good   "   */
maxLenV=0;   blanks=0;   hashes=0;   semics=0;   badVar=0    /*zero all these variables.*/

   do j=0  while lines(cFID)\==0                 /*J:   it counts the lines in the file.*/
   txt=strip(linein(cFID))                       /*read a line (record) from the file,  */
                                                 /*  ··· & strip leading/trailing blanks*/
   if      txt    =''    then do; blanks=blanks+1; iterate; end   /*count # blank lines.*/
   if left(txt,1)=='#'   then do; hashes=hashes+1; iterate; end   /*  "   " lines with #*/
   if left(txt,1)==';'   then do; semics=semics+1; iterate; end   /*  "   "   "     "  ;*/
   eqS=pos('=',txt)                              /*we can't use the   TRANSLATE   BIF.  */
   if eqS\==0  then txt=overlay(' ',txt,eqS)     /*replace the first  '='  with a blank.*/
   parse var txt xxx value;  upper xxx           /*get the variable name and it's value.*/
   value=strip(value)                            /*strip leading and trailing blanks.   */
   if value='' then value='true'                 /*if no value,  then use   "true".     */
   if symbol(xxx)=='BAD'  then do                /*can REXX utilize the variable name ? */
                               badVar=badVar+1;  bad=bad xxx;  iterate  /*append to list*/
                               end
   varList=varList xxx                           /*add it to the list of good variables.*/
   call value xxx,value                          /*now,  use VALUE to set the variable. */
   maxLenV=max(maxLenV,length(value))            /*maxLen of varNames,  pretty display. */
   end   /*j*/

vars=words(varList);          @ig= 'ignored that began with a'
                    say #(j)       'record's(j) "were read from file: " cFID
if blanks\==0  then say #(blanks)  'blank record's(blanks) "were read."
if hashes\==0  then say #(hashes)  'record's(hashes)   @ig   "#  (hash)."
if semics\==0  then say #(semics)  'record's(semics)   @ig   ";  (semicolon)."
if badVar\==0  then say #(badVar)  'bad variable name's(badVar) 'detected:' bad
say;  say 'The list of'    vars    "variable"s(vars)    'and'    s(vars,'their',"it's"),
                                   "value"s(vars)       'follows:'
say;          do k=1  for vars
              v=word(varList,k)
              say  right(v,maxLenV) '=' value(v)
              end   /*k*/
say;  exit                                       /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
s:       if arg(1)==1  then return arg(3);               return word(arg(2) 's',1)
#:       return right(arg(1),length(j)+11)       /*right justify a number & also indent.*/
err:       do j=1  for arg();  say '***error***    ' arg(j);  say;  end  /*j*/;    exit 13
novalue: syntax:   call err 'REXX program' condition('C') "error",,
         condition('D'),'REXX source statement (line' sigl"):",sourceline(sigl)


fullname = favouritefruit = ""
needspeeling = seedsremoved = false
otherfamily = []

IO.foreach("config.file") do |line|
  line.chomp!
  key, value = line.split(nil, 2)
  case key
  when /^([#;]|$)/; # ignore line
  when "FULLNAME"; fullname = value
  when "FAVOURITEFRUIT"; favouritefruit = value
  when "NEEDSPEELING"; needspeeling = true
  when "SEEDSREMOVED"; seedsremoved = true
  when "OTHERFAMILY"; otherfamily = value.split(",").map(&:strip)
  when /^./; puts "#{key}: unknown key"
  end
end

puts "fullname       = #{fullname}"
puts "favouritefruit = #{favouritefruit}"
puts "needspeeling   = #{needspeeling}"
puts "seedsremoved   = #{seedsremoved}"
otherfamily.each_with_index do |name, i|
  puts "otherfamily(#{i+1}) = #{name}"
end


dim param$(6)
dim paramVal$(6)
param$(1) = "fullname"
param$(2) = "favouritefruit"
param$(3) = "needspeeling"
param$(4) = "seedsremoved"
param$(5) = "otherfamily"
for i = 1 to 6
 paramVal$(i) = "false"
next i

open DefaultDir$ + "\public\a.txt" for binary as #f
while not(eof(#f))
	line input #f, a$
	a$ = trim$(a$)
	if instr("#;",left$(a$,1)) = 0 and a$ <> "" then
		thisParam$ = lower$(word$(a$,1," "))
	  for i = 1 to 5
		if param$(i)    = thisParam$ then
			paramVal$(i)  = "true"
			aa$ = trim$(mid$(a$,len(thisParam$)+2))
			if aa$ <> "" then paramVal$(i) = aa$
		end if
	  next i
	end if
wend
close #f
for i = 1 to 5
  if instr(paramVal$(i),",") > 0 then
   for j = 1 to 2
     print param$(i);"(";j;")";chr$(9);trim$(word$(paramVal$(i),j,","))
   next j
  else
   print param$(i);chr$(9);paramVal$(i)
  end if
next i


use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::iter::FromIterator;
use std::path::Path;

fn main() {
    let path = String::from("file.conf");
    let cfg = config_from_file(path);
    println!("{:?}", cfg);
}

fn config_from_file(path: String) -> Config {
    let path = Path::new(&path);
    let file = File::open(path).expect("File not found or cannot be opened");
    let content = BufReader::new(&file);
    let mut cfg = Config::new();

    for line in content.lines() {
        let line = line.expect("Could not read the line");
        // Remove whitespaces at the beginning and end
        let line = line.trim();

        // Ignore comments and empty lines
        if line.starts_with("#") || line.starts_with(";") || line.is_empty() {
            continue;
        }

        // Split line into parameter name and rest tokens
        let tokens = Vec::from_iter(line.split_whitespace());
        let name = tokens.first().unwrap();
        let tokens = tokens.get(1..).unwrap();

        // Remove the equal signs
        let tokens = tokens.iter().filter(|t| !t.starts_with("="));
        // Remove comment after the parameters
        let tokens = tokens.take_while(|t| !t.starts_with("#") && !t.starts_with(";"));

        // Concat back the parameters into one string to split for separated parameters
        let mut parameters = String::new();
        tokens.for_each(|t| { parameters.push_str(t); parameters.push(' '); });
        // Splits the parameters and trims
        let parameters = parameters.split(',').map(|s| s.trim());
        // Converts them from Vec<&str> into Vec<String>
        let parameters: Vec<String> = parameters.map(|s| s.to_string()).collect();

        // Setting the config parameters
        match name.to_lowercase().as_str() {
            "fullname" => cfg.full_name = parameters.get(0).cloned(),
            "favouritefruit" => cfg.favourite_fruit = parameters.get(0).cloned(),
            "needspeeling" => cfg.needs_peeling = true,
            "seedsremoved" => cfg.seeds_removed = true,
            "otherfamily" => cfg.other_family = Some(parameters),
            _ => (),
        }
    }

    cfg
}

#[derive(Clone, Debug)]
struct Config {
    full_name: Option<String>,
    favourite_fruit: Option<String>,
    needs_peeling: bool,
    seeds_removed: bool,
    other_family: Option<Vec<String>>,
}

impl Config {
    fn new() -> Config {
        Config {
            full_name: None,
            favourite_fruit: None,
            needs_peeling: false,
            seeds_removed: false,
            other_family: None,
        }
    }
}


val conf = scala.io.Source.fromFile("config.file").
  getLines.
  toList.
  filter(_.trim.size > 0).
  filterNot("#;" contains _(0)).
  map(_ split(" ", 2) toList).
  map(_ :+ "true" take 2).
  map {
    s:List[String] => (s(0).toLowerCase, s(1).split(",").map(_.trim).toList)
  }.toMap


$ include "seed7_05.s7i";
  include "scanfile.s7i";

var string:  fullname is "";
var string:  favouritefruit is "";
var boolean: needspeeling is FALSE;
var boolean: seedsremoved is FALSE;
var array string: otherfamily is 0 times "";

const proc: main is func
  local
    var file: configFile is STD_NULL;
    var string: symbol is "";
    var integer: index is 0;
  begin
    configFile := open("readcfg.txt", "r");
    configFile.bufferChar := getc(configFile);
    symbol := lower(getWord(configFile));
    while symbol <> "" do
      skipSpace(configFile);
      if symbol = "#" or symbol = ";" then
        skipLine(configFile);
      elsif symbol = "fullname" then
        fullname := getLine(configFile);
      elsif symbol = "favouritefruit" then
        favouritefruit := getLine(configFile);
      elsif symbol = "needspeeling" then
        needspeeling := TRUE;
      elsif symbol = "seedsremoved" then
        seedsremoved := TRUE;
      elsif symbol = "otherfamily" then
        otherfamily := split(getLine(configFile), ",");
        for key index range otherfamily do
          otherfamily[index] := trim(otherfamily[index]);
        end for;
      else
        writeln(" *** Illegal line " <& literal(getLine(configFile)));
      end if;
      symbol := lower(getWord(configFile));
    end while;
    close(configFile);
    writeln("fullname:       " <& fullname);
    writeln("favouritefruit: " <& favouritefruit);
    writeln("needspeeling:   " <& needspeeling);
    writeln("seedsremoved:   " <& seedsremoved);
    for key index range otherfamily do
      writeln(("otherfamily[" <& index <& "]:") rpad 16 <& otherfamily[index]);
    end for;
  end func;


// read the configuration file and get a list of just the interesting lines
set lines to each line of file "config.txt" where char 1 of each isn't in ("#", ";", "")

set the listFormat's quotes to quote -- be sure to quote values for evaluating

repeat with each configLine in lines
	put word 1 of configLine into varName
	insert varName into variableNames -- make a list of all config variables

	put (words 2 to last of configLine) split by comma into values
	put trim of each item of values into values -- trim any leading/trailing spaces
	if values is empty then set values to true -- no value means boolean true
	do "set" && varName && "to" && values -- assign value to variable
end repeat

repeat with each name in variableNames
	put "Variable" && name && "is" && value(name)
end repeat


var fullname = (var favouritefruit = "");
var needspeeling = (var seedsremoved = false);
var otherfamily = [];

ARGF.each { |line|
    var(key, value) = line.strip.split(/\h+/, 2)...;

    given(key) {
        when (nil)              { }
        when (/^([#;]|\h*$)/)   { }
        when ("FULLNAME")       { fullname = value }
        when ("FAVOURITEFRUIT") { favouritefruit = value }
        when ("NEEDSPEELING")   { needspeeling = true }
        when ("SEEDSREMOVED")   { seedsremoved = true }
        when ("OTHERFAMILY")    { otherfamily = value.split(',')»strip»() }
        default                 { say "#{key}: unknown key" }
    }
}

say "fullname       = #{fullname}";
say "favouritefruit = #{favouritefruit}";
say "needspeeling   = #{needspeeling}";
say "seedsremoved   = #{seedsremoved}";

otherfamily.each_kv {|i, name|
    say "otherfamily(#{i+1}) = #{name}";
}


dict := Dictionary new.
configFile asFilename readingLinesDo:[:line |
    (line isEmpty or:[ line startsWithAnyOf:#('#' ';') ]) ifFalse:[
        s := line readStream.
        (s skipSeparators; atEnd) ifFalse:[
            |optionName values|
            optionName := s upToSeparator.
            values := (s upToEnd asCollectionOfSubstringsSeparatedBy:$,)
                         collect:[:each | each withoutSeparators]
                         thenSelect:[:vals | vals notEmpty].
            dict at:optionName asLowercase put:(values isEmpty
                                                ifTrue:[true]
                                                ifFalse:[
                                                   values size == 1
                                                     ifTrue:[values first]
                                                     ifFalse:[values]]).
        ]
    ].
]


proc readConfig {filename {defaults {}}} {
    global cfg
    # Read the file in
    set f [open $filename]
    set contents [read $f]
    close $f
    # Set up the defaults, if supplied
    foreach {var defaultValue} $defaults {
	set cfg($var) $defaultValue
    }
    # Parse the file's contents
    foreach line [split $contents "\n"] {
	set line [string trim $line]
	# Skip comments
	if {[string match "#*" $line] || [string match ";*" $line]} continue
	# Skip blanks
	if {$line eq ""} continue

	if {[regexp {^\w+$} $line]} {
	    # Boolean case
	    set cfg([string tolower $line]) true
	} elseif {[regexp {^(\w+)\s+([^,]+)$} $line -> var value]} {
	    # Simple value case
	    set cfg([string tolower $var]) $value
	} elseif {[regexp {^(\w+)\s+(.+)$} $line -> var listValue]} {
	    # List value case
	    set cfg([string tolower $var]) {}
	    foreach value [split $listValue ","] {
		lappend cfg([string tolower $var]) [string trim $value]
	    }
	} else {
	    error "malformatted config file: $filename"
	}
    }
}

# Need to supply some default values due to config file grammar ambiguities
readConfig "fruit.cfg" {
    needspeeling false
    seedsremoved false
}
puts "Full name: $cfg(fullname)"
puts "Favourite: $cfg(favouritefruit)"
puts "Peeling?   $cfg(needspeeling)"
puts "Unseeded?  $cfg(seedsremoved)"
puts "Family:    $cfg(otherfamily)"


using System;
using System.Collections.Generic;
using System.Linq;
using static System.Console;
using UI = System.UInt64;
using LST = System.Collections.Generic.List<System.Collections.Generic.List<sbyte>>;
using Lst = System.Collections.Generic.List<sbyte>;
using DT = System.DateTime;

class Program {

    const sbyte MxD = 19;

    public struct term { public UI coeff; public sbyte a, b;
        public term(UI c, int a_, int b_) { coeff = c; a = (sbyte)a_; b = (sbyte)b_; } }

    static int[] digs;   static List<UI> res;   static sbyte count = 0;
    static DT st; static List<List<term>> tLst; static List<LST> lists;
    static Dictionary<int, LST> fml, dmd; static Lst dl, zl, el, ol, il;
    static bool odd; static int nd, nd2; static LST ixs;
    static int[] cnd, di; static LST dis; static UI Dif;

    // converts digs array to the "difference"
    static UI ToDif() { UI r = 0; for (int i = 0; i < digs.Length; i++)
            r = r * 10 + (uint)digs[i]; return r; }

    // converts digs array to the "sum"
    static UI ToSum() { UI r = 0; for (int i = digs.Length - 1; i >= 0; i--)
            r = r * 10 + (uint)digs[i]; return Dif + (r << 1); }

    // determines if the nmbr is square or not
    static bool IsSquare(UI nmbr) { if ((0x202021202030213 & (1 << (int)(nmbr & 63))) != 0)
        { UI r = (UI)Math.Sqrt((double)nmbr); return r * r == nmbr; } return false; }

    // returns sequence of sbytes
    static Lst Seq(sbyte from, int to, sbyte stp = 1) { Lst res = new Lst();
        for (sbyte item = from; item <= to; item += stp) res.Add(item); return res; }

    // Recursive closure to generate (n+r) candidates from (n-r) candidates
    static void Fnpr(int lev) { if (lev == dis.Count) { digs[ixs[0][0]] = fml[cnd[0]][di[0]][0];
            digs[ixs[0][1]] = fml[cnd[0]][di[0]][1]; int le = di.Length, i = 1;
            if (odd) digs[nd >> 1] = di[--le]; foreach (sbyte d in di.Skip(1).Take(le - 1)) {
                digs[ixs[i][0]] = dmd[cnd[i]][d][0]; digs[ixs[i][1]] = dmd[cnd[i++]][d][1]; }
            if (!IsSquare(ToSum())) return; res.Add(ToDif()); WriteLine("{0,16:n0}{1,4}   ({2:n0})",
                (DT.Now - st).TotalMilliseconds, ++count, res.Last()); }
        else foreach (var n in dis[lev]) { di[lev] = n; Fnpr(lev + 1); } }

    // Recursive closure to generate (n-r) candidates with a given number of digits.
    static void Fnmr (LST list, int lev) { if (lev == list.Count) { Dif = 0; sbyte i = 0;
            foreach (var t in tLst[nd2]) { if (cnd[i] < 0) Dif -= t.coeff * (UI)(-cnd[i++]);
                else Dif += t.coeff * (UI)cnd[i++]; } if (Dif <= 0 || !IsSquare(Dif)) return;
            dis = new LST { Seq(0, fml[cnd[0]].Count - 1) };
            foreach (int ii in cnd.Skip(1)) dis.Add(Seq(0, dmd[ii].Count - 1));
            if (odd) dis.Add(il); di = new int[dis.Count]; Fnpr(0);
        } else foreach(sbyte n in list[lev]) { cnd[lev] = n; Fnmr(list, lev + 1); } }

    static void init() { UI pow = 1;
        // terms of (n-r) expression for number of digits from 2 to maxDigits
        tLst = new List<List<term>>(); foreach (int r in Seq(2, MxD)) {
            List<term> terms = new List<term>(); pow *= 10; UI p1 = pow, p2 = 1;
            for (int i1 = 0, i2 = r - 1; i1 < i2; i1++, i2--) {
                terms.Add(new term(p1 - p2, i1, i2)); p1 /= 10; p2 *= 10; }
            tLst.Add(terms); }
        //  map of first minus last digits for 'n' to pairs giving this value
        fml = new Dictionary<int, LST> {
            [0] = new LST { new Lst { 2, 2 }, new Lst { 8, 8 } },
            [1] = new LST { new Lst { 6, 5 }, new Lst { 8, 7 } },
            [4] = new LST { new Lst { 4, 0 } },
            [6] = new LST { new Lst { 6, 0 }, new Lst { 8, 2 } } };
        // map of other digit differences for 'n' to pairs giving this value
        dmd = new Dictionary<int, LST>();
        for (sbyte i = 0; i < 10; i++) for (sbyte j = 0, d = i; j < 10; j++, d--) {
                if (dmd.ContainsKey(d)) dmd[d].Add(new Lst { i, j });
                else dmd[d] = new LST { new Lst { i, j } }; }
        dl = Seq(-9, 9);    // all differences
        zl = Seq( 0, 0);    // zero differences only
        el = Seq(-8, 8, 2); // even differences only
        ol = Seq(-9, 9, 2); // odd differences only
        il = Seq( 0, 9); lists = new List<LST>();
        foreach (sbyte f in fml.Keys) lists.Add(new LST { new Lst { f } }); }

    static void Main(string[] args) { init(); res = new List<UI>(); st = DT.Now; count = 0;
        WriteLine("{0,5}{1,12}{2,4}{3,14}", "digs", "elapsed(ms)", "R/N", "Unordered Rare Numbers");
        for (nd = 2, nd2 = 0, odd = false; nd <= MxD; nd++, nd2++, odd = !odd) { digs = new int[nd];
            if (nd == 4) { lists[0].Add(zl); lists[1].Add(ol); lists[2].Add(el); lists[3].Add(ol); }
            else if (tLst[nd2].Count > lists[0].Count) foreach (LST list in lists) list.Add(dl);
            ixs = new LST();
            foreach (term t in tLst[nd2]) ixs.Add(new Lst { t.a, t.b });
            foreach (LST list in lists) { cnd = new int[list.Count]; Fnmr(list, 0); }
            WriteLine("  {0,2}  {1,10:n0}", nd, (DT.Now - st).TotalMilliseconds); }
        res.Sort();
        WriteLine("\nThe {0} rare numbers with up to {1} digits are:", res.Count, MxD);
        count = 0; foreach (var rare in res) WriteLine("{0,2}:{1,27:n0}", ++count, rare);
        if (System.Diagnostics.Debugger.IsAttached) ReadKey(); }
}


readconfig() (
    # redirect stdin to read from the given filename
    exec 0<"$1"

    declare -l varname
    while IFS=$' =\t' read -ra words; do
        # is it a comment or blank line?
        if [[ ${#words[@]} -eq 0 || ${words[0]} == ["#;"]* ]]; then
            continue
        fi

        # get the variable name
        varname=${words[0]}

        # split all the other words by comma
        value="${words[*]:1}"
        oldIFS=$IFS IFS=,
        values=( $value )
        IFS=$oldIFS

        # assign the other words to a "scalar" variable or array
        case ${#values[@]} in
            0) printf '%s=true\n' "$varname" ;;
            1) printf '%s=%q\n' "$varname" "${values[0]}" ;;
            *) n=0
               for value in "${values[@]}"; do
                   value=${value# }
                   printf '%s[%d]=%q\n' "$varname" $((n++)) "${value% }"
               done
               ;;
        esac
    done
)

# parse the config file and evaluate the output in the current shell
source <( readconfig config.file )

echo "fullname = $fullname"
echo "favouritefruit = $favouritefruit"
echo "needspeeling = $needspeeling"
echo "seedsremoved = $seedsremoved"
for i in "${!otherfamily[@]}"; do
    echo "otherfamily[$i] = ${otherfamily[i]}"
done


// Rare Numbers : Nigel Galloway - December 20th., 2019;
// Nigel Galloway/Enter your username - January 4th., 2021 (see discussion page.
#include <functional>
#include <bitset>
#include <cmath>
using namespace std;
using Z2 = optional<long long>; using Z1 = function<Z2()>;
// powers of 10 array
constexpr auto pow10 = [] { array <long long, 19> n {1}; for (int j{0}, i{1}; i < 19; j = i++) n[i] = n[j] * 10; return n; } ();
long long acc, l;
bool izRev(int n, unsigned long long i, unsigned long long g) {
  return (i / pow10[n - 1] != g % 10) ? false : n < 2 ? true : izRev(n - 1, i % pow10[n - 1], g / 10);
}
const Z1 fG(Z1 n, int start, int end, int reset, const long long step, long long &l) {
  return [n, i{step * start}, g{step * end}, e{step * reset}, &l, step] () mutable {
    while (i<g){i+=step; return Z2(l+=step);}
    l-=g-(i=e); return n();};
}
struct nLH {
  vector<unsigned long long>even{}, odd{};
  nLH(const Z1 a, const vector<long long> b, long long llim){while (auto i = a()) for (auto ng : b)
    if(ng>0 | *i>llim){unsigned long long sq{ng+ *i}, r{sqrt(sq)}; if (r*r == sq) ng&1 ? odd.push_back(sq) : even.push_back(sq);}}
};
const double fac = 3.94;
const int mbs = (int)sqrt(fac * pow10[9]), mbt = (int)sqrt(fac * fac * pow10[9]) >> 3;
const bitset<100000>bs {[]{bitset<100000>n{false}; for(int g{3};g<mbs;++g) n[(g*g)%100000]=true; return n;}()};
constexpr array<const int,  7>li{1,3,0,0,1,1,1},lin{0,-7,0,0,-8,-3,-9},lig{0,9,0,0,8,7,9},lil{0,2,0,0,2,10,2};
const nLH makeL(const int n){
  constexpr int r{9}; acc=0; Z1 g{[]{return Z2{};}}; int s{-r}, q{(n>11)*5}; vector<long long> w{};
  for (int i{1};i<n/2-q+1;++i){l=pow10[n-i-q]-pow10[i+q-1]; s-=i==n/2-q; g=fG(g,s,r,-r,l,acc+=l*s);}
  if(q){long long g0{0}, g1{0}, g2{0}, g3{0}, g4{0}, l3{pow10[n-5]}; while (g0<7){const long long g{-10000*g4-1000*g3-100*g2-10*g1-g0};
    if (bs[(g+1000000000000LL)%100000]) w.push_back(l3*(g4+g3*10+g2*100+g1*1000+g0*10000)+g);
    if(g4<r) ++g4; else{g4= -r; if(g3<r) ++g3; else{g3= -r; if(g2<r) ++g2; else{g2= -r; if(g1<lig[g0]) g1+=lil[g0]; else {g0+=li[g0];g1=lin[g0];}}}}}}
  return q ? nLH(g,w,0) : nLH(g,{0},0);
}
const bitset<100000>bt {[]{bitset<100000>n{false}; for(int g{11};g<mbt;++g) n[(g*g)%100000]=true; return n;}()};
constexpr array<const int, 17>lu{0,0,0,0,2,0,4,0,0,0,1,4,0,0,0,1,1},lun{0,0,0,0,0,0,1,0,0,0,9,1,0,0,0,1,0},lug{0,0,0,0,18,0,17,0,0,0,9,17,0,0,0,11,18},lul{0,0,0,0,2,0,2,0,0,0,0,2,0,0,0,10,2};
const nLH makeH(const int n){
  acc= -pow10[n>>1]-pow10[(n-1)>>1]; Z1 g{[]{ return Z2{};}}; int q{(n>11)*5}; vector<long long> w {};
  for (int i{1}; i<(n>>1)-q+1; ++i) g = fG(g,0,18,0,pow10[n-i-q]+pow10[i+q-1], acc);
  if (n & 1){l=pow10[n>>1]<<1; g=fG(g,0,9,0,l,acc+=l);}
  if(q){long long g0{4}, g1{0}, g2{0}, g3{0}, g4{0},l3{pow10[n-5]}; while (g0<17){const long long g{g4*10000+g3*1000+g2*100+g1*10+g0};
    if (bt[g%100000]) w.push_back(l3*(g4+g3*10+g2*100+g1*1000+g0*10000)+g);
    if (g4<18) ++g4; else{g4=0; if(g3<18) ++g3; else{g3=0; if(g2<18) ++g2; else{g2=0; if(g1<lug[g0]) g1+=lul[g0]; else{g0+=lu[g0];g1=lun[g0];}}}}}}
  return q ? nLH(g,w,0) : nLH(g,{0},pow10[n-1]<<2);
}
#include <chrono>
using namespace chrono; using VU = vector<unsigned long long>; using VS = vector<string>;
template <typename T> // concatenates vectors
vector<T>& operator +=(vector<T>& v, const vector<T>& w) { v.insert(v.end(), w.begin(), w.end()); return v; }
int c{0}; // solution counter
auto st{steady_clock::now()}, st0{st}, tmp{st}; // for determining elasped time
// formats elasped time
string dFmt(duration<double> et, int digs) {
  string res{""}; double dt{et.count()};
  if (dt > 60.0) { int m = (int)(dt / 60.0); dt -= m * 60.0; res = to_string(m) + "m"; }
  res += to_string(dt); return res.substr(0, digs - 1) + 's';
}
// combines list of square differences with list of square sums, reports compatible results
VS dump(int nd, VU lo, VU hi) {
  VS res {};
  for (auto l : lo) for (auto h : hi) {
    auto r { (h - l) >> 1 }, z { h - r };
    if (izRev(nd, r, z)) {
      char buf[99]; sprintf(buf, "%20llu %11lu %10lu", z, (long long)sqrt(h), (long long)sqrt(l));
      res.push_back(buf); } } return res;
}
// reports one block of digits
void doOne(int n, nLH L, nLH H) {
  VS lines = dump(n, L.even, H.even); lines += dump(n, L.odd , H.odd); sort(lines.begin(), lines.end());
  duration<double> tet = (tmp = steady_clock::now()) - st; int ls = lines.size();
  if (ls-- > 0)
    for (int i{0}; i <= ls; ++i)
      printf("%3d %s%s", ++c, lines[i].c_str(), i == ls ? "" : "\n");
  else printf("%s", string(47, ' ').c_str());
  printf("  %2d:     %s  %s\n", n, dFmt(tmp - st0, 8).c_str(), dFmt(tet, 8).c_str()); st0 = tmp;
}
void Rare(int n) { doOne(n, makeL(n), makeH(n)); }
int main(int argc, char *argv[]) {
  int max{argc > 1 ? stoi(argv[1]) : 19}; if (max < 2) max = 2; if (max > 19 ) max = 19;
  printf("%4s %19s %11s %10s %5s %11s %9s\n", "nth", "forward", "rt.sum", "rt.diff", "digs", "block.et", "total.et");
  for (int nd{2}; nd <= max; ++nd) Rare(nd);
}


Set ofso = CreateObject("Scripting.FileSystemObject")
Set config = ofso.OpenTextFile(ofso.GetParentFolderName(WScript.ScriptFullName)&"\config.txt",1)

config_out = ""

Do Until config.AtEndOfStream
	line = config.ReadLine
	If Left(line,1) <> "#" And Len(line) <> 0 Then
		config_out = config_out & parse_var(line) & vbCrLf
	End If
Loop

WScript.Echo config_out

Function parse_var(s)
	'boolean false
	If InStr(s,";") Then
		parse_var = Mid(s,InStr(1,s,";")+2,Len(s)-InStr(1,s,";")+2) & " = FALSE"
	'boolean true
	ElseIf UBound(Split(s," ")) = 0 Then
		parse_var = s & " = TRUE"
	'multiple parameters
	ElseIf InStr(s,",") Then
		var = Left(s,InStr(1,s," ")-1)
		params = Split(Mid(s,InStr(1,s," ")+1,Len(s)-InStr(1,s," ")+1),",")
		n = 1 : tmp = ""
		For i = 0 To UBound(params)
			parse_var = parse_var & var & "(" & n & ") = " & LTrim(params(i)) & vbCrLf
			n = n + 1
		Next
	'single var and paramater
	Else
		parse_var = Left(s,InStr(1,s," ")-1) & " = " & Mid(s,InStr(1,s," ")+1,Len(s)-InStr(1,s," ")+1)
	End If
End Function

config.Close
Set ofso = Nothing


#11 = 0                 // needspeeling = FALSE
#12 = 0                 // seedsremoved = FALSE
Reg_Empty(21)           // fullname
Reg_Empty(22)           // favouritefruit
Reg_Empty(23)           // otherfamily

File_Open("|(PATH_ONLY)\example.cfg")

if (Search("|<FULLNAME|s", BEGIN+ADVANCE+NOERR)) {
    Match("=", ADVANCE)         // skip optional '='
    Reg_Copy_Block(21, CP, EOL_pos)
}
if (Search("|<FAVOURITEFRUIT|s", BEGIN+ADVANCE+NOERR)) {
    Match("=", ADVANCE)
    Reg_Copy_Block(22, CP, EOL_pos)
}
if (Search("|<OTHERFAMILY|s", BEGIN+ADVANCE+NOERR)) {
    Match("=", ADVANCE)
    Reg_Copy_Block(23, CP, EOL_pos)
}
if (Search("|<NEEDSPEELING|s", BEGIN+ADVANCE+NOERR)) {
    #11 = 1
}
if (Search("|<SEEDSREMOVED|s", BEGIN+ADVANCE+NOERR)) {
    #12 = 1
}

Buf_Quit(OK)            // close .cfg file

// Display the variables
Message("needspeeling   = ") Num_Type(#11, LEFT)
Message("seedsremoved   = ") Num_Type(#12, LEFT)
Message("fullname       = ") Reg_Type(21) TN
Message("favouritefruit = ") Reg_Type(22) TN
Message("otherfamily    = ") Reg_Type(23) TN


import std.algorithm;
import std.array;
import std.conv;
import std.datetime.stopwatch;
import std.math;
import std.stdio;

struct Term {
    ulong coeff;
    byte ix1, ix2;
}

enum maxDigits = 16;

ulong toUlong(byte[] digits, bool reverse) {
    ulong sum = 0;
    if (reverse) {
        for (int i = digits.length - 1; i >= 0; --i) {
            sum = sum * 10 + digits[i];
        }
    } else {
        for (size_t i = 0; i < digits.length; ++i) {
            sum = sum * 10 + digits[i];
        }
    }
    return sum;
}

bool isSquare(ulong n) {
    if ((0x202021202030213 & (1 << (n & 63))) != 0) {
        auto root = cast(ulong)sqrt(cast(double)n);
        return root * root == n;
    }
    return false;
}

byte[] seq(byte from, byte to, byte step) {
    byte[] res;
    for (auto i = from; i <= to; i += step) {
        res ~= i;
    }
    return res;
}

string commatize(ulong n) {
    auto s = n.to!string;
    auto le = s.length;
    for (int i = le - 3; i >= 1; i -= 3) {
        s = s[0..i] ~ "," ~ s[i..$];
    }
    return s;
}

void main() {
    auto sw = StopWatch(AutoStart.yes);
    ulong pow = 1;
    writeln("Aggregate timings to process all numbers up to:");
    // terms of (n-r) expression for number of digits from 2 to maxDigits
    Term[][] allTerms = uninitializedArray!(Term[][])(maxDigits - 1);
    for (auto r = 2; r <= maxDigits; r++) {
        Term[] terms;
        pow *= 10;
        ulong pow1 = pow;
        ulong pow2 = 1;
        byte i1 = 0;
        byte i2 = cast(byte)(r - 1);
        while (i1 < i2) {
            terms ~= Term(pow1 - pow2, i1, i2);

            pow1 /= 10;
            pow2 *= 10;

            i1++;
            i2--;
        }
        allTerms[r - 2] = terms;
    }
    //  map of first minus last digits for 'n' to pairs giving this value
    byte[][][byte] fml = [
        0: [[2, 2], [8, 8]],
        1: [[6, 5], [8, 7]],
        4: [[4, 0]],
        6: [[6, 0], [8, 2]]
    ];
    // map of other digit differences for 'n' to pairs giving this value
    byte[][][byte] dmd;
    for (byte i = 0; i < 100; i++) {
        byte[] a = [i / 10, i % 10];
        auto d = a[0] - a[1];
        dmd[cast(byte)d] ~= a;
    }
    byte[] fl = [0, 1, 4, 6];
    auto dl = seq(-9, 9, 1);    // all differences
    byte[] zl = [0];            // zero diferences only
    auto el = seq(-8, 8, 2);    // even differences only
    auto ol = seq(-9, 9, 2);    // odd differences only
    auto il = seq(0, 9, 1);
    ulong[] rares;
    byte[][][] lists = uninitializedArray!(byte[][][])(4);
    foreach (i, f; fl) {
        lists[i] = [[f]];
    }
    byte[] digits;
    int count = 0;

    // Recursive closure to generate (n+r) candidates from (n-r) candidates
    // and hence find Rare numbers with a given number of digits.
    void fnpr(byte[] cand, byte[] di, byte[][] dis, byte[][] indicies, ulong nmr, int nd, int level) {
        if (level == dis.length) {
            digits[indicies[0][0]] = fml[cand[0]][di[0]][0];
            digits[indicies[0][1]] = fml[cand[0]][di[0]][1];
            auto le = di.length;
            if (nd % 2 == 1) {
                le--;
                digits[nd / 2] = di[le];
            }
            foreach (i, d; di[1..le]) {
                digits[indicies[i + 1][0]] = dmd[cand[i + 1]][d][0];
                digits[indicies[i + 1][1]] = dmd[cand[i + 1]][d][1];
            }
            auto r = toUlong(digits, true);
            auto npr = nmr + 2 * r;
            if (!isSquare(npr)) {
                return;
            }
            count++;
            writef("     R/N %2d:", count);
            auto ms = sw.peek();
            writef("  %9s", ms);
            auto n = toUlong(digits, false);
            writef("  (%s)\n", commatize(n));
            rares ~= n;
        } else {
            foreach (num; dis[level]) {
                di[level] = num;
                fnpr(cand, di, dis, indicies, nmr, nd, level + 1);
            }
        }
    }

    // Recursive closure to generate (n-r) candidates with a given number of digits.
    void fnmr(byte[] cand, byte[][] list, byte[][] indicies, int nd, int level) {
        if (level == list.length) {
            ulong nmr, nmr2;
            foreach (i, t; allTerms[nd - 2]) {
                if (cand[i] >= 0) {
                    nmr += t.coeff * cand[i];
                } else {
                    nmr2 += t.coeff * -cast(int)(cand[i]);
                    if (nmr >= nmr2) {
                        nmr -= nmr2;
                        nmr2 = 0;
                    } else {
                        nmr2 -= nmr;
                        nmr = 0;
                    }
                }
            }
            if (nmr2 >= nmr) {
                return;
            }
            nmr -= nmr2;
            if (!isSquare(nmr)) {
                return;
            }
            byte[][] dis;
            dis ~= seq(0, cast(byte)(fml[cand[0]].length - 1), 1);
            for (auto i = 1; i < cand.length; i++) {
                dis ~= seq(0, cast(byte)(dmd[cand[i]].length - 1), 1);
            }
            if (nd % 2 == 1) {
                dis ~= il;
            }
            byte[] di = uninitializedArray!(byte[])(dis.length);
            fnpr(cand, di, dis, indicies, nmr, nd, 0);
        } else {
            foreach (num; list[level]) {
                cand[level] = num;
                fnmr(cand, list, indicies, nd, level + 1);
            }
        }
    }

    for (int nd = 2; nd <= maxDigits; nd++) {
        digits = uninitializedArray!(byte[])(nd);
        if (nd == 4) {
            lists[0] ~= zl;
            lists[1] ~= ol;
            lists[2] ~= el;
            lists[3] ~= ol;
        } else if (allTerms[nd - 2].length > lists[0].length) {
            for (int i = 0; i < 4; i++) {
                lists[i] ~= dl;
            }
        }
        byte[][] indicies;
        foreach (t; allTerms[nd - 2]) {
            indicies ~= [t.ix1, t.ix2];
        }
        foreach (list; lists) {
            byte[] cand = uninitializedArray!(byte[])(list.length);
            fnmr(cand, list, indicies, nd, 0);
        }
        auto ms = sw.peek();
        writefln("  %2d digits:  %9s", nd, ms);
    }

    rares.sort;
    writefln("\nThe rare numbers with up to %d digits are:", maxDigits);
    foreach (i, rare; rares) {
        writefln("  %2d:  %25s", i + 1, commatize(rare));
    }
}


' Configuration file parser routines.
'
' (c) Copyright 1993 - 2011 Mark Hobley
'
' This configuration parser contains code ported from an application program
' written in Microsoft Quickbasic
'
' This code can be redistributed or modified under the terms of version 1.2 of
' the GNU Free Documentation Licence as published by the Free Software Foundation.

Sub readini()
  var.filename = btrim$(var.winpath) & ini.inifile
  var.filebuffersize = ini.inimaxlinelength
  Call openfileread
  If flg.error = "Y" Then
    flg.abort = "Y"
    Exit Sub
  End If
  If flg.exists <> "Y" Then
    flg.abort = "Y"
    Exit Sub
  End If
  var.inistream = var.stream
readinilabela:
  Call readlinefromfile
  If flg.error = "Y" Then
    flg.abort = "Y"
    Call closestream
    flg.error = "Y"
    Exit Sub
  End If
  If flg.endoffile <> "Y" Then
    iniline$ = message$
    If iniline$ <> "" Then
      If Left$(iniline$, 1) <> ini.commentchar AND Left$(iniline$, 1) <> ini.ignorechar Then
        endofinicommand% = 0
        For l% = 1 To Len(iniline$)
          If Mid$(iniline$, l%, 1) < " " Then
            endofinicommand% = l%
          End If
          If Not (endofinicommand%) Then
            If Mid$(iniline$, l%, 1) = " " Then
              endofinicommand% = l%
            End If
          End If
          If endofinicommand% Then
            l% = Len(iniline$)
          End If
        Next l%
        iniarg$ = ""
        If endofinicommand% Then
          If endofinicommand% <> Len(iniline$) Then
            iniarg$ = btrim$(Mid$(iniline$, endofinicommand% + 1))
            If iniarg$ = "" Then
              GoTo readinilabelb
            End If
            inicommand$ = Left$(iniline$, endofinicommand% - 1)
          End If
        Else
          inicommand$ = btrim$(iniline$)
        End If
readinilabelb:
        'interpret command
        inicommand$ = UCase$(inicommand$)
        Select Case inicommand$
          Case "FULLNAME"
            If iniarg$ <> "" Then
              ini.fullname = iniarg$
            End If
          Case "FAVOURITEFRUIT"
            If iniarg$ <> "" Then
              ini.favouritefruit = iniarg$
            End If
          Case "NEEDSPEELING"
            ini.needspeeling = "Y"
          Case "SEEDSREMOVED"
            ini.seedsremoved = "Y"
          Case "OTHERFAMILY"
            If iniarg$ <> "" Then
              ini.otherfamily = iniarg$
              CALL familyparser
            End If
          Case Else
            '!! error handling required
        End Select
      End If
    End If
    GoTo readinilabela
  End If
  Call closestream
  Exit Sub
readinierror:

End Sub

Sub openfileread()
  flg.streamopen = "N"
  Call checkfileexists
  If flg.error = "Y" Then Exit Sub
  If flg.exists <> "Y" Then Exit Sub
  Call getfreestream
  If flg.error = "Y" Then Exit Sub
  var.errorsection = "Opening File"
  var.errordevice = var.filename
  If ini.errortrap = "Y" Then
    On Local Error GoTo openfilereaderror
  End If
  flg.endoffile = "N"
  Open var.filename For Input As #var.stream Len = var.filebuffersize
  flg.streamopen = "Y"
  Exit Sub
openfilereaderror:
  var.errorcode = Err
  Call errorhandler
  resume '!!
End Sub

Public Sub checkfileexists()
  var.errorsection = "Checking File Exists"
  var.errordevice = var.filename
  If ini.errortrap = "Y" Then
    On Local Error GoTo checkfileexistserror
  End If
  flg.exists = "N"
  If Dir$(var.filename, 0) <> "" Then
    flg.exists = "Y"
  End If
  Exit Sub
checkfileexistserror:
  var.errorcode = Err
  Call errorhandler
End Sub

Public Sub getfreestream()
  var.errorsection = "Opening Free Data Stream"
  var.errordevice = ""
  If ini.errortrap = "Y" Then
    On Local Error GoTo getfreestreamerror
  End If
  var.stream = FreeFile
  Exit Sub
getfreestreamerror:
  var.errorcode = Err
  Call errorhandler
  resume '!!
End Sub

Sub closestream()
  If ini.errortrap = "Y" Then
    On Local Error GoTo closestreamerror
  End If
  var.errorsection = "Closing Stream"
  var.errordevice = ""
  flg.resumenext = "Y"
  Close #var.stream
  If flg.error = "Y" Then
    flg.error = "N"
    '!! Call unexpectederror
  End If
  flg.streamopen = "N"
  Exit Sub
closestreamerror:
  var.errorcode = Err
  Call errorhandler
  resume next
End Sub

Sub readlinefromfile()
  If ini.errortrap = "Y" Then
    On Local Error GoTo readlinefromfileerror
  End If
  If EOF(var.stream) Then
    flg.endoffile = "Y"
    Exit Sub
  End If
  Line Input #var.stream, tmp$
  message$ = tmp$
  Exit Sub
readlinefromfileerror:
  var.errorcode = Err
  Call errorhandler
  resume '!!
End Sub

Public Sub errorhandler()
  tmp$ = btrim$(var.errorsection)
  tmp2$ = btrim$(var.errordevice)
  If tmp2$ <> "" Then
    tmp$ = tmp$ + " (" + tmp2$ + ")"
  End If
  tmp$ = tmp$ + " : " + Str$(var.errorcode)
  tmp1% = MsgBox(tmp$, 0, "Error!")
  flg.error = "Y"
  If flg.resumenext = "Y" Then
    flg.resumenext = "N"
'    Resume Next
  Else
    flg.error = "N"
'    Resume
  End If
End Sub

Public Function btrim$(arg$)
  btrim$ = LTrim$(RTrim$(arg$))
End Function


// Find all Rare numbers with a digits. Nigel Galloway: September 18th., 2019.
let rareNums a=
  let tN=set[1L;4L;5L;6L;9L]
  let izPS g=let n=(float>>sqrt>>int64)g in n*n=g
  let n=[for n in [0..a/2-1] do yield ((pown 10L (a-n-1))-(pown 10L n))]|>List.rev
  let rec fN i g e=seq{match e with 0->yield g |e->for n in i do yield! fN [-9L..9L] (n::g) (e-1)}|>Seq.filter(fun g->let g=Seq.map2(*) n g|>Seq.sum in g>0L && izPS g)
  let rec fG n i g e l=seq{
    match l with
     h::t->for l in max 0L (0L-h)..min 9L (9L-h) do if e>1L||l=0L||tN.Contains((2L*l+h)%10L) then yield! fG (n+l*e+(l+h)*g) (i+l*g+(l+h)*e) (g/10L) (e*10L) t
    |_->if n>(pown 10L (a-1)) then for l in (if a%2=0 then [0L] else [0L..9L]) do let g=l*(pown 10L (a/2)) in if izPS (n+i+2L*g) then yield (i+g,n+g)}
  fN [0L..9L] [] (a/2) |> Seq.collect(List.rev >> fG 0L 0L (pown 10L (a-1)) 1L)


import "io" for File
import "/ioutil" for FileUtil

class Configuration {
    construct new(map) {
        _fullName       = map["fullName"]
        _favouriteFruit = map["favouriteFruit"]
        _needsPeeling   = map["needsPeeling"]
        _seedsRemoved   = map["seedsRemoved"]
        _otherFamily    = map["otherFamily"]
    }

    toString {
        return [
            "Full name       = %(_fullName)",
            "Favourite fruit = %(_favouriteFruit)",
            "Needs peeling   = %(_needsPeeling)",
            "Seeds removed   = %(_seedsRemoved)",
            "Other family    = %(_otherFamily)"
        ].join("\n")
    }
}

var commentedOut = Fn.new { |line| line.startsWith("#") || line.startsWith(";") }

var toMapEntry = Fn.new { |line|
    var ix = line.indexOf(" ")
    if (ix == -1) return MapEntry.new(line, "")
    return MapEntry.new(line[0...ix], line[ix+1..-1])
}

var fileName = "configuration.txt"
var lines = File.read(fileName).trimEnd().split(FileUtil.lineBreak)
var mapEntries = lines.map   { |line| line.trim() }.
                       where { |line| line != "" }.
                       where { |line| !commentedOut.call(line) }.
                       map   { |line| toMapEntry.call(line) }
var configurationMap = { "needsPeeling": false, "seedsRemoved": false }
for (me in mapEntries) {
    if (me.key == "FULLNAME") {
        configurationMap["fullName"] = me.value
    } else if (me.key == "FAVOURITEFRUIT") {
        configurationMap["favouriteFruit"] = me.value
    } else if (me.key == "NEEDSPEELING") {
        configurationMap["needsPeeling"] = true
    } else if (me.key == "OTHERFAMILY") {
        configurationMap["otherFamily"] = me.value.split(" , ").map { |s| s.trim() }.toList
    } else if (me.key == "SEEDSREMOVED") {
        configurationMap["seedsRemoved"] = true
    } else {
        System.print("Encountered unexpected key %(me.key)=%(me.value)")
    }
}
System.print(Configuration.new(configurationMap))


Function revn(n As ULongInt, nd As ULongInt) As ULongInt
    Dim As ULongInt r
    For i As UInteger = 1 To nd
        r = r * 10 + n Mod 10
        n = n \ 10
    Next i
    Return r
End Function

Dim As UInteger nd = 2, count, lim = 90, n = 20

Do
    n += 1
    Dim As ULongInt r = revn(n,nd)
    If r < n Then
        Dim As ULongInt s = n + r, d = n - r
        If nd And 1 Then
            If d Mod 1089 <> 0 Then GoTo jump
        Else
            If s Mod 121 <> 0 Then GoTo jump
        End If
        If Frac(Sqr(s)) = 0 And Frac(Sqr(d)) = 0 Then
            count += 1
            Print count; ": "; n
            If count = 5 Then Exit Do : End If
        End If
    End If
jump:
    If n = lim Then
        lim = lim * 10
        nd += 1
        n = (lim \ 9) * 2
    End If
Loop

Print
Print "Done"
Sleep


a = open("rosetta_read.cfg")

while(not eof(#a))
    FLAG = true : REMARK = false
    line input #a line$
    line$ = trim$(line$)
    ll = len(line$)
    c$ = left$(line$, 1)
    switch(c$)
        case "": case "#": REMARK = true : break
        case ";": FLAG = false : line$ = trim$(right$(line$, ll - 1)) : break
        default: MULTI = instr(line$, ",")
    end switch

    if not REMARK then
        GAP = instr(line$, "=") : if not GAP GAP = instr(line$, " ")
        if not GAP then
            print line$, " = ";
            if FLAG then print "true" else print "false" end if
        else
            if MULTI then
                count = 1 : SG = GAP
                repeat
                    print left$(line$, GAP - 1), "(", count, ") = ", trim$(mid$(line$, SG + 1, MULTI - SG - 1))
                    count = count + 1
                    SG = MULTI + 1 : MULTI = instr(line$, ",", SG)
                    if not MULTI MULTI = ll + 1
                until(SG > ll)
            else
                print left$(line$, GAP - 1), " = ", trim$(right$(line$, ll - GAP))
            end if
        end if
    end if
wend

close #a


fcn readConfigFile(config){  //--> read only dictionary
   conf:=Dictionary();
   foreach line in (config){
      line=line.strip();
      if (not line or "#"==line[0] or ";"==line[0]) continue;
      line=line.replace("\t"," ");
      n:=line.find(" ");
      if (Void==n) conf[line.toLower()]=True;  // eg NEEDSPEELING
      else{
	 key:=line[0,n].toLower(); line=line[n,*];
	 n=line.find(",");
	 if (Void!=n) conf[key]=line.split(",").apply("strip").filter();
	 else conf[key]=line;
      }
   }
   conf.makeReadOnly();
}

conf:=readConfigFile(File("foo.conf"));


package main

import (
    "fmt"
    "math"
    "sort"
    "time"
)

type term struct {
    coeff    uint64
    ix1, ix2 int8
}

const maxDigits = 19

func toUint64(digits []int8, reverse bool) uint64 {
    sum := uint64(0)
    if !reverse {
        for i := 0; i < len(digits); i++ {
            sum = sum*10 + uint64(digits[i])
        }
    } else {
        for i := len(digits) - 1; i >= 0; i-- {
            sum = sum*10 + uint64(digits[i])
        }
    }
    return sum
}

func isSquare(n uint64) bool {
    if 0x202021202030213&(1<<(n&63)) != 0 {
        root := uint64(math.Sqrt(float64(n)))
        return root*root == n
    }
    return false
}

func seq(from, to, step int8) []int8 {
    var res []int8
    for i := from; i <= to; i += step {
        res = append(res, i)
    }
    return res
}

func commatize(n uint64) string {
    s := fmt.Sprintf("%d", n)
    le := len(s)
    for i := le - 3; i >= 1; i -= 3 {
        s = s[0:i] + "," + s[i:]
    }
    return s
}

func main() {
    start := time.Now()
    pow := uint64(1)
    fmt.Println("Aggregate timings to process all numbers up to:")
    // terms of (n-r) expression for number of digits from 2 to maxDigits
    allTerms := make([][]term, maxDigits-1)
    for r := 2; r <= maxDigits; r++ {
        var terms []term
        pow *= 10
        pow1, pow2 := pow, uint64(1)
        for i1, i2 := int8(0), int8(r-1); i1 < i2; i1, i2 = i1+1, i2-1 {
            terms = append(terms, term{pow1 - pow2, i1, i2})
            pow1 /= 10
            pow2 *= 10
        }
        allTerms[r-2] = terms
    }
    //  map of first minus last digits for 'n' to pairs giving this value
    fml := map[int8][][]int8{
        0: {{2, 2}, {8, 8}},
        1: {{6, 5}, {8, 7}},
        4: {{4, 0}},
        6: {{6, 0}, {8, 2}},
    }
    // map of other digit differences for 'n' to pairs giving this value
    dmd := make(map[int8][][]int8)
    for i := int8(0); i < 100; i++ {
        a := []int8{i / 10, i % 10}
        d := a[0] - a[1]
        dmd[d] = append(dmd[d], a)
    }
    fl := []int8{0, 1, 4, 6}
    dl := seq(-9, 9, 1) // all differences
    zl := []int8{0}     // zero differences only
    el := seq(-8, 8, 2) // even differences only
    ol := seq(-9, 9, 2) // odd differences only
    il := seq(0, 9, 1)
    var rares []uint64
    lists := make([][][]int8, 4)
    for i, f := range fl {
        lists[i] = [][]int8{{f}}
    }
    var digits []int8
    count := 0

    // Recursive closure to generate (n+r) candidates from (n-r) candidates
    // and hence find Rare numbers with a given number of digits.
    var fnpr func(cand, di []int8, dis [][]int8, indices [][2]int8, nmr uint64, nd, level int)
    fnpr = func(cand, di []int8, dis [][]int8, indices [][2]int8, nmr uint64, nd, level int) {
        if level == len(dis) {
            digits[indices[0][0]] = fml[cand[0]][di[0]][0]
            digits[indices[0][1]] = fml[cand[0]][di[0]][1]
            le := len(di)
            if nd%2 == 1 {
                le--
                digits[nd/2] = di[le]
            }
            for i, d := range di[1:le] {
                digits[indices[i+1][0]] = dmd[cand[i+1]][d][0]
                digits[indices[i+1][1]] = dmd[cand[i+1]][d][1]
            }
            r := toUint64(digits, true)
            npr := nmr + 2*r
            if !isSquare(npr) {
                return
            }
            count++
            fmt.Printf("     R/N %2d:", count)
            ms := uint64(time.Since(start).Milliseconds())
            fmt.Printf("  %9s ms", commatize(ms))
            n := toUint64(digits, false)
            fmt.Printf("  (%s)\n", commatize(n))
            rares = append(rares, n)
        } else {
            for _, num := range dis[level] {
                di[level] = num
                fnpr(cand, di, dis, indices, nmr, nd, level+1)
            }
        }
    }

    // Recursive closure to generate (n-r) candidates with a given number of digits.
    var fnmr func(cand []int8, list [][]int8, indices [][2]int8, nd, level int)
    fnmr = func(cand []int8, list [][]int8, indices [][2]int8, nd, level int) {
        if level == len(list) {
            var nmr, nmr2 uint64
            for i, t := range allTerms[nd-2] {
                if cand[i] >= 0 {
                    nmr += t.coeff * uint64(cand[i])
                } else {
                    nmr2 += t.coeff * uint64(-cand[i])
                    if nmr >= nmr2 {
                        nmr -= nmr2
                        nmr2 = 0
                    } else {
                        nmr2 -= nmr
                        nmr = 0
                    }
                }
            }
            if nmr2 >= nmr {
                return
            }
            nmr -= nmr2
            if !isSquare(nmr) {
                return
            }
            var dis [][]int8
            dis = append(dis, seq(0, int8(len(fml[cand[0]]))-1, 1))
            for i := 1; i < len(cand); i++ {
                dis = append(dis, seq(0, int8(len(dmd[cand[i]]))-1, 1))
            }
            if nd%2 == 1 {
                dis = append(dis, il)
            }
            di := make([]int8, len(dis))
            fnpr(cand, di, dis, indices, nmr, nd, 0)
        } else {
            for _, num := range list[level] {
                cand[level] = num
                fnmr(cand, list, indices, nd, level+1)
            }
        }
    }

    for nd := 2; nd <= maxDigits; nd++ {
        digits = make([]int8, nd)
        if nd == 4 {
            lists[0] = append(lists[0], zl)
            lists[1] = append(lists[1], ol)
            lists[2] = append(lists[2], el)
            lists[3] = append(lists[3], ol)
        } else if len(allTerms[nd-2]) > len(lists[0]) {
            for i := 0; i < 4; i++ {
                lists[i] = append(lists[i], dl)
            }
        }
        var indices [][2]int8
        for _, t := range allTerms[nd-2] {
            indices = append(indices, [2]int8{t.ix1, t.ix2})
        }
        for _, list := range lists {
            cand := make([]int8, len(list))
            fnmr(cand, list, indices, nd, 0)
        }
        ms := uint64(time.Since(start).Milliseconds())
        fmt.Printf("  %2d digits:  %9s ms\n", nd, commatize(ms))
    }

    sort.Slice(rares, func(i, j int) bool { return rares[i] < rares[j] })
    fmt.Printf("\nThe rare numbers with up to %d digits are:\n", maxDigits)
    for i, rare := range rares {
        fmt.Printf("  %2d:  %25s\n", i+1, commatize(rare))
    }
}


rare     =:  ( [email protected]:] *. (nbrPs rr) ) b10
  np     =:  [email protected]:(-: |.)    NB. Not palindromic
  nbrPs  =:  > *. sdPs      NB. n is Bigger than R and the perfect square constraint is satisfied
    sdPs =:  + *.&:ps -     NB. n > rr and both their sum and difference are perfect squares
    ps   =:  0 = 1 | %:     NB. Perfect square (integral sqrt)
  rr     =:  10&#[email protected]:|.      NB. Do note we do reverse the digits twice (once here, once in np)
  b10    =:  10&#.^:_1      NB. Base 10 digits


import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class RareNumbers {
    public interface Consumer5<A, B, C, D, E> {
        void apply(A a, B b, C c, D d, E e);
    }

    public interface Consumer7<A, B, C, D, E, F, G> {
        void apply(A a, B b, C c, D d, E e, F f, G g);
    }

    public interface Recursable5<A, B, C, D, E> {
        void apply(A a, B b, C c, D d, E e, Recursable5<A, B, C, D, E> r);
    }

    public interface Recursable7<A, B, C, D, E, F, G> {
        void apply(A a, B b, C c, D d, E e, F f, G g, Recursable7<A, B, C, D, E, F, G> r);
    }

    public static <A, B, C, D, E> Consumer5<A, B, C, D, E> recurse(Recursable5<A, B, C, D, E> r) {
        return (a, b, c, d, e) -> r.apply(a, b, c, d, e, r);
    }

    public static <A, B, C, D, E, F, G> Consumer7<A, B, C, D, E, F, G> recurse(Recursable7<A, B, C, D, E, F, G> r) {
        return (a, b, c, d, e, f, g) -> r.apply(a, b, c, d, e, f, g, r);
    }

    private static class Term {
        long coeff;
        byte ix1, ix2;

        public Term(long coeff, byte ix1, byte ix2) {
            this.coeff = coeff;
            this.ix1 = ix1;
            this.ix2 = ix2;
        }
    }

    private static final int MAX_DIGITS = 16;

    private static long toLong(List<Byte> digits, boolean reverse) {
        long sum = 0;
        if (reverse) {
            for (int i = digits.size() - 1; i >= 0; --i) {
                sum = sum * 10 + digits.get(i);
            }
        } else {
            for (Byte digit : digits) {
                sum = sum * 10 + digit;
            }
        }
        return sum;
    }

    private static boolean isNotSquare(long n) {
        long root = (long) Math.sqrt(n);
        return root * root != n;
    }

    private static List<Byte> seq(byte from, byte to, byte step) {
        List<Byte> res = new ArrayList<>();
        for (byte i = from; i <= to; i += step) {
            res.add(i);
        }
        return res;
    }

    private static String commatize(long n) {
        String s = String.valueOf(n);
        int le = s.length();
        int i = le - 3;
        while (i >= 1) {
            s = s.substring(0, i) + "," + s.substring(i);
            i -= 3;
        }
        return s;
    }

    public static void main(String[] args) {
        final LocalDateTime startTime = LocalDateTime.now();
        long pow = 1L;
        System.out.println("Aggregate timings to process all numbers up to:");
        // terms of (n-r) expression for number of digits from 2 to maxDigits
        List<List<Term>> allTerms = new ArrayList<>();
        for (int i = 0; i < MAX_DIGITS - 1; ++i) {
            allTerms.add(new ArrayList<>());
        }
        for (int r = 2; r <= MAX_DIGITS; ++r) {
            List<Term> terms = new ArrayList<>();
            pow *= 10;
            long pow1 = pow;
            long pow2 = 1;
            byte i1 = 0;
            byte i2 = (byte) (r - 1);
            while (i1 < i2) {
                terms.add(new Term(pow1 - pow2, i1, i2));

                pow1 /= 10;
                pow2 *= 10;

                i1++;
                i2--;
            }
            allTerms.set(r - 2, terms);
        }
        //  map of first minus last digits for 'n' to pairs giving this value
        Map<Byte, List<List<Byte>>> fml = Map.of(
            (byte) 0, List.of(List.of((byte) 2, (byte) 2), List.of((byte) 8, (byte) 8)),
            (byte) 1, List.of(List.of((byte) 6, (byte) 5), List.of((byte) 8, (byte) 7)),
            (byte) 4, List.of(List.of((byte) 4, (byte) 0)),
            (byte) 6, List.of(List.of((byte) 6, (byte) 0), List.of((byte) 8, (byte) 2))
        );
        // map of other digit differences for 'n' to pairs giving this value
        Map<Byte, List<List<Byte>>> dmd = new HashMap<>();
        for (int i = 0; i < 100; ++i) {
            List<Byte> a = List.of((byte) (i / 10), (byte) (i % 10));

            int d = a.get(0) - a.get(1);
            dmd.computeIfAbsent((byte) d, k -> new ArrayList<>()).add(a);
        }
        List<Byte> fl = List.of((byte) 0, (byte) 1, (byte) 4, (byte) 6);
        List<Byte> dl = seq((byte) -9, (byte) 9, (byte) 1); //  all differences
        List<Byte> zl = List.of((byte) 0);                  // zero differences only
        List<Byte> el = seq((byte) -8, (byte) 8, (byte) 2); // even differences only
        List<Byte> ol = seq((byte) -9, (byte) 9, (byte) 2); //  odd differences only
        List<Byte> il = seq((byte) 0, (byte) 9, (byte) 1);
        List<Long> rares = new ArrayList<>();
        List<List<List<Byte>>> lists = new ArrayList<>();
        for (int i = 0; i < 4; ++i) {
            lists.add(new ArrayList<>());
        }
        for (int i = 0; i < fl.size(); ++i) {
            List<List<Byte>> temp1 = new ArrayList<>();
            List<Byte> temp2 = new ArrayList<>();
            temp2.add(fl.get(i));
            temp1.add(temp2);
            lists.set(i, temp1);
        }
        final AtomicReference<List<Byte>> digits = new AtomicReference<>(new ArrayList<>());
        AtomicInteger count = new AtomicInteger();

        // Recursive closure to generate (n+r) candidates from (n-r) candidates
        // and hence find Rare numbers with a given number of digits.
        Consumer7<List<Byte>, List<Byte>, List<List<Byte>>, List<List<Byte>>, Long, Integer, Integer> fnpr = recurse((cand, di, dis, indicies, nmr, nd, level, func) -> {
            if (level == dis.size()) {
                digits.get().set(indicies.get(0).get(0), fml.get(cand.get(0)).get(di.get(0)).get(0));
                digits.get().set(indicies.get(0).get(1), fml.get(cand.get(0)).get(di.get(0)).get(1));
                int le = di.size();
                if (nd % 2 == 1) {
                    le--;
                    digits.get().set(nd / 2, di.get(le));
                }
                for (int i = 1; i < le; ++i) {
                    digits.get().set(indicies.get(i).get(0), dmd.get(cand.get(i)).get(di.get(i)).get(0));
                    digits.get().set(indicies.get(i).get(1), dmd.get(cand.get(i)).get(di.get(i)).get(1));
                }
                long r = toLong(digits.get(), true);
                long npr = nmr + 2 * r;
                if (isNotSquare(npr)) {
                    return;
                }
                count.getAndIncrement();
                System.out.printf("     R/N %2d:", count.get());
                LocalDateTime checkPoint = LocalDateTime.now();
                long elapsed = Duration.between(startTime, checkPoint).toMillis();
                System.out.printf("  %9sms", elapsed);
                long n = toLong(digits.get(), false);
                System.out.printf("  (%s)\n", commatize(n));
                rares.add(n);
            } else {
                for (Byte num : dis.get(level)) {
                    di.set(level, num);
                    func.apply(cand, di, dis, indicies, nmr, nd, level + 1, func);
                }
            }
        });

        // Recursive closure to generate (n-r) candidates with a given number of digits.
        Consumer5<List<Byte>, List<List<Byte>>, List<List<Byte>>, Integer, Integer> fnmr = recurse((cand, list, indicies, nd, level, func) -> {
            if (level == list.size()) {
                long nmr = 0;
                long nmr2 = 0;
                List<Term> terms = allTerms.get(nd - 2);
                for (int i = 0; i < terms.size(); ++i) {
                    Term t = terms.get(i);
                    if (cand.get(i) >= 0) {
                        nmr += t.coeff * cand.get(i);
                    } else {
                        nmr2 += t.coeff * -cand.get(i);
                        if (nmr >= nmr2) {
                            nmr -= nmr2;
                            nmr2 = 0;
                        } else {
                            nmr2 -= nmr;
                            nmr = 0;
                        }
                    }
                }
                if (nmr2 >= nmr) {
                    return;
                }
                nmr -= nmr2;
                if (isNotSquare(nmr)) {
                    return;
                }
                List<List<Byte>> dis = new ArrayList<>();
                dis.add(seq((byte) 0, (byte) (fml.get(cand.get(0)).size() - 1), (byte) 1));
                for (int i = 1; i < cand.size(); ++i) {
                    dis.add(seq((byte) 0, (byte) (dmd.get(cand.get(i)).size() - 1), (byte) 1));
                }
                if (nd % 2 == 1) {
                    dis.add(il);
                }
                List<Byte> di = new ArrayList<>();
                for (int i = 0; i < dis.size(); ++i) {
                    di.add((byte) 0);
                }
                fnpr.apply(cand, di, dis, indicies, nmr, nd, 0);
            } else {
                for (Byte num : list.get(level)) {
                    cand.set(level, num);
                    func.apply(cand, list, indicies, nd, level + 1, func);
                }
            }
        });

        for (int nd = 2; nd <= MAX_DIGITS; ++nd) {
            digits.set(new ArrayList<>());
            for (int i = 0; i < nd; ++i) {
                digits.get().add((byte) 0);
            }
            if (nd == 4) {
                lists.get(0).add(zl);
                lists.get(1).add(ol);
                lists.get(2).add(el);
                lists.get(3).add(ol);
            } else if (allTerms.get(nd - 2).size() > lists.get(0).size()) {
                for (int i = 0; i < 4; ++i) {
                    lists.get(i).add(dl);
                }
            }
            List<List<Byte>> indicies = new ArrayList<>();
            for (Term t : allTerms.get(nd - 2)) {
                indicies.add(List.of(t.ix1, t.ix2));
            }
            for (List<List<Byte>> list : lists) {
                List<Byte> cand = new ArrayList<>();
                for (int i = 0; i < list.size(); ++i) {
                    cand.add((byte) 0);
                }
                fnmr.apply(cand, list, indicies, nd, 0);
            }
            LocalDateTime checkPoint = LocalDateTime.now();
            long elapsed = Duration.between(startTime, checkPoint).toMillis();
            System.out.printf("  %2d digits:  %9sms\n", nd, elapsed);
        }

        Collections.sort(rares);
        System.out.printf("\nThe rare numbers with up to %d digits are:\n", MAX_DIGITS);
        for (int i = 0; i < rares.size(); ++i) {
            System.out.printf("  %2d:  %25s\n", i + 1, commatize(rares.get(i)));
        }
    }
}


using Formatting, Printf

struct Term
    coeff::UInt64
    ix1::Int8
    ix2::Int8
end

function toUInt64(dgits, reverse)
    return reverse ? foldr((i, j) -> i + 10j, UInt64.(dgits)) :
                     foldl((i, j) -> 10i + j, UInt64.(dgits))
end

function issquare(n)
    if 0x202021202030213 & (1 << (UInt64(n) & 63)) != 0
        root = UInt64(floor(sqrt(n)))
        return root * root == n
    end
    return false
end

seq(from, to, step) = Int8.(collect(from:step:to))

commatize(n::Integer) = format(n, commas=true)

const verbose = true
const count = [0]

"""
Recursive closure to generate (n+r) candidates from (n-r) candidates
and hence find Rare numbers with a given number of digits.
"""
function fnpr(cand, di, dis, indices, nmr, nd, level, dgits, fml, dmd, start, rares, il)
    if level == length(dis)
        dgits[indices[1][1] + 1] = fml[cand[1]][di[1] + 1][1]
        dgits[indices[1][2] + 1] = fml[cand[1]][di[1] + 1][2]
        le = length(di)
        if nd % 2 == 1
            le -= 1
            dgits[nd ÷ 2 + 1] = di[le + 1]
        end
        for (i, d) in enumerate(di[2:le])
            dgits[indices[i+1][1] + 1] = dmd[cand[i+1]][d + 1][1]
            dgits[indices[i+1][2] + 1] = dmd[cand[i+1]][d + 1][2]
        end
        r = toUInt64(dgits, true)
        npr = nmr + 2 * r
        !issquare(npr) && return
        count[1] += 1
        verbose && @printf("     R/N %2d:", count[1])
        !verbose && print("$count rares\b\b\b\b\b\b\b\b\b")
        ms = UInt64(time() * 1000 - start)
        verbose && @printf("  %9s ms", commatize(Int(ms)))
        n = toUInt64(dgits, false)
        verbose && @printf("  (%s)\n", commatize(BigInt(n)))
        push!(rares, n)
    else
        for num in dis[level + 1]
            di[level + 1] = num
            fnpr(cand, di, dis, indices, nmr, nd, level + 1, dgits, fml, dmd, start, rares, il)
        end
    end
end # function fnpr

# Recursive closure to generate (n-r) candidates with a given number of digits.
# var fnmr func(cand []int8, list [][]int8, indices [][2]int8, nd, level int)
function fnmr(cand, list, indices, nd, level, allterms, fml, dmd, dgits, start, rares, il)
    if level == length(list)
        nmr, nmr2 = zero(UInt64), zero(UInt64)
        for (i, t) in enumerate(allterms[nd - 1])
            if cand[i] >= 0
                nmr += t.coeff * UInt64(cand[i])
            else
                nmr2 += t.coeff * UInt64(-cand[i])
                if nmr >= nmr2
                    nmr -= nmr2
                    nmr2 = zero(nmr2)
                else
                    nmr2 -= nmr
                    nmr = zero(nmr)
                end
            end
        end
        nmr2 >= nmr && return
        nmr -= nmr2
        !issquare(nmr) && return
        dis = [[seq(0, Int8(length(fml[cand[1]]) - 1), 1)] ;
            [seq(0, Int8(length(dmd[c]) - 1), 1) for c in cand[2:end]]]
        isodd(nd) && push!(dis, il)
        di = zeros(Int8, length(dis))
        fnpr(cand, di, dis, indices, nmr, nd, 0, dgits, fml, dmd, start, rares, il)
    else
        for num in list[level + 1]
            cand[level + 1] = num
            fnmr(cand, list, indices, nd, level + 1, allterms, fml, dmd, dgits, start, rares, il)
        end
    end
end # function fnmr

function findrare(maxdigits = 19)
    start = time() * 1000.0
    pow = one(UInt64)
    verbose && println("Aggregate timings to process all numbers up to:")
    # terms of (n-r) expression for number of digits from 2 to maxdigits
    allterms = Vector{Vector{Term}}()
    for r in 2:maxdigits
        terms = Term[]
        pow *= 10
        pow1, pow2, i1, i2 = pow, one(UInt64), zero(Int8), Int8(r - 1)
        while i1 < i2
            push!(terms, Term(pow1 - pow2, i1, i2))
            pow1, pow2, i1, i2 = pow1 ÷ 10, pow2 * 10, i1 + 1, i2 - 1
        end
        push!(allterms, terms)
    end
    #  map of first minus last digits for 'n' to pairs giving this value
    fml = Dict(
        0 => [2 => 2, 8 => 8],
        1 => [6 => 5, 8 => 7],
        4 => [4 => 0],
        6 => [6 => 0, 8 => 2],
    )
    # map of other digit differences for 'n' to pairs giving this value
    dmd = Dict{Int8, Vector{Vector{Int8}}}()
    for i in 0:99
        a = [Int8(i ÷ 10), Int8(i % 10)]
        d = a[1] - a[2]
        v = get!(dmd, d, [])
        push!(v, a)
    end
    fl = Int8[0, 1, 4, 6]
    dl = seq(-9, 9, 1)  # all differences
    zl = Int8[0]        # zero differences only
    el = seq(-8, 8, 2)  # even differences only
    ol = seq(-9, 9, 2)  # odd differences only
    il = seq(0, 9, 1)
    rares = UInt64[]
    lists = [[[f]] for f in fl]
    dgits = Int8[]
    count[1] = 0

    for nd = 2:maxdigits
        dgits = zeros(Int8, nd)
        if nd == 4
            push!(lists[1], zl)
            push!(lists[2], ol)
            push!(lists[3], el)
            push!(lists[4], ol)
        elseif length(allterms[nd - 1]) > length(lists[1])
            for i in 1:4
                push!(lists[i], dl)
            end
        end
        indices = Vector{Vector{Int8}}()
        for t in allterms[nd - 1]
            push!(indices, Int8[t.ix1, t.ix2])
        end
        for list in lists
            cand = zeros(Int8, length(list))
            fnmr(cand, list, indices, nd, 0, allterms, fml, dmd, dgits, start, rares, il)
        end
        ms = UInt64(time() * 1000 - start)
        verbose && @printf("  %2d digits:  %9s ms\n", nd, commatize(Int(ms)))
    end

    sort!(rares)
    @printf("\nThe rare numbers with up to %d digits are:\n", maxdigits)
    for (i, rare) in enumerate(rares)
        @printf("  %2d:  %25s\n", i, commatize(BigInt(rare)))
    end
end # findrare function

findrare()