Perl: Split a string on a delimiter, but respect single and double quotes with the delimiter

It's a common practice to split a string on a delimiter like a comma or semi-colon. If your input data contains the delimiter in quotes you need to respect that and not split there. I wrote a simple function to handle 99% of the use cases for splitting in this manner.

quote_split(qq{foo, bar, baz});  # ('foo', 'bar', 'baz')
quote_split(qq{"That's, mine"}); # ("That's mine")
# Split a string on commas, but respect single and double quotes with commas
sub quote_split {
    my ($str, $separator) = @_;
    $separator //= ",";
    if (!length($separator)) {
        die("quote_split separator cannot be empty\n");
    }

    my $separator_re = quotemeta($separator);
    my @items;

    while ($str =~ /\G\s*(?:
        "((?:\\.|[^"\\])*)"   # Double-quoted
        | '((?:\\.|[^'\\])*)' # Single-quoted
        | ((?:(?!$separator_re)[\s\S])+) # Unquoted
    )\s*(?:$separator_re|\z)/gcx) {

        my $item = $1 // $2 // $3;

        # Unescape quoted values
        $item =~ s/\\(['"\\])/$1/g;

        push(@items, $item);
    }

    return @items;
}
Tags:
Leave A Reply

Perl: Basic YAML parsing in a copy/pasteable function

I need very basic YAML parsing in Perl, mostly for reading configuration files. There aren't any great options in Perl core, and I only need a subset of YAML (basic scalars, arrays, and hashes), so I worked up a simple copy/pasteable function.

my $x = yaml_parse($yaml_str);

This implementation supports: nesting, scalars, arrays, and hashes, but is missing null / ~, booleans, and complex quotes.

sub yaml_parse {
    return {} unless defined $_[0] && length $_[0];
    my %data; my $root = \%data; my @st = ([-1, \$root]);
    for my $line (split /\n/, $_[0]) {
        $line =~ s/\r$//;
        next if $line =~ /^\s*(?:$|---\s*$|#)/;
        if ($line =~ /^(\s*)-\s*(.*)$/) {
            my ($n, $v) = (length($1), $2);
            pop @st while $n < $st[-1][0];
            my $cur = ${$st[-1][1]};
            die "yaml_parse: array without parent: $line" if @st == 1;
            $cur = ${$st[-1][1]} = [] if ref $cur eq 'HASH' && !%$cur;
            die "yaml_parse: mixed array/hash" if ref $cur ne 'ARRAY';
            $v =~ s/^\s+|\s+$//g; $v =~ s/^(['"])(.*)\1$/$2/s;
            $v += 0 if $v =~ /^-?\d+(?:\.\d+)?$/;
            push(@$cur, $v);
        } elsif ($line =~ /^(\s*)([\w\-\.\/]+)\s*:\s*(.*)$/) {
            my ($n, $key, $v) = (length($1), $2, $3);
            pop @st while $n <= $st[-1][0];
            my $cur = ${$st[-1][1]};
            die "yaml_parse: '$key' under array" if ref $cur eq 'ARRAY';
            $v =~ s/\s+$//; $v =~ s/^\s+//; $v =~ s/^(['"])(.*)\1$/$2/s;
            if ($v =~ /^\[(.*)\]$/) {
                my @a = grep { length } map { s/^\s+|\s+$//gr =~ s/^(['"])(.*)\1$/$2/sr } split /,/, $1;
                for (@a) { $_ += 0 if /^-?\d+(?:\.\d+)?$/ }
                $v = \@a;
            } else { $v += 0 if $v =~ /^-?\d+(?:\.\d+)?$/; }
            if (ref $v or length $v) { $cur->{$key} = $v }
            else { $cur->{$key} = {}; push(@st, [$n, \$cur->{$key}]) }
        } else { warn "yaml_parse: ignoring: $line\n" }
    } return \%data;
}

YAML::XS is definitely the best Perl YAML parser, but it can be overkill if all you need is simple parsing.

Tags:
Leave A Reply

Javascript: Sluz sandbox

I ported my Sluz templating engine from PHP to JavaScript. It's now possible to run a full Sluz installation entirely in-browser. As a proof-of-concept I ported the Sluz Sandbox to use the JS version of library. Now you can test and validate Sluz code 100% in-browser.

Tags:
Leave A Reply

Hardware required to run on frontier level AI model

AI models like Claude Opus and ChatGPT 5.6 are called "frontier" (i.e. top of the line). There are several fully open source frontier level AI models that are available to download for free. I ran the specs on what it would take to run a frontier level model.

For inference of a frontier model (400B-1T+ params):

  • Minimum (4-bit quantized): ~200-500GB VRAM -> 4-8x H100 80GB GPUs
  • FP16/half precision: ~800GB-2TB VRAM -> 10-32x H100 80GB GPUs
  • System RAM: 512GB+
  • Interconnect: NVLink or InfiniBand between GPUs
  • Storage: Several hundred GB for model weights For training: 10,000-100,000+ GPU-hours on H100-class hardware, multi-million dollar cluster, weeks of continuous run time. No single consumer GPU can run a frontier model - even an RTX 4090 (24GB) is about 10-30x short of VRAM needed for even a heavily quantized frontier model.

If you just want to host a "good" AI model it requires 8x GPUs with 80GB of VRAM each.

It's about $300k to get in the door for a SINGLE server to run AI you can ask questions

Tags:
Leave A Reply

Perl: romuduojr PRNG

Another day, another PRNG ported to Perl. Today is romuduojr from romu-random.org. Pretty simple 64bit PRNG.

Tags:
Leave A Reply

Perl: A simple module that doubles as a script

I have a small Perl script that defines a handful of functions. Perl allows you to make a library that doubles as a script when called directly. Using caller() you can determine if your script was loaded via a require() call, or called directly. This allows you to export functions if called as a library, but run code if called directly via: perl my_lib.pl.

# my_lib.pl
if (!caller()) {
    say greet("Scott");
}

sub greet {
    my $name = shift();

    return "Hello $name";
}

1; # Required if you load as a library

Use a require() call to load the module and get access to the greet() function.

# main.pl
require("/path/my_lib.pl");

say greet("Foo");
Tags:
Leave A Reply

Summary of each book in the Odyssey

The Odyssey by Homer

  1. Odysseus's house is full of suitors eating all his food and drinking his wine waiting for Athena to choose a new husband
  2. Telemachus calls a council of suitors to try and get rid of them, but the mock him and decide to stay
  3. Telemachus sails to Pylos to meet King Nestor and ask about his father's Whereabouts
  4. Telemachus reaches Sparta and learns his father is trapped on Calypso's island. The suitors plan ambush and kill Telemachus when he returns
  5. Odysseus builds a raft and sails for 17 days besieged by Poseidon's wrath. The raft is destroyed and he swims to shore.
  6. Nausicaa finds Odysseus and takes him to her father's house for a weird washing party. She directs him to her royal parents.
  7. The king senses Odysseus greatness and offers his daughter in marriage but Odysseus declines and only wants to return home.
  8. The Phaeacian compete in sports style games and recruit Odysseus. He says he is too old and worn out from the war.
  9. Odysseus gets captured by a huge cyclops and kept in a cave. Odysseus blinds the Cyclops with a flame stick and escapes to his boat taunting the cyclops as he leaves.
  10. Circe turns Odysseus's men into pigs and tempts Odysseus into her bed. He makes her swear an oath she won't harm him on his quest. She tells them they must go to the underworld next.
  11. Odysseus travels to the underworld and meets on his dead crewmen who fell off Circe's roof and broke his neck. He also meets a ghost who looks like his mother and it causes him grief.
  12. Odysseus is warned about the Siren's call. He tells his men to bind him to the mast and ignore his pleas for freedom. The men put wax in their ears and row past the Sirens. His men, starving, slaughter a sacred cow so Zeus destroys their ship and drowns everyone except Odysseus.
  13. Odysseus makes it back to Ithaca, but he doesn't recognize the location. Athena transforms him into a shepherd and tells him to go see the man in charge of his pigs.
  14. Odysseus meets the swineherd who welcomes him warmly without recognizing him. They share a meal and stories of his travels.
  15. Telemachus heads back to Ithaca after Athena warns him of the suitors ambush. Odysseus reveals his identity to the swineherd and they head to Oddysseus's home.
  16. Telemachus arrives at the swineherds home and meets his father who is still disguised. The reunite and plot to murder the suitors.
  17. The three travel to Odysseus's home and the goatherd mocks and kicks him. Odysseus ignores him and meets his dog who recognizes him even in disguise. The suitors assault and insult the disguised Odysseus.
  18. A beggar arrives at the house and challenges Odysseus, but Odysseus knocks his down pretty easily. Eurymachus throws a stool at Odysseus, while he observes the suitors behavior.
  19. Odysseus and Telemachus remove the weapons from the hall. Nurse Eurycleia recognizes the disguised Odysseus by a scar on his foot, but he swears her to secrecy.
  20. Theoclymenus foresees doom for the suitors, prophesying their impending death, but they mock him and dismiss his warning.
  21. Penelope brings out Odysseus's great bow and announces a contest to win her hand. The suitors all fail to string the bow, Odysseus steps up, strings the bow and shoots the arrow through twelve axes heads and wins the contest.
Tags:
Leave A Reply

Git: Keep two branches in sync

I have a Git repository with a primary branch that I maintain actively. I also have a separate feature branch that I'd like to keep in sync with the main branch, and alert if there are any conflicts immediately. Using Git hooks you can script automatically pulling each new commit from the primary branch to the feature branch.

Create a .git/hooks/post-commit file and put these contents in it:

#!/bin/sh

SRC="main"
DST="feature"

branch=$(git branch --show-current)

if [ "$branch" = "$SRC" ]; then
    commit=$(git rev-parse HEAD)

    git checkout $DST &&
    git cherry-pick "$commit" &&
    git checkout main
fi

If there are conflicts, Git errors out immediately and leaves you on the feature branch to manually resolve the conflict.

Tags:
Leave A Reply

Using rsync to keep two directories in sync

I have two directories with (mostly) the same content that I want to keep in sync. Specifically I want to make sure that the newest version of each file is synced to the other directory. This allows me to update a file on either side, and that version will propagate to the other. You can do this with a bi-directional rsync command:

rsync --update -av /dir/a/ /dir/b/
rsync --update -av /dir/b/ /dir/a/

Using --update tells rsync to skip files on the receiving side that are newer. If you sync a -> b and then b -> a you end up with both locations having the newest copy of each file.

Tags:
Leave A Reply

Perl: ULID generation

ULID's are an interesting way to generate globally unique identifiers. Here is a quickie Perl implementation to generate ULIDs. This implementation does not include the intra-millisecond monotonic increment however. If that feature is important to you consider checking out a more full-featured implementation like ULID::Tiny.

for (1 .. 5) {
    say(ulid());
}
sub ulid {
    my $ts    = $_[0] || time() * 1000;
    my $bytes = substr(pack("Q>", $ts), 2, 6);

    # Append 10 random bytes
    for (0 .. 9) { $bytes .= chr(int(rand(256))); }

    # base32 encoding
    my $bits  = unpack("B*", $bytes);
    my $pad   = (5 - (length($bits) % 5)) % 5;
    $bits    .= '0' x $pad;

    # Chars to use for base32
    my @CROCKFORD_CHARS = split(//, '0123456789ABCDEFGHJKMNPQRSTVWXYZ');

    my $result  = '';
    for (my $i = 0; $i < length($bits); $i += 5) {
        my $chunk = substr($bits, $i, 5);
        my $index = 0;
        for my $bit (split //, $chunk) {
            $index = ($index << 1) | $bit;
        }

        $result .= $CROCKFORD_CHARS[$index];
    }

    return $result;
}

See also: UUIDv7

Tags:
Leave A Reply

Perl: Using Inline::C to embed C functions in your Perl scripts

Perl allows "inline" code written in other languages. This is useful because you can have native C functions interact with your Perl code. By placing your C code in the __DATA__ section of your Perl script you get clean separation between the two languages.

Note: Inline::C does not understand uint64_t in function definitions, so anything that interacts with Perl needs to use UV instead. Internally C functions can use and interact with uint64_t variables just fine.

use strict;
use warnings;
use v5.16;
use Inline 'C';

##############################################

seed_splitmix64(time());

for (1 .. 5) {
    say splitmix64();
}

##############################################

__DATA__
__C__

uint64_t x = 123456789;

void seed_splitmix64(UV seed) {
    x = seed;
}

UV splitmix64() {
    uint64_t z = (x += 0x9e3779b97f4a7c15);
    z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
    z = (z ^ (z >> 27)) * 0x94d049bb133111eb;

    return z ^ (z >> 31);
}

This will compile the C code into a shared object in the _Inline directory in whichever directory you instantiated your Perl script. Code is only compiled once (and where there are changes), so your script performance will be very high.

Tags:
Leave A Reply

Comparison of markup languages

I've been investigating JSON vs YAML vs TOML for various applications. After testing many variations I ended up writing a simple tool to compare all three live.

Tags:
Leave A Reply

Linux: SFTP and SCP friendly login banners

To display a message when a user logs into your server, add the following to ~/.bashrc:

# If it's an interactive terminal show the banner
if [[ $- == *i* ]]; then
    echo "Welcome to the monitoring server"
    echo "Configuration is stored in /etc/myapp"
fi

This runs only for interactive sessions. Non-interactive connections such as SFTP and SCP remain unaffected, which prevents automated processes from breaking.

See also: The text_color project.

Tags:
Leave A Reply

YAML is growing on me

The more I learn about YAML, the more I like it. JSON is great as a machine readable format, but it sucks at being human readable. Want to add an element, better make sure you have all the commas and curly braces in the exact right place or the whole thing will be unusable. YAML on the other hand is designed to be human readable and modifiable. It's a very simple key/value system using indentation to represent layers.

PHP has a PECL module with good YAML support. There are also pure PHP versions if you're unable to install PECL modules. Symfony provides one, and so does Spyc. I prefer the latter because it's a single file and very easy to install.

On the Perl side there is YAML::XS, YAML::PP, and many others.

Parsing YAML is very easy in just about every language I can find. If you have a complex data structure that you need humans to interact with use YAML please.

Here is a great breakdown of when to use YAML vs JSON:

Use Case Recommended Why
API request/response JSON Universal support, strict parsing
Configuration files YAML Comments, readability
Browser/JavaScript JSON Native parsing
Kubernetes/Docker YAML Industry standard
Data interchange JSON Unambiguous, fast
Human-edited files YAML Less punctuation

YAML spec differences.

Tags:
Leave A Reply

C: Fill an array of unsigned integers with random data using getentropy()

Often I need to generate random unsigned integers for seeding PRNGs. The best way is using the getentropy() system function to read OS level randomness into your array.

#include <sys/random.h>

// Read from system random to fill up data structure
int8_t fill_urandom(void *buf, size_t bytes) {
    int8_t ok = getentropy(buf, bytes);

    return (ok == 0);
}

Declare your array of integers, and then pass it as a pointer to this function to fill with random bytes from your OS. This will get you a bunch of random integers you can use for seeding PRNGs.

uint64_t seed[4];
fill_urandom(seed, sizeof(seed));
Tags:
Leave A Reply