Argentum can call C/C++ functions and pass parameters of primitive data types and objects.
On Argentum side:
Create a module io.ag
and declare there a number of functions with ";" in place of body:
using sys { String, Cursor, Blob }
readFile(name str, content Blob) int;
writeFile(name str, at int, byteCount int, content -Blob) bool;
On C/C++ side:
Create a library:
cmake_minimum_required (VERSION 3.8)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
project ("io_lib")
add_library (io_lib STATIC
"io_lib.c"
"../argentum/src/runtime/runtime.h")
If in C++, this function should be declared in extern "C"
statement. In C this is not needed.
Functions must be nameded "ag_fn_<moduleName>_<functionName>".
runtime.h
- contains declarations of C99 helper functions and Argentum data structures. Our example uses Blob and String objects.
#include <stdio.h>
#include "../argentum/src/runtime/runtime.h"
int64_t ag_fn_io_readFile(AgString* name, AgBlob* content) {
FILE* f = fopen(name->ptr, "rb"); // name->ptr - points to zero-terminated utf8 chars
if (!f)
return -1;
fseek(f, 0, SEEK_END);
int64_t size = ftell(f);
fseek(f, 0, SEEK_SET);
ag_make_blob_fit(content, size); // helper that resizes sys_Blob
int64_t read_size = fread(content->data, 1, size, f); // content->data points to the actual buffer
fclose(f);
return read_size == size ? size : -1;
}
bool ag_fn_io_writeFile(AgString* name, int64_t at, int64_t size_in_bytes, AgBlob* content) {
if (at < 0
|| (uint64_t)at >= content->size * sizeof(int64_t)
|| (uint64_t)at + (uint64_t)size_in_bytes > content->size * sizeof(int64_t))
return -1;
FILE* f = fopen(name->ptr, "wb");
if (!f)
return -1;
int64_t size_written = fwrite((char*)content->data + at, 1, size_in_bytes, f);
fclose(f);
return size_written == size_in_bytes;
}
Build C99 library and copy it from the "out\build\x64-Release\io_lib.lib"
to the "argentum-demo\libs\io_lib.lib"
We also need to add our new library to the linker invocation:
...... lld-link ..... "..\libs\io_lib.lib"
For the sake of simplicity I'm going to put all tutorial code to the io.ag
module.
That's it. Now we can read and write binary files:
data = Blob;
dataSizeInBytes = readFile("1.txt", data);
dataSizeInBytes >= 0 && writeFile("1.copy.txt", 0, dataSizeInBytes, data)
? log("success");
Support for Text files
fn readTextFile(name str) ?str { // {1}
data = Blob;
size = readFile(name, data); // {2}
size >= 0 // {3}
? data.mkStr(0, size) // {4}
}
Where:
- In line {1} we declare that our function returns ?str aka "optional immutable String" type. In can have either
nothing
or a text string - Line {2} contains the actual call to our C99 function
- Lines {3,4} is a single statement, that was wrapped on two lines to add comments
- Line {3} checks the result of the read operation.
- Line {4} calls the Argentum RTL library function that creates an immutable string from a portion of the mutable byte buffer
- Line {4} contains a "?"-operator, that takes a condition to the left and an expression to the right (in this example - "r").
- If condition fulfills, the "?"-operator executes its right-hand-side (RHS) expression (that in this example returns string) and wraps its result in the optional string type.
- Otherwise it returns an
empty
optional string.
Let's make the main program:
log(readTextFile("1.txt") : "no data");
In this code we call a readTextFile
function, and check its optional(String) result against "emptiness
".
The binary operator ":" expects optional(String) to the left, and a String expression to the right.
- If LHS result is not empty, ":"-operator returns it's extracted (i.e. it's no more optional) value
- otherwise, ":"-operator the executes its RHS to make a value.
If we execute this program, we'll see "no data" in the console.
Let's create the "1.txt" file in the "argentum-demo\workdir\" directory:
(Donald Duck),(Mickey Mouse)
When we run it again, the file contents will be printed to the stdout
.
Read-parse-convert-write data
First of all, let's extends our sys_Cursor class with one helper method:
class Cursor { // {1}
getTill(delimiter int) str { // {2}
r = StrBuilder; // {3}
loop !{ // {4}
c = get(); // {5}
c != delimiter && c != 0 ? r.putCh(c) // {6}
};
r.toStr() // {7}
}
}
This code fragment "extends" the sys_Cursor
class in line {1} and adds a getTill
method to it in line {2}.
This method extracts a portion of the string either to the delimiter or to the end of string:
- in a loop (line {4})
- it reads a Unicode codepoint out of string (line{5})
- and if it is not a delimiter and not a end-of-string, it appends this character {6} to the string builder created at the line {3}
- In general the loop operator iterates till its body returns "empty optional" value (so it acts as repeat-until). In this example we use negate "!" operator to make our loop a "repeat-while".
- Line {7} extracts a string out of string builder.
Having this method we can write the following file parsing code:
b = StrBuilder;
readTextFile("1.txt") ? _.cursor() -> { // {1}
loop {
_.getTill('('); // {2}
firstName = _.getTill(' '); // {3}
lastName = _.getTill(')'); // {4}
b.putStr("First name=").putStr(firstName).newLine(); // {5}
b.putStr("Last name=").putStr(lastName).putStr("\n\n"); // {6}
_.get() == 0 // {7}
};
writeFile("2.txt", 0, b.pos, b) ? log("success\n") // {8}
}
Where:
- Line {1} reads file and skips all processing on failure. On success the file content is passed to the RHS expression of the "?"-operator as a "_" variable, where we extract a cursor to scans this string start-to end and pass this cursor in the
{}
-block. - Line{2} skips everything till "(" using our newly defined Cursor method.
- Lines {3} and {4} extract pieces of string into variables.
- Lines {5} and {6} format them in a new way.
- Line {7} continues the loop skipping "," and checking for the end of file.
- Line {8} calls our C99 function
writeFile
and prints "success" to the console if it returns true.
Lines 3..6 can be rewritten using multiline string literals and string interpolation:
b.putStr("{}//
First name={_.getTill(' ')}
Last name={_.getTill(')')}
");
If we execute this program, we'll see "success" in the console. And file "2.txt" in the workdir:
First name=Donald
Last name=Duck
First name=Mickey
Last name=Mouse
The full code of this tutorial:
using sys { String, StrBuilder, Blob, log }
using string;
fn readFile(name -String, content Blob) int;
fn writeFile(name -String, at int, byteCount int, content -Blob) bool;
fn readTextFile(name String) ?String {
r = "";
data = Blob;
size = readFile(name, data);
size >= 0 && r.fromBlob(data, 0, size) ? r
}
class String {
getTill(delimiter int) String {
r = StrBuilder;
loop !{
c = get();
c != delimiter && c != 0 ? r.putCh(c)
};
r.toStr()
}
}
readTextFile("1.txt") ? {
b = StrBuilder;
loop {
_.getTill('(');
x = _.getTill(' ');
y = _.getTill(')')
b.putStr("{}//
First name={x}
Last name={y}
");
_.get() == 0
};
writeFile("2.txt", 0, b.pos, b) ? log("success\n")
}
This tutorial contains:
- Bindings to C99
- More on optional values and "?" ":" operators.
- Class extensions.
- Loops
Does it support multithreading?
It’s designed to be multithreaded, now I’m working on implementation. It’s the matter of weeks.
Yes, now it’s multithreaded.