import algorithm, sequtils, stats, tables

type
  Record = tuple[score: int; name: string]                  # Input data.
  Groups = OrderedTable[int, seq[string]]                   # Maps score to list of names.
  Rank = tuple[rank: int; name: string; score: int]         # Result.
  FractRank = tuple[rank: float; name: string; score: int]  # Result (fractional).

func cmp(a, b: (int, seq[string])): int =
  ## Comparison function needed to sort the groups.
  cmp(a[0], b[0])

func toGroups(records: openArray[Record]): Groups =
  ## Build a "Groups" table from the records.
  for record in records:
    result.mgetOrPut(record.score, @[]).add record.name
  # Sort the list of names by alphabetic order.
  for score in result.keys:
    sort(result[score])
  # Sort the groups by decreasing score.
  result.sort(cmp, Descending)

func standardRanks(groups: Groups): seq[Rank] =
  var rank = 1
  for score, names in groups.pairs:
    for name in names:
      result.add (rank, name, score)
    inc rank, names.len

func modifiedRanks(groups: Groups): seq[Rank] =
  var rank = 0
  for score, names in groups.pairs:
    inc rank, names.len
    for name in names:
      result.add (rank, name, score)

func denseRanks(groups: Groups): seq[Rank] =
  var rank = 0
  for score, names in groups.pairs:
    inc rank
    for name in names:
      result.add (rank, name, score)

func ordinalRanks(groups: Groups): seq[Rank] =
  var rank = 0
  for score, names in groups.pairs:
    for name in names:
      inc rank
      result.add (rank, name, score)

func fractionalRanks(groups: Groups): seq[FractRank] =
  var rank = 1
  for score, names in groups.pairs:
    let fRank = mean(toSeq(rank..(rank + names.high)))
    for name in names:
      result.add (fRank, name, score)
    inc rank, names.len

when isMainModule:
  const Data = [(44, "Solomon"), (42, "Jason"), (42, "Errol"),
                (41, "Garry"), (41, "Bernard"), (41, "Barry"), (39, "Stephen")]

  let groups = Data.toGroups()
  echo "Standard ranking:"
  for (rank, name, score) in groups.standardRanks():
    echo rank, ": ", name, " ", score

  echo()
  echo "Modified ranking:"
  for (rank, name, score) in groups.modifiedRanks():
    echo rank, ": ", name, " ", score

  echo()
  echo "Dense ranking:"
  for (rank, name, score) in groups.denseRanks():
    echo rank, ": ", name, " ", score

  echo()
  echo "Ordinal ranking:"
  for (rank, name, score) in groups.ordinalRanks():
    echo rank, ": ", name, " ", score

  echo()
  echo "Fractional ranking:"
  for (rank, name, score) in groups.fractionalRanks():
    echo rank, ": ", name, " ", score


my %scores = (
    'Solomon' => 44,
    'Jason'   => 42,
    'Errol'   => 42,
    'Garry'   => 41,
    'Bernard' => 41,
    'Barry'   => 41,
    'Stephen' => 39
);

sub tiers {
    my(%s) = @_; my(%h);
    push @{$h{$s{$_}}}, $_ for keys %s;
    @{\%h}{reverse sort keys %h};
}

sub standard {
    my(%s) = @_; my($result);
    my $rank = 1;
    for my $players (tiers %s) {
        $result .= "$rank " . join(', ', sort @$players) . "\n";
        $rank += @$players;
    }
    return $result;
}

sub modified {
    my(%s) = @_; my($result);
    my $rank = 0;
    for my $players (tiers %s) {
        $rank += @$players;
        $result .= "$rank " . join(', ', sort @$players) . "\n";
    }
    return $result;
}

sub dense {
    my(%s) = @_; my($n,$result);
    $result .= sprintf "%d %s\n", ++$n, join(', ', sort @$_) for tiers %s;
    return $result;
}

sub ordinal {
    my(%s) = @_; my($n,$result);
    for my $players (tiers %s) {
        $result .= sprintf "%d %s\n", ++$n, $_ for sort @$players;
    }
    return $result;
}

sub fractional {
    my(%s) = @_; my($result);
    my $rank = 1;
    for my $players (tiers %s) {
        my $beg = $rank;
        my $end = $rank += @$players;
        my $avg = 0;
        $avg += $_/@$players for $beg .. $end-1;
        $result .= sprintf "%3.1f %s\n", $avg, join ', ', sort @$players;
    }
    return $result;
}

print "Standard:\n"    .   standard(%scores) . "\n";
print "Modified:\n"    .   modified(%scores) . "\n";
print "Dense:\n"       .      dense(%scores) . "\n";
print "Ordinal:\n"     .    ordinal(%scores) . "\n";
print "Fractional:\n"  . fractional(%scores) . "\n";


with javascript_semantics
function ties(sequence scores)
    sequence t = {}, -- {start,num} pairs
             tdx = repeat(0,length(scores))
    integer last = -1
    for i=1 to length(scores) do
        integer curr = scores[i][1]
        if curr=last then
            t[$][2] += 1
        else
            t = append(t,{i,1})
        end if
        tdx[i] = length(t)
        last = curr
    end for
    -- eg {{{1,1},{2,2},{4,3},{7,1}},
    --     {1,2,2,3,3,3,4}}
    return {t,tdx}
end function

enum STANDARD,  -- eg {1,2,2,4,4,4,7}
     MODIFIED,  -- eg {1,3,3,6,6,6,7}
     DENSE,     -- (==tdx)
     ORDINAL,   -- eg {1,2,3,4,5,6,7}
     FRACTION,  -- {1,2.5,2.5,5,5,5,7}
     METHODS = $

function rank(integer i, method, sequence t, tdx)
    integer idx = tdx[i],
            {tx,tn} = t[idx]
    switch method
        case STANDARD: return tx
        case MODIFIED: return tx+tn-1
        case DENSE   : return idx
        case ORDINAL : return i
        case FRACTION: return tx+(tn-1)/2
    end switch
end function

constant scores = {{44, "Solomon"},
                   {42, "Jason"},
                   {42, "Errol"},
                   {41, "Garry"},
                   {41, "Bernard"},
                   {41, "Barry"},
                   {39, "Stephen"}}

sequence {t,tdx} = ties(scores)
printf(1," score name     standard modified dense ordinal fractional\n")
for i=1 to length(scores) do
    sequence ranks = repeat(0,METHODS)
    for method=1 to METHODS do
        ranks[method] = rank(i,method,t,tdx)
    end for
    printf(1,"%5d  %-7s ",scores[i])
    printf(1,"%6g %8g %6g %6g %9g\n",ranks)
end for


function Get-Ranking
{
    [CmdletBinding(DefaultParameterSetName="Standard")]
    [OutputType([PSCustomObject])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [string]
        $InputObject,

        [Parameter(Mandatory=$false,
                   ParameterSetName="Standard")]
        [switch]
        $Standard,

        [Parameter(Mandatory=$false,
                   ParameterSetName="Modified")]
        [switch]
        $Modified,

        [Parameter(Mandatory=$false,
                   ParameterSetName="Dense")]
        [switch]
        $Dense,

        [Parameter(Mandatory=$false,
                   ParameterSetName="Ordinal")]
        [switch]
        $Ordinal,

        [Parameter(Mandatory=$false,
                   ParameterSetName="Fractional")]
        [switch]
        $Fractional
    )

    Begin
    {
        function Get-OrdinalRank ([PSCustomObject[]]$Values)
        {
            for ($i = 0; $i -lt $Values.Count; $i++)
            {
                $Values[$i].Rank = $i + 1
            }

            $Values
        }

        function Get-Rank ([PSCustomObject[]]$Scores)
        {
            foreach ($score in $Scores)
            {
                $score.Group | ForEach-Object {$_.Rank = $score.Rank}
            }

            $Scores.Group
        }

        function New-Competitor ([string]$Name, [int]$Score, [int]$Rank = 0)
        {
            [PSCustomObject]@{
                Name  = $Name
                Score = $Score
                Rank  = $Rank
            }
        }

        $competitors = @()
        $scores = @()
    }
    Process
    {
        @($input) | ForEach-Object {$competitors += New-Competitor -Name $_.Split()[1] -Score $_.Split()[0]}
    }
    End
    {
        $scores = $competitors |
            Sort-Object   -Property Score -Descending |
            Group-Object  -Property Score |
            Select-Object -Property @{Name="Score"; Expression={[int]$_.Name}}, @{Name="Rank"; Expression={0}}, Count, Group

        switch ($PSCmdlet.ParameterSetName)
        {
            "Standard"
            {
                $rank = 1

                for ($i = 0; $i -lt $scores.Count; $i++)
                {
                    $scores[$i].Rank = $rank
                    $rank += $scores[$i].Count
                }

                Get-Rank $scores
            }
            "Modified"
            {
                $rank = 0

                foreach ($score in $scores)
                {
                    $rank = $score.Count + $rank
                    $score.Rank = $rank
                }

                Get-Rank $scores
            }
            "Dense"
            {
                for ($i = 0; $i -lt $scores.Count; $i++)
                {
                    $scores[$i].Rank = $i + 1
                }

                Get-Rank $scores
            }
            "Ordinal"
            {
                Get-OrdinalRank $competitors
            }
            "Fractional"
            {
                Get-OrdinalRank $competitors | Group-Object -Property Score | ForEach-Object {
                    if ($_.Count -gt 1)
                    {
                        $rank = ($_.Group.Rank | Measure-Object -Average).Average

                        foreach ($competitor in $_.Group)
                        {
                            $competitor.Rank = $rank
                        }
                    }
                }

                $competitors
            }
        }
    }
}


def mc_rank(iterable, start=1):
    """Modified competition ranking"""
    lastresult, fifo = None, []
    for n, item in enumerate(iterable, start-1):
        if item[0] == lastresult:
            fifo += [item]
        else:
            while fifo:
                yield n, fifo.pop(0)
            lastresult, fifo = item[0], fifo + [item]
    while fifo:
        yield n+1, fifo.pop(0)

def sc_rank(iterable, start=1):
    """Standard competition ranking"""
    lastresult, lastrank = None, None
    for n, item in enumerate(iterable, start):
        if item[0] == lastresult:
            yield lastrank, item
        else:
            yield n, item
            lastresult, lastrank = item[0], n

def d_rank(iterable, start=1):
    """Dense ranking"""
    lastresult, lastrank = None, start - 1,
    for item in iterable:
        if item[0] == lastresult:
            yield lastrank, item
        else:
            lastresult, lastrank = item[0], lastrank + 1
            yield lastrank, item

def o_rank(iterable, start=1):
    """Ordinal  ranking"""
    yield from enumerate(iterable, start)

def f_rank(iterable, start=1):
    """Fractional ranking"""
    last, fifo = None, []
    for n, item in enumerate(iterable, start):
        if item[0] != last:
            if fifo:
                mean = sum(f[0] for f in fifo) / len(fifo)
                while fifo:
                    yield mean, fifo.pop(0)[1]
        last = item[0]
        fifo.append((n, item))
    if fifo:
        mean = sum(f[0] for f in fifo) / len(fifo)
        while fifo:
            yield mean, fifo.pop(0)[1]

if __name__ == '__main__':
    scores = [(44, 'Solomon'),
              (42, 'Jason'),
              (42, 'Errol'),
              (41, 'Garry'),
              (41, 'Bernard'),
              (41, 'Barry'),
              (39, 'Stephen')]

    print('\nScores to be ranked (best first):')
    for s in scores:
        print('        %2i %s' % (s ))
    for ranker in [sc_rank, mc_rank, d_rank, o_rank, f_rank]:
        print('\n%s:' % ranker.__doc__)
        for rank, score in ranker(scores):
            print('  %3g, %r' % (rank, score))


#lang racket
;; Tim-brown 2014-09-11

;; produces a ranking according to ranking function: rfn
;; INPUT:
;;  lst : (list (score . details))
;;  rfn : (length-scores)
;;         -> (values
;;              ranks-added                  ; how many ranks to add for the next iteration
;;              (idx . rest -> rank-offset)) ; function that produces the rank for the idx^th element
;;                                           ; in the scoring (arity must be >= 1)
(define (rank-list all-scores rfn)
  (let loop ((rank-0 0) (lst (sort all-scores > #:key car)) (acc empty))
    (cond
      [(null? lst) acc]
      [else
       (define 1st-score (caar lst))
       (define (ties-with-1st? cand) (= (car cand) 1st-score))
       (define-values (tied unranked) (splitf-at lst ties-with-1st?))
       ;; all ranking functions should properly handle a singleton tied list
       (define tied-len (length tied))
       (define tied? (> tied-len 1))
       (define-values (ranks-added rank-offset-fn) (rfn tied-len))
       (define ranked-tied (for/list ((t (in-list tied)) (i (in-naturals 1)))
                             (list* tied? (+ rank-0 (rank-offset-fn i)) t)))
       (loop (+ ranks-added rank-0) unranked (append acc ranked-tied))])))

;; Ties share what would have been their first ordinal number
(define (rank-function:Standard l)
  (values l (thunk* 1)))

;; Ties share what would have been their last ordinal number
(define (rank-function:Modified l)
  (values l (thunk* l)))

;; Ties share the next available integer
(define (rank-function:Dense l)
  (values 1 (thunk* 1)))

;; Competitors take the next available integer. Ties are not treated otherwise
(define (rank-function:Ordinal l)
  (values l (λ (n . _) n)))

;; Ties share the mean of what would have been their ordinal numbers
(define (rank-function:Fractional l)
  (values l (thunk* (/ (+ l 1) 2))))

(define score-board
  '((44 . Solomon)
    (42 . Jason)
    (42 . Errol)
    (41 . Garry)
    (41 . Bernard)
    (41 . Barry)
    (39 . Stephen)))

(define format-number
  (match-lambda
    [(? integer? i) i]
    [(and f (app numerator n) (app denominator d))
     (define-values (q r) (quotient/remainder n d))
     (format "~a ~a/~a" q r d)]))

(for ((fn (list
           rank-function:Standard
           rank-function:Modified
           rank-function:Dense
           rank-function:Ordinal
           rank-function:Fractional)))
  (printf "Function: ~s~%" fn)
  (for ((r (in-list (rank-list score-board fn))))
    (printf "~a ~a\t~a\t~a~%"
            (if (car r) "=" " ")
            (format-number (cadr r))
            (caddr r)
            (cdddr r)))
  (newline))


my @scores =
    Solomon => 44,
    Jason   => 42,
    Errol   => 42,
    Garry   => 41,
    Bernard => 41,
    Barry   => 41,
    Stephen => 39;

sub tiers (@s) { @s.classify(*.value).pairs.sort.reverse.map: { .value».key } }

sub standard (@s) {
    my $rank = 1;
    gather for tiers @s -> @players {
	take $rank => @players;
	$rank += @players;
    }
}

sub modified (@s) {
    my $rank = 0;
    gather for tiers @s -> @players {
	$rank += @players;
	take $rank => @players;
    }
}

sub dense (@s) { tiers(@s).map: { ++$_ => @^players } }

sub ordinal (@s) { @s.map: ++$_ => *.key }

sub fractional (@s) {
    my $rank = 1;
    gather for tiers @s -> @players {
	my $beg = $rank;
	my $end = $rank += @players;
	take [+]($beg ..^ $end) / @players => @players;
    }
}

say   "Standard:";   .perl.say for   standard @scores;
say "\nModified:";   .perl.say for   modified @scores;
say "\nDense:";      .perl.say for      dense @scores;
say "\nOrdinal:";    .perl.say for    ordinal @scores;
say "\nFractional:"; .perl.say for fractional @scores;


/**************************
44 Solomon  1 1 1 1 1
42 Jason    2 3 2 2 2.5
42 Errol    2 3 2 3 2.5
41 Garry    4 6 3 4 5
41 Bernard  4 6 3 5 5
41 Barry    4 6 3 6 5
39 Stephen  7 7 4 7 7
**************************/
Do i=1 To 7
  Parse Value sourceline(i+1) With rank.i name.i .
  /* say rank.i name.i */
  End
pool=0
crank=0
Do i=1 To 7
  If rank.i<>crank Then Do
    pool=pool+1
    lo.pool=i
    hi.pool=i
    n.pool=1
    ii.pool=i
    End
  Else Do
    n.pool=n.pool+1
    hi.pool=i
    ii.pool=ii.pool+i
    End
  crank=rank.i
  pool.i=pool
  End
/*
Do j=1 To pool
  Say 'pool' j n.j lo.j hi.j
  End
*/
cp=0
r=0
cnt.=0
Do i=1 To 7
  p=pool.i
  If p<>cp Then
    r=r+1
  res=rank.i left(name.i,8) lo.p hi.p r i ii.p/n.p
  If res=sourceline(i+1) Then cnt.ok=cnt.ok+1
  Say res
  cp=p
  End
Say cnt.ok 'correct lines'


ar = "44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen".lines.map{|line| line.split}
grouped = ar.group_by{|pair| pair.shift.to_i}
s_rnk = 1
m_rnk = o_rnk = 0
puts "stand.\tmod.\tdense\tord.\tfract."

grouped.each.with_index(1) do |(score, names), d_rnk|
  m_rnk += names.flatten!.size
  f_rnk = (s_rnk + m_rnk)/2.0
  names.each do |name|
    o_rnk += 1
    puts "#{s_rnk}\t#{m_rnk}\t#{d_rnk}\t#{o_rnk}\t#{f_rnk.to_s.sub(".0","")}\t#{score} #{name}"
  end
  s_rnk += names.size
end


object RankingMethods extends App {
    case class Score(score: Int, name: String) // incoming data
    case class Rank[Precision](rank: Precision, names: List[String]) // outgoing results (can be int or double)
    case class State[Precision](n: Int, done: List[Rank[Precision]]) { // internal state, no mutable variables
        def next(n: Int, next: Rank[Precision]) = State(n, next :: done)
    }
    def grouped[Precision](list: List[Score]) = // group names together by score, with highest first
        (scala.collection.immutable.TreeMap[Int, List[Score]]() ++ list.groupBy(-_.score))
        .values.map(_.map(_.name)).foldLeft(State[Precision](1, Nil)) _

    // Ranking methods:

    def rankStandard(list: List[Score]) =
        grouped[Int](list){case (state, names) => state.next(state.n+names.length, Rank(state.n, names))}.done.reverse

    def rankModified(list: List[Score]) =
        rankStandard(list).map(r => Rank(r.rank+r.names.length-1, r.names))

    def rankDense(list: List[Score]) =
        grouped[Int](list){case (state, names) => state.next(state.n+1, Rank(state.n, names))}.done.reverse

    def rankOrdinal(list: List[Score]) =
        list.zipWithIndex.map{case (score, n) => Rank(n+1, List(score.name))}

    def rankFractional(list: List[Score]) =
        rankStandard(list).map(r => Rank((2*r.rank+r.names.length-1.0)/2, r.names))

    // Tests:

    def parseScores(s: String) = s split "\\s+" match {case Array(s,n) => Score(s.toInt, n)}
    val test = List("44 Solomon", "42 Jason", "42 Errol", "41 Garry", "41 Bernard", "41 Barry", "39 Stephen").map(parseScores)

    println("Standard:")
    println(rankStandard(test) mkString "\n")
    println("\nModified:")
    println(rankModified(test) mkString "\n")
    println("\nDense:")
    println(rankDense(test) mkString "\n")
    println("\nOrdinal:")
    println(rankOrdinal(test) mkString "\n")
    println("\nFractional:")
    println(rankFractional(test) mkString "\n")

}


var scores = [
    Pair(Solomon => 44),
    Pair(Jason   => 42),
    Pair(Errol   => 42),
    Pair(Garry   => 41),
    Pair(Bernard => 41),
    Pair(Barry   => 41),
    Pair(Stephen => 39),
]

func tiers(s) {
    s.group_by { .value }.kv.sort.flip.map { .value.map{.key} }
}

func standard(s) {
    var rank = 1
    gather {
        for players in tiers(s) {
            take(Pair(rank, players))
            rank += players.len
        }
    }
}

func modified(s) {
    var rank = 0
    gather {
        for players in tiers(s) {
            rank += players.len
            take(Pair(rank, players))
        }
    }
}

func dense(s) {
    tiers(s).map_kv { |k,v| Pair(k+1, v) }
}

func ordinal(s) {
    s.map_kv { |k,v| Pair(k+1, v.key) }
}

func fractional(s) {
    var rank = 1
    gather {
        for players in tiers(s) {
            var beg = rank
            var end = (rank += players.len)
            take(Pair(sum(beg ..^ end) / players.len, players))
        }
    }
}

func display(r) {
    say r.map {|a| '%3s : %s' % a... }.join("\n")
}

say   "Standard:";   display(  standard(scores))
say "\nModified:";   display(  modified(scores))
say "\nDense:";      display(     dense(scores))
say "\nOrdinal:";    display(   ordinal(scores))
say "\nFractional:"; display(fractional(scores))


proc rank {rankingMethod sortedList} {
    # Extract the groups in the data (this is pointless for ordinal...)
    set s [set group [set groups {}]]
    foreach {score who} $sortedList {
	if {$score != $s} {
	    lappend groups [llength $group]
	    set s $score
	    set group {}
	}
	lappend group $who
    }
    lappend groups [llength $group]
    # Construct the rankings; note that we have a zero-sized leading group
    set n 1; set m 0
    foreach g $groups {
	switch $rankingMethod {
	    standard {
		lappend result {*}[lrepeat $g $n]
		incr n $g
	    }
	    modified {
		lappend result {*}[lrepeat $g [incr m $g]]
	    }
	    dense {
		lappend result {*}[lrepeat $g $m]
		incr m
	    }
	    ordinal {
		for {set i 0} {$i < $g} {incr i} {
		    lappend result [incr m]
		}
	    }
	    fractional {
		set val [expr {($n + [incr n $g] - 1) / 2.0}]
		lappend result {*}[lrepeat $g [format %g $val]]
	    }
	}
    }
    return $result
}

set data {
    44 Solomon
    42 Jason
    42 Errol
    41 Garry
    41 Bernard
    41 Barry
    39 Stephen
}
foreach method {standard modified dense ordinal fractional} {
    puts "Using method '$method'...\n  Rank\tScore\tWho"
    foreach rank [rank $method $data] {score who} $data {
	puts "  $rank\t$score\t$who"
    }
}


LET n = 7
DIM puntos(7), ptosnom(7), nombre$(7)

SUB MostarTabla
    FOR i = 1 to n
        PRINT str$(ptosnom(i)); "   "; puntos(i); "  "; nombre$(i)
    NEXT i
    PRINT
END SUB

PRINT "Puntuaciones a clasificar (mejores primero):"
FOR i = 1 to n
    READ puntos(i), nombre$(i)
    PRINT "      "; puntos(i); "  "; nombre$(i)
NEXT i

PRINT
PRINT "--- Standard ranking ---"
LET ptosnom(1) = 1
FOR i = 2 to n
NEXT i
CALL MostarTabla

PRINT "--- Modified ranking ---"
LET ptosnom(n) = n
FOR i = n-1 to 1 step -1
    IF puntos(i) = puntos(i+1) then LET ptosnom(i) = ptosnom(i+1) else LET ptosnom(i) = i
NEXT i
CALL MostarTabla

PRINT "--- Ordinal ranking ---"
FOR i = 1 to n
    LET ptosnom(i) = i
NEXT i
CALL MostarTabla

PRINT "--- Fractional ranking ---"
LET i = 1
LET j = 2
DO
   IF j <= n then
      IF (puntos(j-1) = puntos(j)) then
         LET j = j + 1
      END IF
   END IF

   FOR k = i to j-1
       LET ptosnom(k) = (i+j-1) / 2
   NEXT k
   LET i = j
   LET j = j + 1
LOOP UNTIL i > n
CALL MOSTARTABLA

DATA 44, "Solomon", 42, "Jason", 42, "Errol", 41, "Garry", 41, "Bernard", 41, "Barry", 39, "Stephen"
END


#DEFINE CTAB CHR(9)
#DEFINE CRLF CHR(13) + CHR(10)
LOCAL lcTxt As String, i As Integer
CLOSE DATABASES ALL
SET SAFETY OFF
CLEAR
CREATE CURSOR scores (score I, name V(8), rownum I AUTOINC)
INDEX ON score TAG score COLLATE "Machine"
SET ORDER TO 0
INSERT INTO scores (score, name) VALUES (44, "Solomon")
INSERT INTO scores (score, name) VALUES (42, "Jason")
INSERT INTO scores (score, name) VALUES (42, "Errol")
INSERT INTO scores (score, name) VALUES (41, "Garry")
INSERT INTO scores (score, name) VALUES (41, "Bernard")
INSERT INTO scores (score, name) VALUES (41, "Barry")
INSERT INTO scores (score, name) VALUES (39, "Stephen")

CREATE CURSOR ranks (sc_rank I, mod_rank I, dense I, ordinal I, fractional B(1), score I, name V(8))
INDEX ON score TAG score COLLATE "Machine"
SET ORDER TO 0
APPEND FROM DBF("scores") FIELDS score, name
Std_Comp()
Modified()
Dense()
Ordinal()
Fractional()
COPY TO ranks.txt TYPE DELIMITED WITH TAB
lcTxt = ""
FOR i = 1 TO FCOUNT()
    lcTxt = lcTxt + FIELD(i) + CTAB
ENDFOR
lcTxt = LEFT(lcTxt, LEN(lcTxt) - 1) + CRLF + FILETOSTR("ranks.txt")
STRTOFILE(lcTxt, "ranks.txt")
MODIFY FILE ranks.txt
SET SAFETY ON

FUNCTION ScoreGroup(aa)
LOCAL n As Integer
SELECT score, COUNT(*) As num FROM scores ;
GROUP BY score ORDER BY score DESC INTO ARRAY aa
n = _TALLY
RETURN n
ENDFUNC

PROCEDURE Std_Comp
LOCAL n, i, nn
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS sc_rank ALL
nn = ScoreGroup(@a)
n = 1
FOR i = 1 TO nn
    REPLACE sc_rank WITH n FOR score = a[i,1]
    n = n + a[i,2]
ENDFOR
ENDPROC

PROCEDURE Modified
LOCAL n, i, nn
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS mod_rank ALL
nn = ScoreGroup(@a)
n = 0
FOR i = 1 TO nn
    n = n + a[i,2]
    REPLACE mod_rank WITH n FOR score = a[i,1]
ENDFOR
ENDPROC

PROCEDURE Dense
LOCAL n, i, nn
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS dense ALL
nn = ScoreGroup(@a)
SELECT ranks
n = 0
FOR i = 1 TO nn
    n = n + a[i,2]
    REPLACE dense WITH i FOR score = a[i,1]
ENDFOR
ENDPROC

PROCEDURE Ordinal
SELECT ranks
BLANK FIELDS ordinal ALL
REPLACE ordinal WITH RECNO() ALL
ENDPROC

PROCEDURE Fractional
LOCAL i As Integer, nn As Integer
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS fractional ALL
SELECT CAST(AVG(rownum) As B(1)), score FROM scores ;
GROUP BY score ORDER BY score DESC INTO ARRAY a
nn = _TALLY
FOR i = 1 TO nn
    REPLACE fractional WITH a[i,1] FOR score = a[i,2]
ENDFOR
ENDPROC


import "/math" for Nums
import "/fmt" for Fmt

/* all ranking functions assume the array of Pairs is non-empty and already sorted
   by decreasing order of scores and then, if the scores are equal, by reverse
   alphabetic order of names
*/

var standardRanking = Fn.new { |scores|
    var rankings = List.filled(scores.count, 0)
    rankings[0] = 1
    for (i in 1...scores.count) {
        rankings[i] = (scores[i][0] == scores[i-1][0]) ? rankings[i-1] : i + 1
    }
    return rankings
}

var modifiedRanking = Fn.new { |scores|
    var rankings = List.filled(scores.count, 0)
    rankings[0] = 1
    for (i in 1...scores.count) {
        rankings[i] =  i + 1
        var currScore = scores[i][0]
        for (j in i-1..0) {
            if (currScore != scores[j][0]) break
            rankings[j] = i + 1
        }
    }
    return rankings
}

var denseRanking = Fn.new { |scores|
    var rankings = List.filled(scores.count, 0)
    rankings[0] = 1
    var prevRanking = 1
    for (i in 1...scores.count) {
        rankings[i] = (scores[i][0] == scores[i-1][0]) ? prevRanking : (prevRanking = prevRanking+1)
    }
    return rankings
}

var ordinalRanking = Fn.new { |scores| (1..scores.count).toList }

var fractionalRanking = Fn.new { |scores|
    var rankings = List.filled(scores.count, 0)
    rankings[0] = 1
    for (i in 1...scores.count) {
        var k = i
        var currScore = scores[i][0]
        for (j in i-1..0) {
            if (currScore != scores[j][0]) break
            k = j
        }
        var avg = Nums.mean(k..i) + 1
        for (m in k..i) rankings[m] = avg
    }
    return rankings
}

var printRankings = Fn.new { |title, rankings, scores|
    System.print(title + ":")
    for (i in 0...rankings.count) {
        System.print("%(rankings[i])  %(scores[i][0]) %(scores[i][1])")
    }
    System.print()
}

var printFractionalRankings = Fn.new { |title, rankings, scores|
    System.print(title + ":")
    for (i in 0...rankings.count) {
        Fmt.print("$3.2f  $d $s", rankings[i], scores[i][0], scores[i][1])
    }
    System.print()
}

var scores = [[44, "Solomon"], [42, "Jason"], [42, "Errol"],  [41, "Garry"],
              [41, "Bernard"], [41, "Barry"], [39, "Stephen"]]
printRankings.call("Standard ranking", standardRanking.call(scores), scores)
printRankings.call("Modified ranking", modifiedRanking.call(scores), scores)
printRankings.call("Dense ranking", denseRanking.call(scores), scores)
printRankings.call("Ordinal ranking", ordinalRanking.call(scores), scores)
printFractionalRankings.call("Fractional ranking", fractionalRanking.call(scores), scores)


n = 7
dim puntos(7), ptosnom(7), nombre$(7)

sub MostarTabla()
    for i = 1 to n
        print str$(ptosnom(i)), "   ", puntos(i), "  ", nombre$(i)
    next i
    print
end sub

print "Puntuaciones a clasificar (mejores primero):"
for i = 1 to n
    read puntos(i), nombre$(i)
    print "      ", puntos(i), "  ", nombre$(i)
next i

print
print "--- Standard ranking ---"
ptosnom(1) = 1
for i = 2 to n
    if puntos(i) = puntos(i-1) then ptosnom(i) = ptosnom(i-1) else ptosnom(i) = i : fi
next i
MostarTabla()

print "--- Modified ranking ---"
ptosnom(n) = n
for i = n-1 to 1 step -1
    if puntos(i) = puntos(i+1) then ptosnom(i) = ptosnom(i+1) else ptosnom(i) = i : fi
next i
MostarTabla()

print "--- Ordinal ranking ---"
for i = 1 to n
    ptosnom(i) = i
next i
MostarTabla()

print "--- Fractional ranking ---"
i = 1
j = 2
repeat
    if j <= n then
        if (puntos(j-1) = puntos(j)) then j = j + 1 : fi
    end if

    for k = i to j-1
        ptosnom(k) = (i+j-1) / 2
    next k
    i = j
    j = j + 1
until i > n
MostarTabla()

data 44, "Solomon", 42, "Jason", 42, "Errol", 41, "Garry", 41, "Bernard", 41, "Barry", 39, "Stephen"
end


fcn group(scores){ // group like scores into one list --> list of lists
   sink:=List();
   scores.reduce('wrap(ps,sn,buf){
      if(sn[0]!=ps){ sink.append(buf.copy()); buf.clear(); }
      buf+sn;
      sn[0];
   },scores[0][0],buf:=List());
   sink.append(buf);
}
fcn print(list,rank){
   list.apply2('wrap(sn){ "%2s: %s (%d)".fmt(rank,sn[1],sn[0]):println(_); });
}
fcn rankViaStandard(scores){
   rank:=1;
   foreach group in (group(scores)){ print(group,rank); rank+=group.len(); }
}
fcn rankViaModified(scores){
   rank:=0;
   foreach group in (group(scores)){ rank+=group.len(); print(group,rank); }
}
fcn rankViaDense(scores){
   rank:=1;
   foreach group in (group(scores)){ print(group,rank); rank+=1; }
}
fcn rankViaOrdinal(scores){
   scores.apply2('wrap(sn,rr){ "%2s: %s (%d)".fmt(rr.inc(),sn[1],sn[0]):println(_); },Ref(1));
}
fcn rankViaFractional(scores){
   rank:=1;
   foreach group in (group(scores)){
      n:=group.len(); r:=rank.reduce(n,'+,0.0)/n; rank+=n;
      print(group,"%5.2f".fmt(r));
   }
}


F readconf(fname)
   [String = String] ret
   L(=line) File(fname).read_lines()
      line = line.trim((‘ ’, "\t", "\r", "\n"))
      I line == ‘’ | line.starts_with(‘#’)
         L.continue

      V boolval = 1B
      I line.starts_with(‘;’)
         line = line.ltrim(‘;’)

         I line.split_py().len != 1
            L.continue
         boolval = 0B

      V bits = line.split(‘ ’, 2, group_delimiters' 1B)
      String k, v
      I bits.len == 1
         k = bits[0]
         v = String(boolval)
      E
         (k, v) = bits
      ret[k.lowercase()] = v
   R ret

V conf = readconf(‘config.txt’)
L(k, v) sorted(conf.items())
   print(k‘ = ’v)


with Config; use Config;
with Ada.Text_IO; use Ada.Text_IO;

procedure Rosetta_Read_Cfg is
  cfg: Configuration:= Init("rosetta_read.cfg", Case_Sensitive => False, Variable_Terminator => ' ');
  fullname       : String  := cfg.Value_Of("*", "fullname");
  favouritefruit : String  := cfg.Value_Of("*", "favouritefruit");
  needspeeling   : Boolean := cfg.Is_Set("*", "needspeeling");
  seedsremoved   : Boolean := cfg.Is_Set("*", "seedsremoved");
  otherfamily    : String  := cfg.Value_Of("*", "otherfamily");
begin
  Put_Line("fullname = "       & fullname);
  Put_Line("favouritefruit = " & favouritefruit);
  Put_Line("needspeeling = "   & Boolean'Image(needspeeling));
  Put_Line("seedsremoved = "   & Boolean'Image(seedsremoved));
  Put_Line("otherfamily = "    & otherfamily);
end;


record r, s;
integer c;
file f;
list l;
text an, d, k;

an = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

f.affix("tmp/config");

while ((c = f.peek) ^ -1) {
    integer removed;

    f.side(" \t\r");
    c = f.peek;
    removed = c == ';';
    if (removed) {
        f.pick;
        f.side(" \t\r");
        c = f.peek;
    }
    c = place(an, c);
    if (-1 < c && c < 52) {
        f.near(an, k);
        if (removed) {
            r[k] = "false";
        } else {
            f.side(" \t\r");
            if (f.peek == '=') {
                f.pick;
                f.side(" \t\r");
            }
            f.ever(",#\n", d);
            d = bb_drop(d, " \r\t");
            if (f.peek != ',') {
                r[k] = ~d ? d : "true";
            } else {
                f.news(l, 0, 0, ",");
                lf_push(l, d);
                for (c, d in l) {
                    l[c] = bb_drop(d, " \r\t").bf_drop(" \r\t").string;
                }
                s.put(k, l);
                f.seek(-1, SEEK_CURRENT);
            }
        }
    }

    f.slip;
}

r.wcall(o_, 0, 2, ": ", "\n");

for (k, l in s) {
    o_(k, ": ");
    l.ucall(o_, 0, ", ");
    o_("\n");
}


parseConfig: function [f][
    lines: split.lines read f
    lines: select map lines 'line [strip replace line {/[#;].*/} ""]
                            'line [not? empty? line]
    result: #[]

    fields: loop lines 'line [
        field: first match line {/^[A-Z]+/}
        rest: strip replace line field ""
        parts: select map split.by:"," rest => strip 'part -> not? empty? part

        val: null
        case [(size parts)]
            when? [= 0] -> val: true
            when? [= 1] -> val: first parts
            else -> val: parts

        result\[lower field]: val
    ]

    return result
]

loop parseConfig relative "config.file" [k,v][
    if? block? v -> print [k "->" join.with:", " v]
    else -> print [k "->" v]
]


; Author: AlephX, Aug 18 2011
data = %A_scriptdir%\rosettaconfig.txt
comma := ","

Loop, Read, %data%
	{
	if NOT (instr(A_LoopReadLine, "#") == 1 OR A_LoopReadLine == "")

		{
		if instr(A_LoopReadLine, ";") == 1
			{
			parameter := RegExReplace(Substr(A_LoopReadLine,2), "^[ \s]+|[ \s]+$", "")
			%parameter% = "1"
			}
		else
			{
			parameter := RegExReplace(A_LoopReadLine, "^[ \s]+|[ \s]+$", "")

			if instr(parameter, A_Space)
				{
				value := substr(parameter, instr(parameter, A_Space)+1,999)
				parameter := substr(parameter, 1, instr(parameter, A_Space)-1)

				if (instr(value, ",") <> 0)
					{
					Loop, Parse, value, %comma% ,%A_Space%
						%parameter%%A_Index% := A_Loopfield
					}
				else
					%parameter% = %value%
				}
			else
				%parameter% = "0"
			}
		}
	}
msgbox, FULLNAME %fullname%`nFAVOURITEFRUIT %FAVOURITEFRUIT%`nNEEDSPEELING %NEEDSPEELING%`nSEEDSREMOVED %SEEDSREMOVED%`nOTHERFAMILY %OTHERFAMILY1% + %OTHERFAMILY2%


# syntax: GAWK -f READ_A_CONFIGURATION_FILE.AWK
BEGIN {
    fullname = favouritefruit = ""
    needspeeling = seedsremoved = "false"
    fn = "READ_A_CONFIGURATION_FILE.INI"
    while (getline rec <fn > 0) {
      tmp = tolower(rec)
      if (tmp ~ /^ *fullname/) { fullname = extract(rec) }
      else if (tmp ~ /^ *favouritefruit/) { favouritefruit = extract(rec) }
      else if (tmp ~ /^ *needspeeling/) { needspeeling = "true" }
      else if (tmp ~ /^ *seedsremoved/) { seedsremoved = "true" }
      else if (tmp ~ /^ *otherfamily/) { split(extract(rec),otherfamily,",") }
    }
    close(fn)
    printf("fullname=%s\n",fullname)
    printf("favouritefruit=%s\n",favouritefruit)
    printf("needspeeling=%s\n",needspeeling)
    printf("seedsremoved=%s\n",seedsremoved)
    for (i=1; i<=length(otherfamily); i++) {
      sub(/^ +/,"",otherfamily[i]) # remove leading spaces
      sub(/ +$/,"",otherfamily[i]) # remove trailing spaces
      printf("otherfamily(%d)=%s\n",i,otherfamily[i])
    }
    exit(0)
}
function extract(rec,  pos,str) {
    sub(/^ +/,"",rec)       # remove leading spaces before parameter name
    pos = match(rec,/[= ]/) # determine where data begins
    str = substr(rec,pos)   # extract the data
    gsub(/^[= ]+/,"",str)   # remove leading "=" and spaces
    sub(/ +$/,"",str)       # remove trailing spaces
    return(str)
}


' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
' Read a Configuration File V1.0                    '
'                                                   '
' Developed by A. David Garza Marín in VB-DOS for   '
' RosettaCode. December 2, 2016.                    '
' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '

OPTION EXPLICIT  ' For VB-DOS, PDS 7.1
' OPTION _EXPLICIT  ' For QB64

' SUBs and FUNCTIONs
DECLARE FUNCTION ErrorMessage$ (WhichError AS INTEGER)
DECLARE FUNCTION YorN$ ()
DECLARE FUNCTION FileExists% (WhichFile AS STRING)
DECLARE FUNCTION ReadConfFile% (NameOfConfFile AS STRING)
DECLARE FUNCTION getVariable$ (WhichVariable AS STRING)
DECLARE FUNCTION getArrayVariable$ (WhichVariable AS STRING, WhichIndex AS INTEGER)

' Register for values located
TYPE regVarValue
  VarName AS STRING * 20
  VarType AS INTEGER ' 1=String, 2=Integer, 3=Real
  VarValue AS STRING * 30
END TYPE

' Var
DIM rVarValue() AS regVarValue, iErr AS INTEGER, i AS INTEGER, iHMV AS INTEGER
DIM otherfamily(1 TO 2) AS STRING
DIM fullname AS STRING, favouritefruit AS STRING, needspeeling AS INTEGER, seedsremoved AS INTEGER
CONST ConfFileName = "config.fil"

' ------------------- Main Program ------------------------
CLS
PRINT "This program reads a configuration file and shows the result."
PRINT
PRINT "Default file name: "; ConfFileName
PRINT
iErr = ReadConfFile(ConfFileName)
IF iErr = 0 THEN
  iHMV = UBOUND(rVarValue)
  PRINT "Variables found in file:"
  FOR i = 1 TO iHMV
    PRINT RTRIM$(rVarValue(i).VarName); " = "; RTRIM$(rVarValue(i).VarValue); " (";
    SELECT CASE rVarValue(i).VarType
      CASE 0: PRINT "Undefined";
      CASE 1: PRINT "String";
      CASE 2: PRINT "Integer";
      CASE 3: PRINT "Real";
    END SELECT
    PRINT ")"
  NEXT i
  PRINT

  ' Sets required variables
  fullname = getVariable$("FullName")
  favouritefruit = getVariable$("FavouriteFruit")
  needspeeling = VAL(getVariable$("NeedSpeeling"))
  seedsremoved = VAL(getVariable$("SeedsRemoved"))
  FOR i = 1 TO 2
    otherfamily(i) = getArrayVariable$("OtherFamily", i)
  NEXT i
  PRINT "Variables requested to set values:"
  PRINT "fullname = "; fullname
  PRINT "favouritefruit = "; favouritefruit
  PRINT "needspeeling = ";
  IF needspeeling = 0 THEN PRINT "false" ELSE PRINT "true"
  PRINT "seedsremoved = ";
  IF seedsremoved = 0 THEN PRINT "false" ELSE PRINT "true"
  FOR i = 1 TO 2
    PRINT "otherfamily("; i; ") = "; otherfamily(i)
  NEXT i
ELSE
  PRINT ErrorMessage$(iErr)
END IF
' --------- End of Main Program -----------------------

END

FileError:
  iErr = ERR
RESUME NEXT

FUNCTION ErrorMessage$ (WhichError AS INTEGER)
    ' Var
    DIM sError AS STRING

    SELECT CASE WhichError
      CASE 0: sError = "Everything went ok."
      CASE 1: sError = "Configuration file doesn't exist."
      CASE 2: sError = "There are no variables in the given file."
    END SELECT

    ErrorMessage$ = sError
END FUNCTION

FUNCTION FileExists% (WhichFile AS STRING)
    ' Var
    DIM iFile AS INTEGER
    DIM iItExists AS INTEGER
    SHARED iErr AS INTEGER

    ON ERROR GOTO FileError
    iFile = FREEFILE
    iErr = 0
    OPEN WhichFile FOR BINARY AS #iFile
    IF iErr = 0 THEN
        iItExists = LOF(iFile) > 0
        CLOSE #iFile

        IF NOT iItExists THEN
            KILL WhichFile
        END IF
    END IF
    ON ERROR GOTO 0
    FileExists% = iItExists

END FUNCTION

FUNCTION getArrayVariable$ (WhichVariable AS STRING, WhichIndex AS INTEGER)
  ' Var
  DIM i AS INTEGER, iHMV AS INTEGER, iCount AS INTEGER
  DIM sVar AS STRING, sVal AS STRING, sWV AS STRING
  SHARED rVarValue() AS regVarValue

  ' Looks for a variable name and returns its value
  iHMV = UBOUND(rVarValue)
  sWV = UCASE$(LTRIM$(RTRIM$(WhichVariable)))
  sVal = ""
  DO
    i = i + 1
    sVar = UCASE$(RTRIM$(rVarValue(i).VarName))
    IF sVar = sWV THEN
      iCount = iCount + 1
      IF iCount = WhichIndex THEN
        sVal = LTRIM$(RTRIM$(rVarValue(i).VarValue))
      END IF
    END IF
  LOOP UNTIL i >= iHMV OR sVal <> ""

  ' Found it or not, it will return the result.
  ' If the result is "" then it didn't found the requested variable.
  getArrayVariable$ = sVal

END FUNCTION

FUNCTION getVariable$ (WhichVariable AS STRING)
  ' Var
  DIM i AS INTEGER, iHMV AS INTEGER
  DIM sVal AS STRING

  ' For a single variable, looks in the first (and only)
  '   element of the array that contains the name requested.
  sVal = getArrayVariable$(WhichVariable, 1)

  getVariable$ = sVal
END FUNCTION

FUNCTION ReadConfFile% (NameOfConfFile AS STRING)
  ' Var
  DIM iFile AS INTEGER, iType AS INTEGER, iVar AS INTEGER, iHMV AS INTEGER
  DIM iVal AS INTEGER, iCurVar AS INTEGER, i AS INTEGER, iErr AS INTEGER
  DIM dValue AS DOUBLE
  DIM sLine AS STRING, sVar AS STRING, sValue  AS STRING
  SHARED rVarValue() AS regVarValue

  ' This procedure reads a configuration file with variables
  '  and values separated by the equal sign (=) or a space.
  '  It needs the FileExists% function.
  '  Lines begining with # or blank will be ignored.
  IF FileExists%(NameOfConfFile) THEN
    iFile = FREEFILE
    REDIM rVarValue(1 TO 10) AS regVarValue
    OPEN NameOfConfFile FOR INPUT AS #iFile
      WHILE NOT EOF(iFile)
        LINE INPUT #iFile, sLine
        sLine = RTRIM$(LTRIM$(sLine))
        IF LEN(sLine) > 0 THEN ' Does it have any content?
          IF LEFT$(sLine, 1) <> "#" THEN  ' Is not a comment?
            IF LEFT$(sLine,1) = ";" THEN  ' It is a commented variable
              sLine = LTRIM$(MID$(sLine, 2))
            END IF
            iVar = INSTR(sLine, "=")  ' Is there an equal sign?
            IF iVar = 0 THEN iVar = INSTR(sLine, " ") ' if not then is there a space?

            GOSUB AddASpaceForAVariable
            iCurVar = iHMV
            IF iVar > 0 THEN  ' Is a variable and a value
              rVarValue(iHMV).VarName = LEFT$(sLine, iVar - 1)
            ELSE              ' Is just a variable name
              rVarValue(iHMV).VarName = sLine
              rVarValue(iHMV).VarValue = ""
            END IF

            IF iVar > 0 THEN  ' Get the value(s)
              sLine = LTRIM$(MID$(sLine, iVar + 1))
              DO  ' Look for commas
                iVal = INSTR(sLine, ",")
                IF iVal > 0 THEN  ' There is a comma
                  rVarValue(iHMV).VarValue = RTRIM$(LEFT$(sLine, iVal - 1))
                  GOSUB AddASpaceForAVariable
                  rVarValue(iHMV).VarName = rVarValue(iHMV - 1).VarName  ' Repeats the variable name
                  sLine = LTRIM$(MID$(sLine, iVal + 1))
                END IF
              LOOP UNTIL iVal = 0
              rVarValue(iHMV).VarValue = sLine

              ' Determine the variable type of each variable found in this step
              FOR i = iCurVar TO iHMV
                GOSUB DetermineVariableType
              NEXT i
            END IF
          END IF
        END IF
      WEND
    CLOSE iFile
    IF iHMV > 0 THEN
      REDIM PRESERVE rVarValue(1 TO iHMV) AS regVarValue
      iErr = 0 ' Everything ran ok.
    ELSE
      REDIM rVarValue(1 TO 1) AS regVarValue
      iErr = 2 ' No variables found in configuration file
    END IF
  ELSE
    iErr = 1 ' File doesn't exist
  END IF

  ReadConfFile = iErr

EXIT FUNCTION

AddASpaceForAVariable:
  iHMV = iHMV + 1

  IF UBOUND(rVarValue) < iHMV THEN  ' Are there space for a new one?
    REDIM PRESERVE rVarValue(1 TO iHMV + 9) AS regVarValue
  END IF
RETURN

DetermineVariableType:
  sValue = RTRIM$(rVarValue(i).VarValue)
  IF ASC(LEFT$(sValue, 1)) < 48 OR ASC(LEFT$(sValue, 1)) > 57 THEN
    rVarValue(i).VarType = 1  ' String
  ELSE
    dValue = VAL(sValue)
    IF CLNG(dValue) = dValue THEN
      rVarValue(i).VarType = 2 ' Integer
    ELSE
      rVarValue(i).VarType = 3 ' Real
    END IF
  END IF
RETURN

END FUNCTION


      BOOL = 1
      NAME = 2
      ARRAY = 3

      optfile$ = "options.cfg"

      fullname$ = FNoption(optfile$, "FULLNAME", NAME)
      favouritefruit$ = FNoption(optfile$, "FAVOURITEFRUIT", NAME)
      needspeeling% = FNoption(optfile$, "NEEDSPEELING", BOOL)
      seedsremoved% = FNoption(optfile$, "SEEDSREMOVED", BOOL)
      !^otherfamily$() = FNoption(optfile$, "OTHERFAMILY", ARRAY)

      PRINT "fullname = " fullname$
      PRINT "favouritefruit = " favouritefruit$
      PRINT "needspeeling = "; : IF needspeeling% PRINT "true" ELSE PRINT "false"
      PRINT "seedsremoved = "; : IF seedsremoved% PRINT "true" ELSE PRINT "false"
      PRINT "otherfamily(1) = " otherfamily$(1)
      PRINT "otherfamily(2) = " otherfamily$(2)
      END

      DEF FNoption(file$, key$, type%)
      LOCAL file%, opt$, comma%, bool%, name$, size%, !^array$()
      file% = OPENIN(file$)
      IF file% = 0 THEN = 0
      WHILE NOT EOF#file%
        opt$ = GET$#file%
        WHILE RIGHT$(opt$) = " " opt$ = LEFT$(opt$) : ENDWHILE
        IF opt$ = key$ OR LEFT$(opt$, LEN(key$)+1) = key$ + " " THEN
          opt$ = MID$(opt$, LEN(key$) + 1)
          WHILE LEFT$(opt$,1) = " " opt$ = MID$(opt$,2) : ENDWHILE
          CASE type% OF
            WHEN BOOL: bool% = TRUE : EXIT WHILE
            WHEN NAME: name$ = opt$ : EXIT WHILE
            WHEN ARRAY:
              REPEAT
                comma% = INSTR(opt$, ",", comma%+1)
                IF comma% size% += 1
              UNTIL comma% = 0
              DIM array$(size% + 1)
              size% = 0
              REPEAT
                comma% = INSTR(opt$, ",")
                IF comma% THEN
                  size% += 1
                  array$(size%) = LEFT$(opt$, comma%-1)
                  opt$ = MID$(opt$, comma%+1)
                  WHILE LEFT$(opt$,1) = " " opt$ = MID$(opt$,2) : ENDWHILE
                ENDIF
              UNTIL comma% = 0
              array$(size% + 1) = opt$
              EXIT WHILE
          ENDCASE
        ENDIF
      ENDWHILE
      CLOSE #file%
      CASE type% OF
        WHEN BOOL: = bool%
        WHEN NAME: = name$
        WHEN ARRAY: = !^array$()
      ENDCASE
      = 0


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <confini.h>

#define rosetta_uint8_t unsigned char

#define FALSE 0
#define TRUE 1

#define CONFIGS_TO_READ 5
#define INI_ARRAY_DELIMITER ','

/* Assume that the config file represent a struct containing all the parameters to load */
struct configs {
	char *fullname;
	char *favouritefruit;
	rosetta_uint8_t needspeeling;
	rosetta_uint8_t seedsremoved;
	char **otherfamily;
	size_t otherfamily_len;
	size_t _configs_left_;
};

static char ** make_array (size_t * arrlen, const char * src, const size_t buffsize, IniFormat ini_format) {

	/* Allocate a new array of strings and populate it from the stringified source */
	*arrlen = ini_array_get_length(src, INI_ARRAY_DELIMITER, ini_format);
	char ** const dest = *arrlen ? (char **) malloc(*arrlen * sizeof(char *) + buffsize) : NULL;
	if (!dest) { return NULL; }
	memcpy(dest + *arrlen, src, buffsize);
	char * iter = (char *) (dest + *arrlen);
	for (size_t idx = 0; idx < *arrlen; idx++) {
		dest[idx] = ini_array_release(&iter, INI_ARRAY_DELIMITER, ini_format);
		ini_string_parse(dest[idx], ini_format);
	}
	return dest;

}

static int configs_member_handler (IniDispatch *this, void *v_confs) {

	struct configs *confs = (struct configs *) v_confs;

	if (this->type != INI_KEY) {

		return 0;

	}

	if (ini_string_match_si("FULLNAME", this->data, this->format)) {

		if (confs->fullname) { return 0; }
		this->v_len = ini_string_parse(this->value, this->format); /* Remove all quotes, if any */
		confs->fullname = strndup(this->value, this->v_len);
		confs->_configs_left_--;

	} else if (ini_string_match_si("FAVOURITEFRUIT", this->data, this->format)) {

		if (confs->favouritefruit) { return 0; }
		this->v_len = ini_string_parse(this->value, this->format); /* Remove all quotes, if any */
		confs->favouritefruit = strndup(this->value, this->v_len);
		confs->_configs_left_--;

	} else if (ini_string_match_si("NEEDSPEELING", this->data, this->format)) {

		if (~confs->needspeeling & 0x80) { return 0; }
		confs->needspeeling = ini_get_bool(this->value, TRUE);
		confs->_configs_left_--;

	} else if (ini_string_match_si("SEEDSREMOVED", this->data, this->format)) {

		if (~confs->seedsremoved & 0x80) { return 0; }
		confs->seedsremoved = ini_get_bool(this->value, TRUE);
		confs->_configs_left_--;

	} else if (!confs->otherfamily && ini_string_match_si("OTHERFAMILY", this->data, this->format)) {

		if (confs->otherfamily) { return 0; }
		this->v_len = ini_array_collapse(this->value, INI_ARRAY_DELIMITER, this->format); /* Save memory (not strictly needed) */
		confs->otherfamily = make_array(&confs->otherfamily_len, this->value, this->v_len + 1, this->format);
		confs->_configs_left_--;

	}

	/* Optimization: stop reading the INI file when we have all we need */
	return !confs->_configs_left_;

}

static int populate_configs (struct configs * confs) {

	/* Define the format of the configuration file */
	IniFormat config_format = {
		.delimiter_symbol = INI_ANY_SPACE,
		.case_sensitive = FALSE,
		.semicolon_marker = INI_IGNORE,
		.hash_marker = INI_IGNORE,
		.multiline_nodes = INI_NO_MULTILINE,
		.section_paths = INI_NO_SECTIONS,
		.no_single_quotes = FALSE,
		.no_double_quotes = FALSE,
		.no_spaces_in_names = TRUE,
		.implicit_is_not_empty = TRUE,
		.do_not_collapse_values = FALSE,
		.preserve_empty_quotes = FALSE,
		.disabled_after_space = TRUE,
		.disabled_can_be_implicit = FALSE
	};

	*confs = (struct configs) { NULL, NULL, 0x80, 0x80, NULL, 0, CONFIGS_TO_READ };

	if (load_ini_path("rosetta.conf", config_format, NULL, configs_member_handler, confs) & CONFINI_ERROR) {

		fprintf(stderr, "Sorry, something went wrong :-(\n");
		return 1;

	}

	confs->needspeeling &= 0x7F;
	confs->seedsremoved &= 0x7F;

	return 0;

}

int main () {

	struct configs confs;

	ini_global_set_implicit_value("YES", 0);

	if (populate_configs(&confs)) {

		return 1;

	}

	/* Print the configurations parsed */

	printf(

		"Full name: %s\n"
		"Favorite fruit: %s\n"
		"Need spelling: %s\n"
		"Seeds removed: %s\n",

		confs.fullname,
		confs.favouritefruit,
		confs.needspeeling ? "True" : "False",
		confs.seedsremoved ? "True" : "False"

	);

	for (size_t idx = 0; idx < confs.otherfamily_len; idx++) {

		printf("Other family[%d]: %s\n", idx, confs.otherfamily[idx]);

	}

	/* Free the allocated memory */

	#define FREE_NON_NULL(PTR) if (PTR) { free(PTR); }

	FREE_NON_NULL(confs.fullname);
	FREE_NON_NULL(confs.favouritefruit);
	FREE_NON_NULL(confs.otherfamily);

	return 0;

}


#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/case_conv.hpp>
using namespace std;
using namespace boost;

typedef boost::tokenizer<boost::char_separator<char> > Tokenizer;
static const char_separator<char> sep(" ","#;,");

//Assume that the config file represent a struct containing all the parameters to load
struct configs{
	string fullname;
	string favoritefruit;
	bool needspelling;
	bool seedsremoved;
	vector<string> otherfamily;
} conf;

void parseLine(const string &line, configs &conf)
{
	if (line[0] == '#' || line.empty())
		return;
	Tokenizer tokenizer(line, sep);
	vector<string> tokens;
	for (Tokenizer::iterator iter = tokenizer.begin(); iter != tokenizer.end(); iter++)
		tokens.push_back(*iter);
	if (tokens[0] == ";"){
		algorithm::to_lower(tokens[1]);
		if (tokens[1] == "needspeeling")
			conf.needspelling = false;
		if (tokens[1] == "seedsremoved")
			conf.seedsremoved = false;
	}
	algorithm::to_lower(tokens[0]);
	if (tokens[0] == "needspeeling")
		conf.needspelling = true;
	if (tokens[0] == "seedsremoved")
		conf.seedsremoved = true;
	if (tokens[0] == "fullname"){
		for (unsigned int i=1; i<tokens.size(); i++)
			conf.fullname += tokens[i] + " ";
		conf.fullname.erase(conf.fullname.size() -1, 1);
	}
	if (tokens[0] == "favouritefruit")
		for (unsigned int i=1; i<tokens.size(); i++)
			conf.favoritefruit += tokens[i];
	if (tokens[0] == "otherfamily"){
		unsigned int i=1;
		string tmp;
		while (i<=tokens.size()){
			if ( i == tokens.size() || tokens[i] ==","){
				tmp.erase(tmp.size()-1, 1);
				conf.otherfamily.push_back(tmp);
				tmp = "";
				i++;
			}
			else{
				tmp += tokens[i];
				tmp += " ";
				i++;
			}
		}
	}
}

int _tmain(int argc, TCHAR* argv[])
{
	if (argc != 2)
	{
		wstring tmp = argv[0];
		wcout << L"Usage: " << tmp << L" <configfile.ini>" << endl;
		return -1;
	}
	ifstream file (argv[1]);

	if (file.is_open())
		while(file.good())
		{
			char line[255];
			file.getline(line, 255);
			string linestring(line);
			parseLine(linestring, conf);
		}
	else
	{
		cout << "Unable to open the file" << endl;
		return -2;
	}

	cout << "Fullname= " << conf.fullname << endl;
	cout << "Favorite Fruit= " << conf.favoritefruit << endl;
	cout << "Need Spelling= " << (conf.needspelling?"True":"False") << endl;
	cout << "Seed Removed= " << (conf.seedsremoved?"True":"False") << endl;
	string otherFamily;
	for (unsigned int i = 0; i < conf.otherfamily.size(); i++)
		otherFamily += conf.otherfamily[i] + ", ";
	otherFamily.erase(otherFamily.size()-2, 2);
	cout << "Other Family= " << otherFamily << endl;

	return 0;
}


(ns read-conf-file.core
  (:require [clojure.java.io :as io]
            [clojure.string :as str])
  (:gen-class))

(def conf-keys ["fullname"
                "favouritefruit"
                "needspeeling"
                "seedsremoved"
                "otherfamily"])

(defn get-lines
  "Read file returning vec of lines."
  [file]
  (try
    (with-open [rdr (io/reader file)]
      (into [] (line-seq rdr)))
    (catch Exception e (.getMessage e))))

(defn parse-line
  "Parse passed line returning vec: token, vec of values."
  [line]
  (if-let [[_ k v] (re-matches #"(?i)^\s*([a-z]+)(?:\s+|=)?(.+)?$" line)]
    (let [k (str/lower-case k)]
      (if v
        [k (str/split v #",\s*")]
        [k [true]]))))

(defn mk-conf
  "Build configuration map from lines."
  [lines]
  (->> (map parse-line lines)
       (filter (comp not nil?))
       (reduce (fn
                 [m [k v]]
                 (assoc m k v)) {})))

(defn output
  [conf-keys conf]
  (doseq [k conf-keys]
    (let [v (get conf k)]
      (if v
        (println (format "%s = %s" k (str/join ", " v)))
        (println (format "%s = %s" k "false"))))))

(defn -main
  [filename]
  (output conf-keys (mk-conf (get-lines filename))))


       identification division.
       program-id. ReadConfiguration.

       environment division.
       configuration section.
       repository.
           function all intrinsic.

       input-output section.
       file-control.
           select config-file     assign to "Configuration.txt"
                                  organization line sequential.
       data division.
       file section.

       fd  config-file.
       01  config-record          pic is x(128).

       working-storage section.
       77  idx                    pic 9(3).
       77  pos                    pic 9(3).
       77  last-pos               pic 9(3).
       77  config-key             pic x(32).
       77  config-value           pic x(64).
       77  multi-value            pic x(64).
       77  full-name              pic x(64).
       77  favourite-fruit        pic x(64).
       77  other-family           pic x(64) occurs 10.
       77  need-speeling          pic x(5) value "false".
       77  seeds-removed          pic x(5) value "false".

       procedure division.
       main.
           open input config-file
           perform until exit
              read config-file
                 at end
                    exit perform
              end-read
              move trim(config-record) to config-record
              if config-record(1:1) = "#" or ";" or spaces
                 exit perform cycle
              end-if
              unstring config-record delimited by spaces into config-key
              move trim(config-record(length(trim(config-key)) + 1:)) to config-value
              if config-value(1:1) = "="
                 move trim(config-value(2:)) to config-value
              end-if
              evaluate upper-case(config-key)
                 when "FULLNAME"
                    move config-value to full-name
                 when "FAVOURITEFRUIT"
                    move config-value to favourite-fruit
                 when "NEEDSPEELING"
                    if config-value = spaces
                       move "true" to config-value
                    end-if
                    if config-value = "true" or "false"
                       move config-value to need-speeling
                    end-if
                 when "SEEDSREMOVED"
                    if config-value = spaces
                       move "true" to config-value
                    end-if,
                    if config-value = "true" or "false"
                       move config-value to seeds-removed
                    end-if
                 when "OTHERFAMILY"
                    move 1 to idx, pos
                    perform until exit
                       unstring config-value delimited by "," into multi-value with pointer pos
                          on overflow
                             move trim(multi-value) to other-family(idx)
                             move pos to last-pos
                          not on overflow
                             if config-value(last-pos:) <> spaces
                                move trim(config-value(last-pos:)) to other-family(idx)
                             end-if,
                             exit perform
                       end-unstring
                       add 1 to idx
                    end-perform
              end-evaluate
           end-perform
           close config-file

           display "fullname = " full-name
           display "favouritefruit = " favourite-fruit
           display "needspeeling = " need-speeling
           display "seedsremoved = " seeds-removed
           perform varying idx from 1 by 1 until idx > 10
              if other-family(idx) <> low-values
                 display "otherfamily(" idx ") = " other-family(idx)
              end-if
           end-perform
           .


(ql:quickload :parser-combinators)

(defpackage :read-config
  (:use :cl :parser-combinators))

(in-package :read-config)

(defun trim-space (string)
  (string-trim '(#\space #\tab) string))

(defun any-but1? (except)
  (named-seq? (<- res (many1? (except? (item) except)))
              (coerce res 'string)))

(defun values? ()
  (named-seq? (<- values (sepby? (any-but1? #\,) #\,))
              (mapcar 'trim-space values)))

(defun key-values? ()
  (named-seq? (<- key (word?))
              (opt? (many? (whitespace?)))
              (opt? #\=)
              (<- values (values?))
              (cons key (or (if (cdr values) values (car values)) t))))

(defun parse-line (line)
  (setf line (trim-space line))
  (if (or (string= line "") (member (char line 0) '(#\# #\;)))
      :comment
      (parse-string* (key-values?) line)))

(defun parse-config (stream)
  (let ((hash (make-hash-table :test 'equal)))
    (loop for line = (read-line stream nil nil)
       while line
       do (let ((parsed (parse-line line)))
            (cond ((eq parsed :comment))
                  ((eq parsed nil) (error "config parser error: ~a" line))
                  (t (setf (gethash (car parsed) hash) (cdr parsed))))))
    hash))


import std.stdio, std.string, std.conv, std.regex, std.getopt;

enum VarName(alias var) = var.stringof.toUpper;

void setOpt(alias Var)(in string line) {
    auto m = match(line, regex(`^` ~ VarName!Var ~ `(\s+(.*))?`));
    if (!m.empty) {
        static if (is(typeof(Var) == string))
            Var = m.captures.length > 2 ? m.captures[2] : "";
        static if (is(typeof(Var) == bool))
            Var = true;
        static if (is(typeof(Var) == int))
            Var = m.captures.length > 2 ? to!int(m.captures[2]) : 0;
    }
}

void main(in string[] args) {
    string fullName, favouriteFruit, otherFamily;
    bool needsPeeling, seedsRemoved; // Default false.
    auto f = "readcfg.txt".File;

    foreach (line; f.byLine) {
        auto opt = line.strip.idup;
        setOpt!fullName(opt);
        setOpt!favouriteFruit(opt);
        setOpt!needsPeeling(opt);
        setOpt!seedsRemoved(opt);
        setOpt!otherFamily(opt);
    }

    writefln("%14s = %s", VarName!fullName, fullName);
    writefln("%14s = %s", VarName!favouriteFruit, favouriteFruit);
    writefln("%14s = %s", VarName!needsPeeling, needsPeeling);
    writefln("%14s = %s", VarName!seedsRemoved, seedsRemoved);
    writefln("%14s = %s", VarName!otherFamily, otherFamily);
}


$ open input config.ini
$ loop:
$  read /end_of_file = done input line
$  line = f$edit( line, "trim" )  ! removes leading and trailing spaces or tabs
$  if f$length( line ) .eq. 0 then $ goto loop
$  first_character = f$extract( 0, 1, line )
$  if first_character .eqs. "#" .or. first_character .eqs. ";" then $ goto loop
$  equal_sign_offset = f$locate( "=", line )
$  length_of_line = f$length( line )
$  if equal_sign_offset .ne. length_of_line then $ line = f$extract( 0, equal_sign_offset, line ) + " " + f$extract( equal_sign_offset + 1, length_of_line, line )
$  option_name = f$element( 0, " ", line )
$  parameter_data = line - option_name - " "
$  if parameter_data .eqs. "" then $ parameter_data = "true"
$  'option_name = parameter_data
$  show symbol 'option_name
$  goto loop
$ done:
$ close input


unit uSettings;

interface

uses
  System.SysUtils, System.IoUtils, System.Generics.Collections, System.Variants;

type
  TVariable = record
    value: variant;
    function ToString: string;
    class operator Implicit(a: variant): TVariable;
    class operator Implicit(a: TVariable): TArray<string>;
    class operator Implicit(a: TVariable): string;
  end;

  TSettings = class(TDictionary<string, TVariable>)
  private
    function GetVariable(key: string): TVariable;
    procedure SetVariable(key: string; const Value: TVariable);
    function GetKey(line: string; var key: string; var value: variant; var
      disable: boolean): boolean;
    function GetAllKeys: TList<string>;
  public
    procedure LoadFromFile(Filename: TfileName);
    procedure SaveToFile(Filename: TfileName);
    property Variable[key: string]: TVariable read GetVariable write SetVariable; default;
  end;

implementation

{ TVariable }

class operator TVariable.Implicit(a: variant): TVariable;
begin
  Result.value := a;
end;

class operator TVariable.Implicit(a: TVariable): TArray<string>;
begin
  if VarIsType(a.value, varArray or varOleStr) then
    Result := a.value
  else
    raise Exception.Create('Error: can''t convert this type data in array');
end;

class operator TVariable.Implicit(a: TVariable): string;
begin
  Result := a.ToString;
end;

function TVariable.ToString: string;
var
  arr: TArray<string>;
begin
  if VarIsType(value, varArray or varOleStr) then
  begin
    arr := value;
    Result := string.Join(', ', arr).Trim;
  end
  else
    Result := value;
  Result := Result.Trim;
end;

{ TSettings }

function TSettings.GetAllKeys: TList<string>;
var
  key: string;
begin
  Result := TList<string>.Create;
  for key in Keys do
    Result.Add(key);
end;

function TSettings.GetKey(line: string; var key: string; var value: variant; var
  disable: boolean): boolean;
var
  line_: string;
  j: integer;
begin
  line_ := line.Trim;
  Result := not (line_.IsEmpty or (line_[1] = '#'));
  if not Result then
    exit;

  disable := (line_[1] = ';');
  if disable then
    delete(line_, 1, 1);

  var data := line_.Split([' '], TStringSplitOptions.ExcludeEmpty);
  case length(data) of
    1: //Boolean
      begin
        key := data[0].ToUpper;
        value := True;
      end;

    2: //Single String
      begin
        key := data[0].ToUpper;
        value := data[1].Trim;
      end;

  else // Mult String value or Array of value
    begin
      key := data[0];
      delete(line_, 1, key.Length);
      if line_.IndexOf(',') > -1 then
      begin
        data := line_.Trim.Split([','], TStringSplitOptions.ExcludeEmpty);
        for j := 0 to High(data) do
          data[j] := data[j].Trim;
        value := data;
      end
      else
        value := line_.Trim;
    end;
  end;
  Result := true;
end;

function TSettings.GetVariable(key: string): TVariable;
begin
  key := key.Trim.ToUpper;
  if not ContainsKey(key) then
    add(key, false);

  result := Items[key];
end;

procedure TSettings.LoadFromFile(Filename: TfileName);
var
  key, line: string;
  value: variant;
  disabled: boolean;
  Lines: TArray<string>;
begin
  if not FileExists(Filename) then
    exit;

  Clear;
  Lines := TFile.ReadAllLines(Filename);
  for line in Lines do
  begin
    if GetKey(line, key, value, disabled) then
    begin
      if disabled then
        AddOrSetValue(key, False)
      else
        AddOrSetValue(key, value)
    end;
  end;
end;

procedure TSettings.SaveToFile(Filename: TfileName);
var
  key, line: string;
  value: variant;
  disabled: boolean;
  Lines: TArray<string>;
  i: Integer;
  All_kyes: TList<string>;
begin
  All_kyes := GetAllKeys();
  SetLength(Lines, 0);
  i := 0;
  if FileExists(Filename) then
  begin
    Lines := TFile.ReadAllLines(Filename);
    for i := high(Lines) downto 0 do
    begin
      if GetKey(Lines[i], key, value, disabled) then
      begin
        if not ContainsKey(key) then
        begin
          Lines[i] := '; ' + Lines[i];
          Continue;
        end;

        All_kyes.Remove(key);

        disabled := VarIsType(Variable[key].value, varBoolean) and (Variable[key].value
          = false);
        if not disabled then
        begin
          if VarIsType(Variable[key].value, varBoolean) then
            Lines[i] := key
          else
            Lines[i] := format('%s %s', [key, Variable[key].ToString])
        end
        else
          Lines[i] := '; ' + key;
      end;
    end;

  end;

  // new keys
  i := high(Lines) + 1;
  SetLength(Lines, Length(Lines) + All_kyes.Count);
  for key in All_kyes do
  begin
    Lines[i] := format('%s %s', [key, Variable[key].ToString]);
    inc(i);
  end;

  Tfile.WriteAllLines(Filename, Lines);

  All_kyes.Free;
end;

procedure TSettings.SetVariable(key: string; const Value: TVariable);
begin
  AddOrSetValue(key.Trim.ToUpper, Value);
end;
end.


(edit 'preferences)
;; current contents to edit is displayed in the input box
(define (preferences)
(define-syntax-rule (++ n) (begin (set! n (1+ n)) n))
(define-syntax-rule (% a b) (modulo a b))
;; (lib 'gloops)
(lib 'timer))

;; enter new preferences
(define (preferences)
    (define FULLNAME "Foo Barber")
    (define FAVOURITEFRUIT 'banana)
    (define NEEDSPELLING #t)
; SEEDSREMOVED
    (define OTHERFAMILY '("Rhu Barber" "Harry Barber")))


defmodule Configuration_file do
  def read(file) do
    File.read!(file)
    |> String.split(~r/\n|\r\n|\r/, trim: true)
    |> Enum.reject(fn line -> String.starts_with?(line, ["#", ";"]) end)
    |> Enum.map(fn line ->
         case String.split(line, ~r/\s/, parts: 2) do
           [option]         -> {to_atom(option), true}
           [option, values] -> {to_atom(option), separate(values)}
         end
       end)
  end

  def task do
    defaults = [fullname: "Kalle", favouritefruit: "apple", needspeeling: false, seedsremoved: false]
    options = read("configuration_file") ++ defaults
    [:fullname, :favouritefruit, :needspeeling, :seedsremoved, :otherfamily]
    |> Enum.each(fn x ->
         values = options[x]
         if is_boolean(values) or length(values)==1 do
           IO.puts "#{x} = #{values}"
         else
           Enum.with_index(values) |> Enum.each(fn {value,i} ->
             IO.puts "#{x}(#{i+1}) = #{value}"
           end)
         end
       end)
  end

  defp to_atom(option), do: String.downcase(option) |> String.to_atom

  defp separate(values), do: String.split(values, ",") |> Enum.map(&String.strip/1)
end

Configuration_file.task


-module( configuration_file ).

-export( [read/1, task/0] ).

read( File ) ->
    {ok, Binary} = file:read_file( File ),
    Lines = [X || <<First:8, _T/binary>> = X <- binary:split(Binary, <<"\n">>, [global]), First =/= $#, First =/= $;],
    [option_from_binaries(binary:split(X, <<" ">>)) || X <- Lines].

task() ->
    Defaults = [{fullname, "Kalle"}, {favouritefruit, "apple"}, {needspeeling, false}, {seedsremoved, false}],
    Options = read( "configuration_file" ) ++ Defaults,
    [io:fwrite("~p = ~p~n", [X, proplists:get_value(X, Options)]) || X <- [fullname, favouritefruit, needspeeling, seedsremoved, otherfamily]].

option_from_binaries( [Option] ) -> {erlang:list_to_atom(string:to_lower(erlang:binary_to_list(Option))), true};
option_from_binaries( [Option, Values] ) -> {erlang:list_to_atom(string:to_lower(erlang:binary_to_list(Option))), option_from_binaries_value(binary:split(Values, <<", ">>))}.

option_from_binaries_value( [Value] ) -> erlang:binary_to_list(Value);
option_from_binaries_value( Values ) -> [erlang:binary_to_list(X) || X <- Values].


class Main
{
  // remove the given key and an optional '=' from start of line
  Str removeKey (Str key, Str line)
  {
    remainder := line[key.size..-1].trim
    if (remainder.startsWith("="))
    {
      remainder = remainder.replace("=", "").trim
    }
    return remainder
  }

  Void main ()
  {
    // define the variables which need configuring
    fullname := ""
    favouritefruit := ""
    needspeeling := false
    seedsremoved := false
    Str[] otherfamily := [,]

    // loop through the file, setting variables as needed
    File(`config.dat`).eachLine |Str line|
    {
      line = line.trim
      if (line.isEmpty || line.startsWith("#") || line.startsWith(";"))
      {
        // do nothing for empty and comment lines
      }
      else if (line.upper.startsWith("FULLNAME"))
      {
        fullname = removeKey("FULLNAME", line)
      }
      else if (line.upper.startsWith("FAVOURITEFRUIT"))
      {
        favouritefruit = removeKey("FAVOURITEFRUIT", line)
      }
      else if (line.upper.startsWith("NEEDSPEELING"))
      {
        needspeeling = true
      }
      else if (line.upper.startsWith("SEEDSREMOVED"))
      {
        seedsremoved = true
      }
      else if (line.upper.startsWith("OTHERFAMILY"))
      {
        otherfamily = removeKey("OTHERFAMILY", line).split(',')
      }
    }

    // report results
    echo ("Full name is $fullname")
    echo ("Favourite fruit is $favouritefruit")
    echo ("Needs peeling is $needspeeling")
    echo ("Seeds removed is $seedsremoved")
    echo ("Other family is " + otherfamily.join(", "))
  }
}


\ declare the configuration variables in the FORTH app
FORTH DEFINITIONS

32 CONSTANT $SIZE

VARIABLE FULLNAME       $SIZE ALLOT
VARIABLE FAVOURITEFRUIT $SIZE ALLOT
VARIABLE NEEDSPEELING
VARIABLE SEEDSREMOVED
VARIABLE OTHERFAMILY(1) $SIZE ALLOT
VARIABLE OTHERFAMILY(2) $SIZE ALLOT

: -leading  ( addr len -- addr' len' )
            begin over [email protected] bl = while 1 /string repeat ;   \ remove leading blanks

: trim      ( addr len -- addr len) -leading -trailing ;  \ remove blanks both ends

\ create the config file interpreter -------
VOCABULARY CONFIG                                          \ create a namespace
CONFIG DEFINITIONS                                         \ put things in the namespace
: SET     ( addr --) true swap ! ;
: RESET   ( addr --) false swap ! ;
: #        ( -- )      1 PARSE 2DROP ;                     \ parse line and throw away
: =        ( addr --)  1 PARSE trim ROT PLACE ;            \ string assignment operator
synonym ;  #                                               \ 2nd comment operator is simple

FORTH DEFINITIONS
\ this command reads and interprets the config.txt file
: CONFIGURE ( -- ) CONFIG  s" CONFIG.TXT" INCLUDED  FORTH ;
\ config file interpreter ends ------

\ tools to validate the CONFIG interpreter
: $.      ( str --)  count type ;
: BOOL.   ( ? --)    @ IF ." ON"  ELSE ." OFF" THEN ;

: .CONFIG       CR  ." Fullname       : " FULLNAME $.
                CR  ." Favourite fruit: " FAVOURITEFRUIT $.
                CR  ." Needs peeling  : " NEEDSPEELING bool.
                CR  ." Seeds removed  : " SEEDSREMOVED bool.
                CR  ." Family:"
                CR   otherfamily(1) $.
                CR   otherfamily(2) $.  ;


program readconfig
  implicit none
  integer, parameter    :: strlen = 100
  logical               :: needspeeling = .false., seedsremoved =.false.
  character(len=strlen) :: favouritefruit = "", fullname = "", fst, snd
  character(len=strlen), allocatable :: otherfamily(:), tmp(:)
  character(len=1000)   :: line
  integer               :: lun, stat,  j, j0, j1, ii = 1, z
  integer, parameter    :: state_begin=1, state_in_fst=2, state_in_sep=3

  open(newunit=lun, file="config.ini", status="old")

  do
    read(lun, "(a)", iostat=stat) line
    if (stat<0) exit
    if ((line(1:1) == "#") .or. &
        (line(1:1) == ";") .or. &
        (len_trim(line)==0)) then
      cycle
    end if
    z = state_begin
    do j = 1, len_trim(line)
      if (z == state_begin) then
        if (line(j:j)/=" ") then
          j0 = j
          z = state_in_fst
        end if
      elseif (z == state_in_fst) then
        if (index("= ",line(j:j))>0) then
          fst = lower(line(j0:j-1))
          z = state_in_sep
        end if
      elseif (z == state_in_sep) then
        if (index(" =",line(j:j)) == 0) then
          snd = line(j:)
          exit
        end if
      else
         stop "not possible to be here"
      end if
    end do
    if (z == state_in_fst) then
      fst = lower(line(j0:))
    elseif (z == state_begin) then
      cycle
    end if

    if (fst=="fullname") then
      read(snd,"(a)") fullname
    elseif (fst=="favouritefruit") then
      read(snd,"(a)") favouritefruit
    elseif (fst=="seedsremoved") then
      seedsremoved = .true.
    elseif (fst=="needspeeling") then
      needspeeling = .true.
    elseif (fst=="otherfamily") then
      j = 1; ii = 1
      do while (len_trim(snd(j:)) >0)
        j1  = index(snd(j:),",")
        if (j1==0) then
          j1 = len_trim(snd)
        else
          j1 = j + j1 - 2
        end if
        do
          if (j>len_trim(snd)) exit
          if (snd(j:j) /= " ") exit
          j = j +1
        end do
        allocate(tmp(ii))
        tmp(1:ii-1) = otherfamily
        call move_alloc(tmp, otherfamily)
        read(snd(j:j1),"(a)"), otherfamily(ii)
        j = j1 + 2
        ii = ii + 1
      end do
    else
      print *, "unknown option '"//trim(fst)//"'"; stop
    end if
  end do
  close(lun)

  print "(a,a)","fullname = ",       trim(fullname)
  print "(a,a)","favouritefruit = ", trim(favouritefruit)
  print "(a,l)","needspeeling = ",   needspeeling
  print "(a,l)","seedsremoved = ",   seedsremoved
  print "(a,*(a,:,', '))", "otherfamily = ", &
         (trim(otherfamily(j)), j=1,size(otherfamily))

contains

pure function lower (str) result (string)
    implicit none
    character(*), intent(In) :: str
    character(len(str))      :: string
    Integer :: ic, i

    character(26), parameter :: cap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    character(26), parameter :: low = 'abcdefghijklmnopqrstuvwxyz'

    string = str
    do i = 1, len_trim(str)
        ic = index(cap, str(i:i))
        if (ic > 0) string(i:i) = low(ic:ic)
    end do
end function

end program


' FB 1.05.0 Win64

Sub split (s As Const String, sepList As Const String, result() As String)
  If s = "" OrElse sepList = "" Then
     Redim result(0)
     result(0) = s
     Return
  End If
  Dim As Integer i, j, count = 0, empty = 0, length
  Dim As Integer position(Len(s) + 1)
  position(0) = 0

  For i = 0 To len(s) - 1
    For j = 0 to Len(sepList) - 1
      If s[i] = sepList[j] Then
        count += 1
        position(count) = i + 1
      End If
    Next j
  Next i

  Redim result(count)
  If count  = 0 Then
    result(0) = s
    Return
  End If

  position(count + 1) = len(s) + 1

  For i = 1 To count + 1
    length = position(i) - position(i - 1) - 1
    result(i - 1) = Mid(s, position(i - 1) + 1, length)
  Next
End Sub

Type ConfigData
  fullName As String
  favouriteFruit As String
  needsPeeling As Boolean
  seedsRemoved As Boolean
  otherFamily(Any) As String
End Type

Sub readConfigData(fileName As String, cData As ConfigData)
  Dim fileNum As Integer = FreeFile
  Open fileName For Input As #fileNum
  If err > 0 Then
    Print "File could not be opened"
    Sleep
    End
  End If
  Dim ln As String
  While Not Eof(fileNum)
    Line Input #fileNum, ln
    If ln = "" OrElse Left(ln, 1) = "#" OrElse Left(ln, 1) = ";" Then Continue While
    If UCase(Left(ln, 8)) = "FULLNAME" Then
      cData.fullName = Trim(Mid(ln, 9), Any " =")
    ElseIf UCase(Left(ln, 14)) = "FAVOURITEFRUIT" Then
      cData.favouriteFruit = Trim(Mid(ln, 15), Any " =")
    ElseIf UCase(Left(ln, 12)) = "NEEDSPEELING" Then
      Dim s As String = Trim(Mid(ln, 13), Any " =")
      If s = ""  OrElse UCase(s) = "TRUE" Then
        cData.needsPeeling = True
      Else
        cData.needsPeeling = False
      End If
    ElseIf UCase(Left(ln, 12)) = "SEEDSREMOVED" Then
      Dim s As String = Trim(Mid(ln, 13), Any " =")
      If s = ""  OrElse UCase(s) = "TRUE" Then
        cData.seedsRemoved = True
      Else
        cData.seedsRemoved = False
      End If
    ElseIf UCase(Left(ln, 11)) = "OTHERFAMILY" Then
       split Mid(ln, 12), ",", cData.otherFamily()
       For i As Integer = LBound(cData.otherFamily) To UBound(cData.otherFamily)
         cData.otherFamily(i) = Trim(cData.otherFamily(i), Any " =")
       Next
    End If
  Wend
  Close #fileNum
End Sub

Dim fileName As String = "config.txt"
Dim cData As ConfigData
readConfigData fileName, cData
Print "Full name        = "; cData.fullName
Print "Favourite fruit  = "; cData.favouriteFruit
Print "Needs peeling    = "; cData.needsPeeling
Print "Seeds removed    = "; cData.seedsRemoved
For i As Integer = LBound(cData.otherFamily) To UBound(cData.otherFamily)
  Print "Other family("; Str(i); ")  = "; cData.otherFamily(i)
Next
Print
Print "Press any key to quit"
Sleep


Public Sub Form_Open()
Dim fullname As String = Settings["fullname", "Foo Barber"]    'If fullname is empty then use the default "Foo Barber"
Dim favouritefruit As String = Settings["favouritefruit", "banana"]
Dim needspeeling As String = Settings["needspeling", True]
Dim seedsremoved As String = Settings["seedsremoved", False]
Dim otherfamily As String[] = Settings["otherfamily", ["Rhu Barber", "Harry Barber"]]

Print fullname

'To save
Settings["fullname"] = "John Smith"
fullname = Settings["fullname"]

Print fullname

End


package config

import (
	"errors"
	"io"
	"fmt"
	"bytes"
	"strings"
	"io/ioutil"
)

var (
	ENONE    = errors.New("Requested value does not exist")
	EBADTYPE = errors.New("Requested type and actual type do not match")
	EBADVAL  = errors.New("Value and type do not match")
)

type varError struct {
	err error
	n   string
	t   VarType
}

func (err *varError) Error() string {
	return fmt.Sprintf("%v: (%q, %v)", err.err, err.n, err.t)
}

type VarType int

const (
	Bool VarType = 1 + iota
	Array
	String
)

func (t VarType) String() string {
	switch t {
	case Bool:
		return "Bool"
	case Array:
		return "Array"
	case String:
		return "String"
	}

	panic("Unknown VarType")
}

type confvar struct {
	Type VarType
	Val  interface{}
}

type Config struct {
	m map[string]confvar
}

func Parse(r io.Reader) (c *Config, err error) {
	c = new(Config)
	c.m = make(map[string]confvar)

	buf, err := ioutil.ReadAll(r)
	if err != nil {
		return
	}

	lines := bytes.Split(buf, []byte{'\n'})

	for _, line := range lines {
		line = bytes.TrimSpace(line)
		if len(line) == 0 {
			continue
		}
		switch line[0] {
		case '#', ';':
			continue
		}

		parts := bytes.SplitN(line, []byte{' '}, 2)
		nam := string(bytes.ToLower(parts[0]))

		if len(parts) == 1 {
			c.m[nam] = confvar{Bool, true}
			continue
		}

		if strings.Contains(string(parts[1]), ",") {
			tmpB := bytes.Split(parts[1], []byte{','})
			for i := range tmpB {
				tmpB[i] = bytes.TrimSpace(tmpB[i])
			}
			tmpS := make([]string, 0, len(tmpB))
			for i := range tmpB {
				tmpS = append(tmpS, string(tmpB[i]))
			}

			c.m[nam] = confvar{Array, tmpS}
			continue
		}

		c.m[nam] = confvar{String, string(bytes.TrimSpace(parts[1]))}
	}

	return
}

func (c *Config) Bool(name string) (bool, error) {
	name = strings.ToLower(name)

	if _, ok := c.m[name]; !ok {
		return false, nil
	}

	if c.m[name].Type != Bool {
		return false, &varError{EBADTYPE, name, Bool}
	}

	v, ok := c.m[name].Val.(bool)
	if !ok {
		return false, &varError{EBADVAL, name, Bool}
	}
	return v, nil
}

func (c *Config) Array(name string) ([]string, error) {
	name = strings.ToLower(name)

	if _, ok := c.m[name]; !ok {
		return nil, &varError{ENONE, name, Array}
	}

	if c.m[name].Type != Array {
		return nil, &varError{EBADTYPE, name, Array}
	}

	v, ok := c.m[name].Val.([]string)
	if !ok {
		return nil, &varError{EBADVAL, name, Array}
	}
	return v, nil
}

func (c *Config) String(name string) (string, error) {
	name = strings.ToLower(name)

	if _, ok := c.m[name]; !ok {
		return "", &varError{ENONE, name, String}
	}

	if c.m[name].Type != String {
		return "", &varError{EBADTYPE, name, String}
	}

	v, ok := c.m[name].Val.(string)
	if !ok {
		return "", &varError{EBADVAL, name, String}
	}

	return v, nil
}


def config = [:]
def loadConfig = { File file ->
    String regex = /^(;{0,1})\s*(\S+)\s*(.*)$/
    file.eachLine { line ->
        (line =~ regex).each { matcher, invert, key, value ->
            if (key == '' || key.startsWith("#")) return
            parts = value ? value.split(/\s*,\s*/) : (invert ? [false] : [true])
            if (parts.size() > 1) {
                parts.eachWithIndex{ part, int i -> config["$key(${i + 1})"] = part}
            } else {
                config[key] = parts[0]
            }
        }
    }
}


import Data.Char
import Data.List
import Data.List.Split

main :: IO ()
main = readFile "config" >>= (print . parseConfig)

parseConfig :: String -> Config
parseConfig = foldr addConfigValue defaultConfig . clean . lines
    where clean = filter (not . flip any ["#", ";", "", " "] . (==) . take 1)

addConfigValue :: String -> Config -> Config
addConfigValue raw config = case key of
    "fullname"       -> config {fullName      = values}
    "favouritefruit" -> config {favoriteFruit = values}
    "needspeeling"   -> config {needsPeeling  = True}
    "seedsremoved"   -> config {seedsRemoved  = True}
    "otherfamily"    -> config {otherFamily   = splitOn "," values}
    _                -> config
    where (k, vs) = span (/= ' ') raw
          key = map toLower k
          values = tail vs

data Config = Config
    { fullName      :: String
    , favoriteFruit :: String
    , needsPeeling  :: Bool
    , seedsRemoved  :: Bool
    , otherFamily   :: [String]
    } deriving (Show)

defaultConfig :: Config
defaultConfig = Config "" "" False False []


procedure main(A)
    ws := ' \t'
    vars := table()
    every line := !&input do {
        line ? {
            tab(many(ws))
            if any('#;') | pos(0) then next
            vars[map(tab(upto(ws)\1|0))] := getValue()
            }
        }
    show(vars)
end

procedure getValue()
    ws := ' \t'
    a := []
    while not pos(0) do {
        tab(many(ws))
        put(a, trim(tab(upto(',')|0)))
        move(1)
        }
    return a
end

procedure show(t)
    every pair := !sort(t) do {
        every (s := pair[1]||" = ") ||:= !pair[2] || ", "
        write(s[1:-2])
        }
end


require'regex'
set=:4 :'(x)=:y'

cfgString=:4 :0
  y set ''
  (1;&,~'(?i:',y,')\s*(.*)') y&set rxapply x
)

cfgBoolean=:4 :0
  y set 0
  (1;&,~'(?i:',y,')\s*(.*)') y&set rxapply x
  if.-.0-:y do.y set 1 end.
)

taskCfg=:3 :0
  cfg=: ('[#;].*';'') rxrplc 1!:1<y
  cfg cfgString 'fullname'
  cfg cfgString 'favouritefruit'
  cfg cfgBoolean 'needspeeling'
  cfg cfgBoolean 'seedsremoved'
  i.0 0
)


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ConfigReader {
    private static final Pattern             LINE_PATTERN = Pattern.compile( "([^ =]+)[ =]?(.*)" );
    private static final Map<String, Object> DEFAULTS     = new HashMap<String, Object>() {{
        put( "needspeeling", false );
        put( "seedsremoved", false );
    }};

    public static void main( final String[] args ) {
        System.out.println( parseFile( args[ 0 ] ) );
    }

    public static Map<String, Object> parseFile( final String fileName ) {
        final Map<String, Object> result = new HashMap<String, Object>( DEFAULTS );
        /*v*/ BufferedReader      reader = null;

        try {
            reader = new BufferedReader( new FileReader( fileName ) );
            for ( String line; null != ( line = reader.readLine() );  ) {
                parseLine( line, result );
            }
        } catch ( final IOException x ) {
            throw new RuntimeException( "Oops: " + x, x );
        } finally {
            if ( null != reader ) try {
                reader.close();
            } catch ( final IOException x2 ) {
                System.err.println( "Could not close " + fileName + " - " + x2 );
            }
        }

        return result;
    }

    private static void parseLine( final String line, final Map<String, Object> map ) {
        if ( "".equals( line.trim() ) || line.startsWith( "#" ) || line.startsWith( ";" ) )
            return;

        final Matcher matcher = LINE_PATTERN.matcher( line );

        if ( ! matcher.matches() ) {
            System.err.println( "Bad config line: " + line );
            return;
        }

        final String key   = matcher.group( 1 ).trim().toLowerCase();
        final String value = matcher.group( 2 ).trim();

        if ( "".equals( value ) ) {
            map.put( key, true );
        } else if ( -1 == value.indexOf( ',' ) ) {
            map.put( key, value );
        } else {
            final String[] values = value.split( "," );

            for ( int i = 0; i < values.length; i++ ) {
                values[ i ] = values[ i ].trim();
            }
            map.put( key, Arrays.asList( values ) );
        }
    }
}


function parseConfig(config) {
    // this expression matches a line starting with an all capital word,
    // and anything after it
    var regex = /^([A-Z]+)(.*)$/mg;
    var configObject = {};

    // loop until regex.exec returns null
    var match;
    while (match = regex.exec(config)) {
        // values will typically be an array with one element
        // unless we want an array
        // match[0] is the whole match, match[1] is the first group (all caps word),
        // and match[2] is the second (everything through the end of line)
        var key = match[1], values = match[2].split(",");
        if (values.length === 1) {
            configObject[key] = values[0];
        }
        else {
            configObject[key] = values.map(function(value){
                return value.trim();
            });
        }
    }

    return configObject;
}


def parse:

  def uc: .name | ascii_upcase;

  def parse_boolean:
    capture( "(?<name>^[^ ] *$)" )
    | { (uc) : true };

  def parse_var_value:
    capture( "(?<name>^[^ ]+)[ =] *(?<value>[^,]+ *$)" )
    | { (uc) : .value };

  def parse_var_array:
    capture( "(?<name>^[^ ]+)[ =] *(?<value>.*)" )
    | { (uc) : (.value | sub(" +$";"") | [splits(", *")]) };

  reduce inputs as $i ({};
    if $i|length == 0 or test("^[#;]") then .
    else . + ($i | ( parse_boolean // parse_var_value // parse_var_array // {} ))
    end);

parse


function readconf(file)
    vars = Dict()
    for line in eachline(file)
        line = strip(line)
        if !isempty(line) && !startswith(line, '#') && !startswith(line, ';')
            fspace  = searchindex(line, " ")
            if fspace == 0
                vars[Symbol(lowercase(line))] = true
            else
                vname, line = Symbol(lowercase(line[1:fspace-1])), line[fspace+1:end]
                value = ',' ∈ line ? strip.(split(line, ',')) : line
                vars[vname] = value
            end
        end
    end
    for (vname, value) in vars
        eval(:($vname = $value))
    end
    return vars
end

readconf("test.conf")

@show fullname favouritefruit needspeeling otherfamily


import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths

data class Configuration(val map: Map<String, Any?>) {
    val fullName: String by map
    val favoriteFruit: String by map
    val needsPeeling: Boolean by map
    val otherFamily: List<String> by map
}

fun main(args: Array<String>) {
    val lines = Files.readAllLines(Paths.get("src/configuration.txt"), StandardCharsets.UTF_8)
    val keyValuePairs = lines.map{ it.trim() }
            .filterNot { it.isEmpty() }
            .filterNot(::commentedOut)
            .map(::toKeyValuePair)

    val configurationMap = hashMapOf<String, Any>("needsPeeling" to false)
    for (pair in keyValuePairs) {
        val (key, value) = pair
        when (key) {
            "FULLNAME"       -> configurationMap.put("fullName", value)
            "FAVOURITEFRUIT" -> configurationMap.put("favoriteFruit", value)
            "NEEDSPEELING"   -> configurationMap.put("needsPeeling", true)
            "OTHERFAMILY"    -> configurationMap.put("otherFamily", value.split(" , ").map { it.trim() })
            else             -> println("Encountered unexpected key $key=$value")
        }
    }
    println(Configuration(configurationMap))
}

private fun commentedOut(line: String) = line.startsWith("#") || line.startsWith(";")

private fun toKeyValuePair(line: String) = line.split(Regex(" "), 2).let {
    Pair(it[0], if (it.size == 1) "" else it[1])
}


#!/bin/ksh

# Read a configuration file

#	# Variables:
#

#	# The configuration file (below) could be read in from a file
#	  But this method keeps everything together.
# e.g. config=$(< /path/to/config_file)

integer config_num=0
config='# This is a configuration file in standard configuration file format
#
# Lines beginning with a hash or a semicolon are ignored by the application
# program. Blank lines are also ignored by the application program.

# This is the fullname parameter
FULLNAME Foo Barber

# This is a favourite fruit
FAVOURITEFRUIT banana

# This is a boolean that should be set
NEEDSPEELING

# This boolean is commented out
; SEEDSREMOVED

# Configuration option names are not case sensitive, but configuration parameter
# data is case sensitive and may be preserved by the application program.

# An optional equals sign can be used to separate configuration parameter data
# from the option name. This is dropped by the parser.

# A configuration option may take multiple parameters separated by commas.
# Leading and trailing whitespace around parameter names and parameter data fields
# are ignored by the application program.

OTHERFAMILY Rhu Barber, Harry Barber'

isComment='#|;'
paraDelim=' |='
boolean="SEEDSREMOVED|NEEDSPEELING"

typeset -T Config_t=(
	typeset -h		'Full name'				fullname
	typeset -h		'Favorite fruit'		favouritefruit
	typeset -h		'Boolean NEEDSPEELING'	needspeeling=false
	typeset -h		'Boolean SEEDSREMOVED'	seedsremoved=false
	typeset -a -h	'Other family'			otherfamily

	function set_name {
		typeset fn ; fn=$(echo $1)	# Strip any leading/trailing white space
		_.fullname="${fn}"
	}

	function set_fruit {
		typeset fruit ; fruit=$(echo $1)
		_.favouritefruit="${fruit}"
	}

	function set_bool {
		typeset bool ; typeset -u bool=$1

		case ${bool} in
			NEEDSPEELING) _.needspeeling=true ;;
			SEEDSREMOVED) _.seedsremoved=true ;;
		esac
	}

	function set_family {
		typeset ofam ; ofam=$(echo $1)
		typeset farr i ; typeset -a farr ; integer i

		oldIFS="$IFS" ; IFS=',' ; farr=( ${ofam} ) ; IFS="${oldIFS}"
		for ((i=0; i<${#farr[*]}; i++)); do
			_.otherfamily[i]=$(echo ${farr[i]})
		done
	}
)

#	# Functions:
#

#	# Function _parseconf(config) - Parse uncommented lines
#
function _parseconf {
	typeset _cfg ; _cfg="$1"
	typeset _conf ; nameref _conf="$2"

	echo "${_cfg}" |	\
	while read; do
		[[ $REPLY == @(${isComment})* ]] || [[ $REPLY == '' ]] && continue
		_parseline "$REPLY" _conf
	done
}

function _parseline {
	typeset _line ; _line=$(echo $1)
	typeset _conf ; nameref _conf="$2"
	typeset _param _value ; typeset -u _param

	_param=${_line%%+(${paraDelim})*}
	_value=${_line#*+(${paraDelim})}

	if [[ ${_param} == @(${boolean}) ]]; then
		_conf.set_bool ${_param}
	else
		case ${_param} in
			FULLNAME)		_conf.set_name "${_value}" ;;
			FAVOURITEFRUIT)	_conf.set_fruit ${_value} ;;
			OTHERFAMILY)	_conf.set_family "${_value}" ;;
		esac
	fi
}
 ######
# main #
 ######

typeset -a configuration		# Indexed array of configurations
Config_t configuration[config_num]
_parseconf "${config}" configuration[config_num]

for cnum in ${!configuration[*]}; do
	printf "fullname = %s\n" "${configuration[cnum].fullname}"
	printf "favouritefruit = %s\n" ${configuration[cnum].favouritefruit}
	printf "needspeeling = %s\n" ${configuration[cnum].needspeeling}
	printf "seedsremoved = %s\n" ${configuration[cnum].seedsremoved}
	for ((i=0; i<${#configuration[cnum].otherfamily[*]}; i++)); do
		print "otherfamily($((i+1))) = ${configuration[cnum].otherfamily[i]}"
	done
done


local(config = '# This is a configuration file in standard configuration file format
#
# Lines beginning with a hash or a semicolon are ignored by the application
# program. Blank lines are also ignored by the application program.

# This is the fullname parameter

FULLNAME Foo Barber

# This is a favourite fruit
FAVOURITEFRUIT = banana

# This is a boolean that should be set
NEEDSPEELING

# This boolean is commented out
; SEEDSREMOVED

# Configuration option names are not case sensitive, but configuration parameter
# data is case sensitive and may be preserved by the application program.

# An optional equals sign can be used to separate configuration parameter data
# from the option name. This is dropped by the parser.

# A configuration option may take multiple parameters separated by commas.
# Leading and trailing whitespace around parameter names and parameter data fields
# are ignored by the application program.

OTHERFAMILY Rhu Barber, Harry Barber
')
// if config is in a file collect it like this
//local(config = file('path/and/file.name') -> readstring)

define getconfig(term::string, config::string) => {

	local(
		regexp	= regexp(-find = `(?m)^` + #term + `($|\s*=\s*|\s+)(.*)$`, -input = #config, -ignorecase),
		result
	)

	while(#regexp -> find) => {
		#result = (#regexp -> groupcount > 1 ? (#regexp -> matchString(2) -> trim& || true))
		if(#result -> asstring >> ',') => {
			#result = #result -> split(',')
			#result -> foreach => {#1 -> trim}
		}
		return #result
	}
	return false

}

local(
	fullname	= getconfig('FULLNAME', #config),
	favorite	= getconfig('FAVOURITEFRUIT', #config),
	sedsremoved	= getconfig('SEEDSREMOVED', #config),
	needspeel	= getconfig('NEEDSPEELING', #config),
	otherfamily	= getconfig('OTHERFAMILY', #config)
)

#fullname
'
'
#favorite
'
'
#sedsremoved
'
'
#needspeel
'
'
#otherfamily
'
'


dim confKeys$(100)
dim confValues$(100)

optionCount = ParseConfiguration("a.txt")

fullName$ = GetOption$( "FULLNAME", optionCount)
favouriteFruit$ = GetOption$( "FAVOURITEFRUIT", optionCount)
needsPeeling = HasOption("NEEDSPEELING", optionCount)
seedsRemoved = HasOption("SEEDSREMOVED", optionCount)
otherFamily$ = GetOption$( "OTHERFAMILY", optionCount)  'it's easier to keep the comma-separated list as a string

print "Full name: "; fullName$
print "likes: "; favouriteFruit$
print "needs peeling: "; needsPeeling
print "seeds removed: "; seedsRemoved

print "other family:"
otherFamily$ = GetOption$( "OTHERFAMILY", optionCount)
counter = 1
while word$(otherFamily$, counter, ",") <> ""
    print counter; ". "; trim$(word$(otherFamily$, counter, ","))
    counter = counter + 1
wend
end

'parses the configuration file, stores the uppercase keys in array confKeys$ and corresponding values in confValues$
'returns the number of key-value pairs found
function ParseConfiguration(fileName$)
    count = 0
    open fileName$ for input as #f
    while not(eof(#f))
        line input #f, s$
        if not(Left$(s$,1) = "#" or Left$( s$,1) = ";" or trim$(s$) = "") then  'ignore empty and comment lines
            s$ = trim$(s$)
            key$ = ParseKey$(s$)
            value$ = trim$(Mid$(s$,len(key$) + 1))
            if Left$( value$,1) = "=" then value$ = trim$(Mid$(value$,2))  'optional =
            count = count + 1
            confKeys$(count) = upper$(key$)
            confValues$(count) = value$
        end if
    wend
    close #f
    ParseConfiguration = count
end function

function ParseKey$(s$)
    'key is the first word in s$, delimited by whitespace or =
    s$ = word$(s$, 1)
    ParseKey$ = trim$(word$(s$, 1, "="))
end function

function GetOption$( key$, optionCount)
    index = Find.confKeys( 1, optionCount, key$)
    if index > 0 then GetOption$ =(confValues$(index))
end function

function HasOption(key$, optionCount)
    HasOption = Find.confKeys( 1, optionCount, key$) > 0
end function

function Find.confKeys( Start, Finish, value$)
    Find.confKeys = -1
    for i = Start to Finish
        if confKeys$(i) = value$ then Find.confKeys = i : exit for
    next i
end function


conf = {}

fp = io.open( "conf.txt", "r" )

for line in fp:lines() do
    line = line:match( "%s*(.+)" )
    if line and line:sub( 1, 1 ) ~= "#" and line:sub( 1, 1 ) ~= ";" then
 	option = line:match( "%S+" ):lower()
	value  = line:match( "%S*%s*(.*)" )

	if not value then
 	    conf[option] = true
	else
	    if not value:find( "," ) then
		conf[option] = value
	    else
		value = value .. ","
		conf[option] = {}
		for entry in value:gmatch( "%s*(.-)," ) do
		    conf[option][#conf[option]+1] = entry
		end
	    end
	end

    end
end

fp:close()

print( "fullname = ", conf["fullname"] )
print( "favouritefruit = ", conf["favouritefruit"] )
if conf["needspeeling"] then print( "needspeeling = true" ) else print( "needspeeling = false" ) end
if conf["seedsremoved"] then print( "seedsremoved = true" ) else print( "seedsremoved = false" ) end
if conf["otherfamily"] then
    print "otherfamily:"
    for _, entry in pairs( conf["otherfamily"] ) do
	print( "", entry )
    end
end


ClearAll[CreateVar, ImportConfig];
CreateVar[x_, y_String: "True"] := Module[{},
  If[StringFreeQ[y, ","]
   ,
   ToExpression[x <> "=" <> y]
   ,
   ToExpression[x <> "={" <> [email protected][StringSplit[y, ","], ","] <> "}"]
   ]
  ]
ImportConfig[configfile_String] := Module[{data},
  (*data = ImportString[configfile, "List", "Numeric" -> False];*)
  data=Import[configfile,"List","Numeric"\[Rule]False];

  data = StringTrim /@ data;
  data = Select[data, # =!= "" &];
  data = Select[data, ! StringMatchQ[#, "#" | ";" ~~ ___] &];
  data = If[! StringFreeQ[#, " "], StringSplit[#, " ", 2], {#}] & /@ data;

  CreateVar @@@ data;
 ]
ImportConfig[file]


function R = readconf(configfile)
% READCONF reads configuration file.
%
% The value of boolean parameters can be tested with
%    exist(parameter,'var')

if nargin<1,
   configfile = 'q.conf';
end;

fid = fopen(configfile);
if fid<0, error('cannot open file %s\n',a); end;

while ~feof(fid)
    line = strtrim(fgetl(fid));
    if isempty(line) || all(isspace(line)) || strncmp(line,'#',1) || strncmp(line,';',1),
	; % no operation
    else
	[var,tok] = strtok(line,' \t=');
	var = upper(var);
	if any(tok==','),
		k = 1;
		while (1)
			[val, tok]=strtok(tok,',');
			R.(var){k} = strtrim(val);  	% return value of function
			eval(sprintf('%s{%i}=''%s'';',var,k,strtrim(val)));  % stores variable in local workspace
		if isempty(tok), break; end;
			k=k+1;
		end;
	else
		tok = strtrim(tok);
		R.(var) = tok;		% return value of function
		eval(sprintf('%s=''%s''; ',var,tok));  % stores variable in local workspace
	end;
    end;
end;
fclose(fid);
whos,     % shows the parameter in the local workspace