# New Ticket Created by  emilbarton 
# Please include the string:  [perl #126205]
# in the subject line of all future correspondence about this issue. 
# <URL: https://rt.perl.org/Ticket/Display.html?id=126205 >


Hi,
I'm facing this error and don't understand why. I'm not able to tell if
its a bug or something wrong in my code. My Perl6 version is : 
2015.07.1-185-gd8ef5ea built on MoarVM version 2015.07-108-g7e9f29e.
I've asked on #perl6 at freenode and didn't get an answer. There is a
gist at https://gist.github.com/emilbarton/41d3e7094dc0d6115c00 with
the same files as sent here (see the example at EOF for error
reproduction). The program I'm writing is my first in Perl6 and its
purpose is to serve and query Sqlite3 dbs. I erased as much as I could
of my code to let the bare error show, which doesn't make my attempted
script look very consistent.

Thank you in advance if you can tell me how to overcome this problem.

emilbarton
#!/usr/bin/env perl6

use v6;
use Terminal::ANSIColor;
use Readline;

## General variables:

if @*ARGS.elems < 3 { die "Usage: mytest maindb user password [sessionid] " }

my $sessionId = "0";
my $mainDb = @*ARGS[0];
my $User = @*ARGS[1];
my $Passwd = @*ARGS[2];
if @*ARGS[3] { $sessionId = @*ARGS[3] };


my ($s1,$s2,$s3);
my (@a1,@a2,@a3);
my (%h1, %h2, %h3, %Runtime);


## Server variables:
my $Name = 'mytest';
my $Host = "localhost";
my $Port = "6789";
my $u6Host = "localhost";
my Int $u6Port = 9876;
my $localHost = "localhost";
my $localTime = "+2:00:00";
my @Base = "db1", "db2";
my $debFlag = 1;
my $debCnt = 0;
## END Server variables.

## message variables:
my $DATA_SEP = "\x[001F]";
my $RES_SOH = "\x[0001]";
my $RES_STX = "\x[0002]";
my $Rcnt = 1;
## END message variables.

## Readline:
my $Readline = Readline.new;
## END Readline
my $Moment = DateTime.new(now);
say color('reset') && color('underline red'), $Moment~$localTime, color('reset');

## Read-Eval Loop:
while my $Input = $Readline.readline( "$Name? " ) {
    my $sub = "main_loop_";
    given $Input {
        when /:i^\s*quit\s*$/  { exit 0 } # END quit
        default {
            my $res = test($Input);
            say color('cyan'), $res, color('reset');
        } # END
    } # END given $response
} # END while
## END Read-Eval Loop.

## Subroutines:

## Debug some data:
sub deb_say($input,$orig){
    my $sub = "deb_say_";
    my $res = 0;
    if $debFlag  != 0 {
        $res = $debCnt;
        say $input ~ " -- at " ~ $orig ~ ": " ~ ++$debCnt ~ " (d=" ~ $debFlag ~ ")." ;
    }
    return ($res);
} ## END deb_say()

## Extract a result string:
sub extract_res ($str) {
  my $sub = "extract_res_";
  $str ~~ /^ (.*) $RES_SOH $RES_STX (.*)$/ ;  #m:
  my $header = $0; my $row = $1;
  my @headers = split $RES_SOH, $header;
  my @rows; my $n = 0;
  while $row ~~ /^(<-[\x0002]>*)[\x0002].*$/ { #m: $RES_STX : char class doesn't interpolate.
    @rows.push("$0\t");
    $n++;
    $row ~~ s/^<-[\x0002]>*[\x0002](.*)$/$0/; #s:s:
    # say $row ~ "\n***\n";
    if $n == @headers.elems { @rows.push("\n") ; $n = 0; }
  }
  @rows.push($row) unless @rows.elems > 0;
  return @headers.join("\t") ~ "\n===\n" ~ @rows.join("");
} ## END extract_res().

## get_instant:
sub get_instant($input){
    my $sub = "get_instant_";
    my $res = $input;
    $res ~~ s:i/^Instant\:(.+)$/$0/; #s:i/:
    return ($res);
} ## END get_instant()

## Add separator chars:
sub tab_level ($n, $char) {
    my $sub = "tab_level_";
    my $seq;
	$char = " " unless ($char.length > 0);
	loop (my $c = 0; $c < $n; $c++) { $seq .= $char }
	$seq ~= " " unless ($seq ~~ /$char/);
	return $seq;
} ## END tab_level()


sub test ($input) {
    my $sub = "test_";
    my $conn = IO::Socket::INET.new(:host($u6Host), :port($u6Port));
    $conn.print: "$Host$DATA_SEP$Port$DATA_SEP$mainDb$DATA_SEP$User$DATA_SEP$Passwd$DATA_SEP$sessionId$DATA_SEP$input";
    my $ret = $conn.recv;
    $conn.close;
    return $ret;
} ## END test().

## END Subroutines.

=finish

Example:

run:
$ perl6 150927-problem_server.pl
    in one terminal window, then:
$ perl6 150927-problem_client.pl db1 admin adminp
    in another terminal window, then there type:
$ test 1
    and see the resulting error in the first window:

    2015-09-27T11:38:32Z+2:00:00
    localhost|6789|db1|admin|adminp|0|test 1 -- at _parse_com_1: 1 (d=1).
    2015-09-27T11:42:10Z+2:00:00 -- localhost : db1 : admin : 0 : test 1
    recording new user..
    localhost,6789,db1,admin,adminp -- at _record_user_2: 2 (d=1).
    admin@db1 -- at Runtime_test_user_1: 3 (d=1).
    Cannot assign to a readonly variable or a value
      in block  at 150927-problem.pl:187
      in method test_user at 150927-problem.pl:184
      in sub record_user at 150927-problem.pl:340
      in block  at 150927-problem.pl:242
      in block <unit> at 150927-problem.pl:230
#!/usr/bin/env perl6

use v6;
use Terminal::ANSIColor;
use Readline;

## General variables:
my ($s1,$s2,$s3,$Moment);
my (@a1,@a2,@a3);
my (%h1, %h2, %h3, %Runtime);

## Server variables:
my @publicBase = ("db1", "db2");
my Int $publicPort = 9876;
my $DATA_SEP = "\x[001F]";
my $RES_SOH = "\x[0001]";
my $RES_STX = "\x[0002]";
our $localTime = "+2:00:00";
our $localHost = "localhost";
our @privateBase;
our $privateHost = "localhost";
our $privatePort = "8765";
our $privateUser = "admin";
our $privatePasswd = "adminp";
our $debFlag = 1;
our $debCnt = 0;
## END Server variables.

our regex sql { <[\w\s\,\;\:\!\?\'\+\-\*\°\@\#\(\)\/\\]> };
our regex prohibited { <-[\w\s\,\;\:\!\?\'\"\+\-\*\°\@\#\(\)\/\\]> };
our regex dquote { <[\"]> };
our regex squote { <[\']> };
class Account { # Unid user accounts are needed to avoid useless user tests.
    has $.id;
    has $.host;
    has $.port;
    has $.state;
    has $.db;
    has $.permission;
    has $.passwd;
    method update($h, $p, $db, $pw, $per, $sta){
        my $methname = "_update_";
        $!host = $h;
        $!port = $p;
        $!db = $db;
        $!passwd = $pw;
        $!permission = $per unless $per !~~ /\w+/;
        $!state = $sta unless $sta !~~ /\w+/;
        return self;
    } ## END method update
} ## END class Account
class Host {
    has $.host;
    has $.port;
    has $.db;
} ## END class Host
class Message {
    has $!input;
    has $!session;
    has $!user;
    has $!host;
    has $!port;
    has $!base;
    has $!mode;
    has $!password;
    has Bool $.correct;
    has @.output;
    submethod BUILD(:$input, :$host, :$base, :$port, :$session, :$user, :$password){
        my $methname = "_BUILD_";
        $!input := $input;
        $!session := $session;
        $!host := $host;
        $!base := $base;
        $!port := $port;
        $!user := $user;
        $!password := $password;
        if $session == 0 {
            $!correct := False;
            push @!output, "invalid sessionid";
            return;
        }
        my @checked = self.check($input);
        if @checked.elems > 0 {
            $!correct := True;
        }
        else {
            $!correct := False;
            # throw an exception otherwise the rest fails;
        }
        deb_say("correct:"~$!correct,self.^name~$methname);
    } ## END submethod BUILD
    method check($m){
        my $methname = "_check_";
        deb_say("checking:"~$m,self.^name~$methname);
        my @checked;
        given $m {
            my @matches = m:global/<dquote>+/; #m:
            if @matches.elems % 2 !== 0 {
                deb_say("odd number of double quotes",self.^name~$methname);
                push @!output, "odd number of double quotes: "~@matches;
                $!correct = False;
                return @checked;
            }
        }
        if $m ~~ /\w+/ {
            unshift @checked, $m;
        }
        else {
            deb_say("invalid message",self.^name~$methname);
            push @!output, "invalid message";
            $!correct = False;
            return @checked;
        }
        #$m ~~ s:g:i / (<![\"]>*)(\")(<![\"]>*)(\")(<![\"]>*)/$0%Unid_In<rep_dquot>$2%Unid_In<rep_dquot>$4/;#s:
        # other checkings here
        deb_say("checked: "~@checked.elems~" clauses; ",self.^name~$methname);
        # other uncorrectness results treated by Clause;
        @checked;
    } ## END method check
} ## END class Message
class User {
    has $.id;
    has $.session;
    has $.state;
    has Account @.account;
    method add_account($u,$h,$p,$db,$pw){
        my $methname = "_add_account_";
        push @!account, Account.new(id => $u, host => $h, port => $p, db => $db, passwd => $pw );
    } ## END method add_account
    method get_account($u){
        my $methname = "_get_account_";
        my $out = 0;
        if @!account.elems > 0 {
            for @!account { if $_.id ~~ /^<$u>$/ { $out = $_; last } }
        }
        return $out;
    } ## END method get_account
    method update_account($u, $h, $p, $db, $pw, $per, $sta){
        my $methname = "_update_account_";
        my $account = 0;
        if @!account.elems > 0 {
            for @!account { if $_.id ~~ /^<$u>$/ { $account = $_; last } }
        }
        unless $account == 0 { return $account.update($u, $h, $p, $db, $pw, $per, $sta) }
        $account;
    } ## END method update_account
} ## END class User
class Runtime {
    has $.start;
    has User @.user;
    method add_user ($s, $u, $h, $p, $db, $pw) {
        my $methname = "_add_user_";
        my $user = User.new(id => $u, session => $s, state => "new");
        push @!user, $user;
        $user.add_account($u, $h, $p, $db, $pw);
    } ## END method add_user
    method get_user ($u) {
        my $methname = "_get_user_";
        my $out = 0;
        if @!user.elems > 0 {
            for @!user { if $_.id ~~ /^<$u>$/ { $out = $_; last } }
        }
        return $out;
    } ## END method get_user
    method test_user($host,$port,$base,$user,$pw){
      my $methname = "_test_user_";
      my @auth =(1,2,3);
      if @auth.elems > 2 {
        my $test = "test OK: user not logged in: "~@!user.elems;
        if @!user.elems > 0 {
            for @!user {
                deb_say($_.id,self.^name~$methname~"1");
                if $_.id ~~ /^<$user>$/ {
                    $test = "test OK: user logged in: "~@!user.elems;
                    last;
                } # END if $_.id
            } # END for @!user
        } # END if @!user.elems > 0
        deb_say($test,self.^name~$methname~"3");
      } # END if @auth.elems > 2
      else { say("test: KO "~@auth.elems~" "~self.^name~$methname~"1") }
      return @auth;
    } ## END method test_user
    method update_user ($i, $s, $u, $h, $p, $db, $pw, $per, $sta) {
        my $methname = "_update_user_";
        my $user = 0;
        if @!user.elems > 0 {
            for @!user { if $_.id ~~ /^<$u>$/ { $user = $_; $user.session = $i; $user.state = $s; last } }
        }
        unless $user == 0 { $user.update_account($u, $h, $p, $db, $pw, $per, $sta) }
        return $user;
    } ## END method update_user
} ## END class Runtime
## Readline:
my $Readline = Readline.new;
## END Readline
## Time
$Moment = DateTime.new(now);
say color('reset') && color('underline red'), $Moment~$localTime, color('reset');
## END Time
## Runtime
my $Run = Runtime.new(start => $Moment~$localTime);
push @privateBase, Host.new(db => $_, host => $privateHost, port => $privatePort ) for @publicBase;
## END Log
## Connection
my $Listen = IO::Socket::INET.new(:listen, :localport($publicPort));
## END Connection
## Read-Eval Loop:
loop { ## INET.pm
    my $subname = '_main_loop_';
    my $conn = $Listen.accept;
    while my $input = $conn.recv(:bin) {
        my $com = $input.decode('UTF-8');
        my ($reqhost, $reqport, $reqdb, $requser, $reqpw, $reqsession, $request) := parse_com($com);
        say DateTime.new(now)~$localTime~" -- "~$reqhost~" : "~$reqdb~" : "~$requser~" : "~$reqsession~" : "~$request;
        # Open or append to session (->subdb users):
        my ($sessionId, $lastCom);
        my $recid = "0";
        if $recid ~~ /^0$/ || $recid !~~ /\S+/ {
            say "recording new user..";
            $sessionId = record_user($requser,$reqhost,$reqport,$reqdb,$reqpw,$request);
        }
        else {
            my $mode = $sessionId = "0";
            if $Run.get_user("$requser\@$reqdb") !== 0 { # TODO: Handle also the case where user has closed session:
                $sessionId = $Run.get_user("$requser\@$reqdb").session;
                $mode = 1; ## Fresh session.
            } # Update entry creating or not a new sessionId (has the user passed an old sessionId?).
            if $reqsession !~~ /^0$/ && $Run.get_user("$requser\@$reqdb").state !~~ /^fresh$/ {
                unless $Run.get_user("$requser\@$reqdb").test_session($reqsession) ~~ /^0$/ {
                    $sessionId = $_;
                    $mode = 2; ## Old session.
                }
            }
            say "updating user entry..$recid";
            $sessionId = update_user($recid,$requser,$reqhost,$reqport,$reqdb,$reqpw,$request,$sessionId,$mode);
        }
       	my $mess = Message.new(input => $request, session => $sessionId, user => $requser, password => $reqpw, host => $reqhost, base => $reqdb, port => ~$reqport);
        my $res = "session id:"~$sessionId ~ "\n";
       	if $mess.correct == False {
            deb_say("invalid message",$subname~"2");
            deb_say("session id:"~$sessionId,$subname~"3");
        }
        # else { $mess.compute(); }
        $res = $res ~ $_ ~ "\n" for $mess.output;
        $conn.print: $res ;
    } ## END while $input
    $conn.close;
} ## END Read-Eval Loop.
## Subroutines:
## Debug some data:
sub deb_say($input,$orig){
    my $subname = '_deb_say_';
    my $res = 0;
    if $debFlag  != 0 {
        $res = $debCnt;
        say $input ~ " -- at " ~ $orig ~ ": " ~ ++$debCnt ~ " (d=" ~ $debFlag ~ ")." ;
    }
    return ($res);
} ## END deb_say()
## Extract a result string: (the data structure has to be known beforehand)
sub extract_res ($str) {
  my $subname = '_extract_res_';
  unless $str ~~ /^ (.*) $RES_SOH $RES_STX (.*)$/ { return "0" }  #m:
  my $header = $0; my $row = "$1";
  my @headers = split $RES_SOH, $header;
  my @rows; my $n = 0;
  while $row ~~ /^(<-[\x0002]>*)[\x0002].*$/ { #m: $RES_STX : char class doesn't interpolate.
    @rows.push("$0\t");
    $n++;
    $row ~~ s/^<-[\x0002]>*[\x0002](.*)$/$0/; #s:s:
    # say $row ~ "\n***\n";
    if $n == @headers.elems { @rows.push("\n") ; $n = 0; }
  }
  @rows.push($row) unless @rows.elems > 0;
  return @rows;
} ## END extract_res().

sub get_instant($input){
    my $sub = "get_instant_";
    my $res = $input;
    $res ~~ s:i/^Instant\:(.+)$/$0/; #s:i/:
    return ($res);
} ## END get_instant()

## Parse an incoming connection:
sub parse_com($input){
    my $subname = '_parse_com_';
    $input ~~ /^(\w+)$DATA_SEP(\d+)$DATA_SEP(\w+)$DATA_SEP(\w+)$DATA_SEP(\w+)$DATA_SEP(.+)$DATA_SEP(.+)$/;
    my $reqhost = $0;
    my $reqport = $1;
    my $reqdb = $2;
    my $requser = $3;
    my $reqpassword = $4;
    my $reqsession = $5;
    my $request = $6;
    deb_say($reqhost~"|"~$reqport~"|"~$reqdb~"|"~$requser~"|"~$reqpassword~"|"~$reqsession~"|"~$request,$subname~"1");
    return ($reqhost,$reqport,$reqdb,$requser,$reqpassword,$reqsession,$request);
} ## END parse_com


## Create a new user entry
sub record_user ($requser,$reqhost,$reqport,$reqdb,$reqpw,$request){
    my $subname = '_record_user_';
    my $sessionid = get_instant(now);
    deb_say("$reqhost,$reqport,$reqdb,$requser,$reqpw",$subname~"2");
    ## Let's record user info before testing it:
    $Run.add_user($sessionid, "$requser\@$reqdb", $reqhost, $reqport, $reqdb, $reqpw);
    $sessionid = 0 unless $Run.test_user($reqhost,$reqport,$reqdb,$requser,$reqpw).elems > 2 ; #m: $RES_STX : char class doesn't interpolate.;
    return $sessionid;
} ## END record_user()

## Update user entry
sub update_user ($recid,$requser,$reqhost,$reqport,$reqdb,$reqpw,$request,$reqsession,$mode){
    my $subname = '_update_user_';
    my ($out, $req, $symid, $test);
    my $sessionid = $reqsession;
    $test = 0;
    # sessionid can be an old session or 0 but also a fresh one.
    # was sessionid provided by user?
    # mode=0:new, mode=1:fresh, mode=2:old.
    if $sessionid ~~ /^0$/  or $test == 1 {
        $sessionid = get_instant(now) if $mode == 0;
        say "session for "~$requser~"\@"~$reqdb~":"~$reqsession;
        unless $mode == 1 {
            return 0 unless $Run.test_user($reqhost,$reqport,$reqdb,$requser,$reqpw).elems > 2;
        }
    }
    $Run.update_user($sessionid,"fresh","$requser\@$reqdb",$reqhost,$reqport,$reqdb,$reqpw,'','');
    return $sessionid;
} ## END update_user()

## END Subroutines.

=finish

Example:

run:
$ perl6 150927-problem_server.pl
    in one terminal window, then:
$ perl6 150927-problem_client.pl db1 admin adminp
    in another terminal window, then there type:
$ test 1
    and see the resulting error in the first window:

    2015-09-27T11:38:32Z+2:00:00
    localhost|6789|db1|admin|adminp|0|test 1 -- at _parse_com_1: 1 (d=1).
    2015-09-27T11:42:10Z+2:00:00 -- localhost : db1 : admin : 0 : test 1
    recording new user..
    localhost,6789,db1,admin,adminp -- at _record_user_2: 2 (d=1).
    admin@db1 -- at Runtime_test_user_1: 3 (d=1).
    Cannot assign to a readonly variable or a value
      in block  at 150927-problem.pl:187
      in method test_user at 150927-problem.pl:184
      in sub record_user at 150927-problem.pl:340
      in block  at 150927-problem.pl:242
      in block <unit> at 150927-problem.pl:230

Reply via email to