Embedded Code Expressions with Word Operators: Which one would you coose?

I was thinking of the ideal expression syntax for the programming language DQ, which is targeted also for embedded software development.

I started with the (dominating) C syntax.

Operators in C

In C the following operators have shared meanings:

  • &: bitwise "and" operation OR address of
  • *: multiplication OR pointer dereference
  • /: truncated integer division OR floating point division

Further operators in C:

  • %: integer division reminder
  • && or and: logical "and"
  • || or or: logical "or"
  • ! or not: logical "not"
  • ~: bitwise "not"
  • ^: bitwise "xor"
  • ?: ternary operator

Operators in DQ

I think for the good source code readability and clarity every different operation should have a different symbol. Therefore the shared symbols from C are not taken over. These operators are already fixed in DQ:

  • &: address-of operator (widespread standard)
  • ^: pointer dereference (standard in other languages)
  • *: multiplication only
  • /: floating point division only (standard in other languages)
  • or: logical "or" (widespread standard)
  • and: logical "and" (widespread standard)
  • not: logical "not" (widespread standard)

These symbols are already fixed for special purposes:

  • #: compiler directives (#ifdef etc)
  • $: context local specials (e.g. myarray[0:$end-2])
  • ?: inference marker
  • @: namespace designator (e.g. @def.LINUX)

DQ cannot use the C standard &, |, ~, ^ for the bitwise operations, because the & and ^ is used for other (fixed) purposes. But we've run out of the good symbols. The obvious choice, that other existing languages also use, is reserving some words for the remaining operations. In DQ these (all-capital) words are reserved currently as operators:

  • AND: bitwise "and"
  • OR: bitwise "or"
  • NOT: bitwise "not"
  • XOR: bitwise "xor"
  • IDIV: truncated integer division
  • IMOD: integer division reminder

For the modify-assign statements with a word operator a leading = is required, otherwise it looks awkward:

regs.OSPEEDR OR= (1 << pinx2)  // invalid
regs.OSPEEDR =OR= (1 << pinx2)

Examples with All-Capital Operators

tmp = RCC.CFGR
tmp =AND= NOT 3
tmp =OR= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) AND 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR =AND= NOT RCC_CR_PLLON
while RCC.CR AND RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed IDIV pll_input_freq
var plln : uint = vcospeed  IDIV pll_input_freq
var pllq : uint = vcospeed  IDIV 48000000

RCC.PLLCFGR = (0
    OR (pllsrc << 22)
    OR (pllm   <<  0)
    OR (plln   <<  6)
    OR (((pllp >> 1) - 1) << 16)
    OR (pllq   << 24)
)

regs.MODER =AND= NOT (3 << pinx2)
regs.MODER =OR=      (n << pinx2)

if flags AND PINCFG_OPENDRAIN <> 0:
    regs.OTYPER =OR= (1 << apinnum)
else:
    regs.OTYPER =AND= NOT (1 << apinnum)
endif

regs.PUPDR =AND= NOT (3 << pinx2)
if flags AND PINCFG_PULLUP <> 0:
    regs.PUPDR =OR= (1 << pinx2)
elif flags AND PINCFG_PULLDOWN <> 0:
    regs.PUPDR =OR= (2 << pinx2)
endif

Prefixed Word Operators

I'm thinking to change the all-capital word operators with a % prefixed lowercase words:

  • %and: bitwise "and"
  • %or: bitwise "or"
  • %not: bitwise "not"
  • %xor: bitwise "xor"
  • %div or %idiv: truncated integer division
  • %mod or %idiv: integer division remainder

The sample code would look like this way:

tmp = RCC.CFGR
tmp %and= %not 3
tmp %or= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) %and 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR %and= %not RCC_CR_PLLON
while RCC.CR %and RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed %div pll_input_freq
var plln : uint = vcospeed  %div pll_input_freq
var pllq : uint = vcospeed  %div 48000000

RCC.PLLCFGR = (0
    %or (pllsrc << 22)  // select PLL source
    %or (pllm   <<  0)
    %or (plln   <<  6)
    %or (((pllp >> 1) - 1) << 16)
    %or (pllq   << 24)
)

regs.MODER %and= %not (3 << pinx2)
regs.MODER %or=       (n << pinx2)

if flags %and PINCFG_OPENDRAIN <> 0:
    regs.OTYPER %or= (1 << apinnum)
else:
    regs.OTYPER %and= %not (1 << apinnum)
endif

regs.PUPDR %and= %not (3 << pinx2)
if flags %and PINCFG_PULLUP <> 0:
    regs.PUPDR %or= (1 << pinx2)
elif flags %and PINCFG_PULLDOWN <> 0:
    regs.PUPDR %or= (2 << pinx2)
endif

Which version do you like more?

or

Do you have some other ideas for the operator notation?

reddit.com
u/Mean-Decision-3502 — 1 day ago

Title: Expressions with Word Operators: Which one would you coose?

I was thinking of the ideal expression syntax for the programming language DQ. I started with the (dominating) C syntax.

Operators in C

In C the following operators have shared meanings:

  • &: bitwise "and" operation OR address of
  • *: multiplication OR pointer dereference
  • /: truncated integer division OR floating point division

Further operators in C:

  • %: integer division reminder
  • && or and: logical "and"
  • || or or: logical "or"
  • ! or not: logical "not"
  • ~: bitwise "not"
  • ^: bitwise "xor"
  • ?: ternary operator

Operators in DQ

I think for the good source code readability and clarity every different operation should have a different symbol. Therefore the shared symbols from C are not taken over. These operators are already fixed in DQ:

  • &: address-of operator (widespread standard)
  • ^: pointer dereference (standard in other languages)
  • *: multiplication only
  • /: floating point division only (standard in other languages)
  • or: logical "or" (widespread standard)
  • and: logical "and" (widespread standard)
  • not: logical "not" (widespread standard)

These symbols are already fixed for special purposes:

  • #: compiler directives (#ifdef etc)
  • $: context local specials (e.g. myarray[0:$end-2])
  • ?: inference marker
  • @: namespace designator (e.g. @def.LINUX)

DQ cannot use the C standard &, |, ~, ^ for the bitwise operations, because the & and ^ is used for other (fixed) purposes. But we've run out of the good symbols. The obvious choice, that other existing languages also use, is reserving some words for the remaining operations. In DQ these (all-capital) words are reserved currently as operators:

  • AND: bitwise "and"
  • OR: bitwise "or"
  • NOT: bitwise "not"
  • XOR: bitwise "xor"
  • IDIV: truncated integer division
  • IMOD: integer division reminder

For the modify-assign statements with a word operator a leading = is required, otherwise it looks awkward:

regs.OSPEEDR OR= (1 << pinx2)  // invalid
regs.OSPEEDR =OR= (1 << pinx2)

Examples with All-Capital Operators

tmp = RCC.CFGR
tmp =AND= NOT 3
tmp =OR= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) AND 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR =AND= NOT RCC_CR_PLLON
while RCC.CR AND RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed IDIV pll_input_freq
var plln : uint = vcospeed  IDIV pll_input_freq
var pllq : uint = vcospeed  IDIV 48000000

RCC.PLLCFGR = (0
    OR (pllsrc << 22)
    OR (pllm   <<  0)
    OR (plln   <<  6)
    OR (((pllp >> 1) - 1) << 16)
    OR (pllq   << 24)
)

regs.MODER =AND= NOT (3 << pinx2)
regs.MODER =OR=      (n << pinx2)

if flags AND PINCFG_OPENDRAIN <> 0:
    regs.OTYPER =OR= (1 << apinnum)
else:
    regs.OTYPER =AND= NOT (1 << apinnum)
endif

regs.PUPDR =AND= NOT (3 << pinx2)
if flags AND PINCFG_PULLUP <> 0:
    regs.PUPDR =OR= (1 << pinx2)
elif flags AND PINCFG_PULLDOWN <> 0:
    regs.PUPDR =OR= (2 << pinx2)
endif

Prefixed Word Operators

I'm thinking to change the all-capital word operators with a % prefixed lowercase words:

  • %and: bitwise "and"
  • %or: bitwise "or"
  • %not: bitwise "not"
  • %xor: bitwise "xor"
  • %div or %idiv: truncated integer division
  • %mod or %idiv: integer division remainder

The sample code would look like this way:

tmp = RCC.CFGR
tmp %and= %not 3
tmp %or= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) %and 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR %and= %not RCC_CR_PLLON
while RCC.CR %and RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed %div pll_input_freq
var plln : uint = vcospeed  %div pll_input_freq
var pllq : uint = vcospeed  %div 48000000

RCC.PLLCFGR = (0
    %or (pllsrc << 22)  // select PLL source
    %or (pllm   <<  0)
    %or (plln   <<  6)
    %or (((pllp >> 1) - 1) << 16)
    %or (pllq   << 24)
)

regs.MODER %and= %not (3 << pinx2)
regs.MODER %or=       (n << pinx2)

if flags %and PINCFG_OPENDRAIN <> 0:
    regs.OTYPER %or= (1 << apinnum)
else:
    regs.OTYPER %and= %not (1 << apinnum)
endif

regs.PUPDR %and= %not (3 << pinx2)
if flags %and PINCFG_PULLUP <> 0:
    regs.PUPDR %or= (1 << pinx2)
elif flags %and PINCFG_PULLDOWN <> 0:
    regs.PUPDR %or= (2 << pinx2)
endif

Which version do you like more?

or

Do you have some other ideas for the operator notation?

EDIT

Version with band / bor etc, as "jason-reddit-public" suggested:

  • band: bitwise "and"
  • bor: bitwise "or"
  • bnot: bitwise "not"
  • bxor: bitwise "xor"
  • idiv: truncated integer division
  • imod: integer division reminder

​

tmp = RCC.CFGR
tmp =band= bnot 3
tmp =bor= RCC_CFGR_SW_HSI
RCC.CFGR = tmp
while (RCC.CFGR >> 2) band 3 <> RCC_CFGR_SW_HSI:
endwhile

RCC.CR =band= bnot RCC_CR_PLLON
while RCC.CR band RCC_CR_PLLRDY != 0:
endwhile

var pllm : uint = basespeed idiv pll_input_freq
var plln : uint = vcospeed  idiv pll_input_freq
var pllq : uint = vcospeed  idiv 48000000

RCC.PLLCFGR = (0
    bor (pllsrc << 22)
    bor (pllm   <<  0)
    bor (plln   <<  6)
    bor (((pllp >> 1) - 1) << 16)
    bor (pllq   << 24)
)

regs.MODER =band= bnot (3 << pinx2)
regs.MODER =band=      (n << pinx2)

if flags band PINCFG_OPENDRAIN <> 0:
    regs.OTYPER =bor= (1 << apinnum)
else:
    regs.OTYPER =bor= bnot (1 << apinnum)
endif

regs.PUPDR =band= bnot (3 << pinx2)
if flags band PINCFG_PULLUP <> 0:
    regs.PUPDR =bor= (1 << pinx2)
elif flags AND PINCFG_PULLDOWN <> 0:
    regs.PUPDR =bor= (2 << pinx2)
endif
reddit.com
u/Mean-Decision-3502 — 1 day ago

The “3 / 2 * 10 != 10 * 3 / 2” Problem

Coming from school math, it feels pretty strange that:

3 / 2 * 10 != 10 * 3 / 2

This expression can evaluate to true or false depending on the programming language you use.

Languages where the two sides are NOT equal Languages where the two sides ARE equal
C, C++, C#, Java, Kotlin, Scala, Ruby, Go, D, Rust, Swift, Zig, Odin, V, Fortran, Python 2 Python 3, JavaScript, TypeScript, Dart, R, Lua 5.3+, Perl, MATLAB, Pascal, Mojo, Nim, Crystal, Julia, Haskell

Why are the two sides not equal in the languages on the left?

On the left side of the expression above, the operation 3 / 2 is evaluated first using integer arithmetic—truncating the fractional part—which results in 1. This is then multiplied by 10, giving a result of 10 for the left side.

On the right side, 10 * 3 = 30 is the first step. Dividing this by 2 gives 15. Thus:

10 != 15

These languages prioritize the efficient (fast) execution of expressions over mathematical correctness, as integer arithmetic is significantly faster than floating-point arithmetic. Unfortunately, these languages use the same / operator for both integer and floating-point division, selecting the operation based on the types of the operands.

Regrettably, the expression 3 / 2 * 10.0 still yields 10 in most of these languages (and results in a compilation error in Rust). Even though we indicated our intent to use floating-point numbers by writing 10.0, it is already too late: compilers evaluate 3 / 2 as integer arithmetic in the first step. Expressions like 3.0 / 2 * 10 or 3 / 2.0 * 10, on the other hand, produce 15.

Thus, depending on the operand types, you end up with either 10 or 15. This situation becomes even more dangerous when variables are involved in the expression:

num / denum * scale != scale * num / denum

This can evaluate to true or false depending on the types of the num and denum variables (float vs. int). To avoid these pitfalls, developers use type casting:

(float)num / denum * scale != scale * (float)num / denum

This ensures the compiler performs floating-point division. (Note: The expression above can still evaluate to true due to floating-point precision limitations).

Why are the two sides equal in the languages on the right?

Many of the languages listed here are dynamically typed or scripting languages. They were not primarily built for raw execution speed, but rather for ease of use or mathematical correctness. In these languages, numbers are typically handled as floating-point values by default, so 3 / 2 is always 1.5.

However, languages like Pascal, Haskell, Mojo, Nim, Crystal, and Dart are statically typed and distinguish between integers and floating-point numbers just like C or C++. What happens differently here?

In these languages, the / symbol always denotes floating-point division. In 3 / 2 * 10, 3 and 2 are implicitly converted to floating-point numbers first, performing a floating-point division that yields 1.5. Next comes the multiplication: 1.5 * 10. Since one operand is a float, 10 is converted to float before multiplication. (Note: C handles 3.0 / 2 * 10 in a similar manner).

Unintended floating-point operations—which might carry performance penalties—generally trigger compilation errors in these statically typed languages, because floats are not automatically demoted/converted to integers (unlike in C/C++). Most of these languages offer a separate operator specifically for integer division (such as div or //).

What happens when a language doesn't work the way we expect?

When using the languages on the left, / can result in either integer or floating-point division. If integer division occurs when you intended to perform floating-point calculations, your program will likely produce incorrect results (possibly only for specific input data). Once you have been burned by this a few times, you become overly cautious with division and often clutter expressions with explicit casts to guarantee proper execution.

If you explicitly want integer division behavior, you usually don't need to do anything extra—other than ensuring that neither side of the / operator evaluates to a floating-point type.

In contrast, when using the languages on the right, there are no surprises with /: the result is always a floating-point number. If you try to store this result in an integer variable, you will typically get a compilation error. Your program is far more likely to work correctly out of the box—at worst, running slightly slower if integer division could have been used instead. If you specifically need integer division, you must use the dedicated operator provided for it (e.g., div or //).

Why is the “3 / 2 * 10 != 10 * 3 / 2” behavior more common?

In the majority of compiled languages—unfortunately including many modern ones—the two sides of this expression are not equal due to default integer division rules.

I regularly use both Pascal and C/C++. To me, Pascal's approach is much more intuitive: it doesn't carry noticeable drawbacks, and it remains easy to control. On the other hand, C/C++'s behavior is a frequent source of bugs at my workplace.

I genuinely don't understand why the 3 / 2 * 10 != 10 * 3 / 2 design remains the prevalent choice.

reddit.com
u/Mean-Decision-3502 — 14 days ago

New Languages: Standardizing API, Examples ?

Hi, some of you are developing new general-purpose programming languages here. When the language is ready, you have to develop standard APIs, like file-io, json-handling etc. Users, and you would have benefit, when the APIs would be same/similar across multiple different languages.

Standardizing some Examples would allow the users to compare languages more easily.

What do you think?

reddit.com
u/Mean-Decision-3502 — 1 month ago
▲ 0 r/Compilers+1 crossposts

DQ, a Human-Friendly Universal Programming Language, Is Now Publicly Available

After several months of design and development, I have made the DQ programming language and compiler publicly available.

DQ is a strongly typed, compiled programming language intended for both embedded systems and desktop/server applications. Its design is influenced by Pascal, C++, and Python, with an emphasis on readable syntax, explicit behavior, native-code performance, and practical low-level programming.

A Hello World in DQ:

use print
function *Main() -> int:
    PrintLn("hello from DQ")
    return 0
endfunc

Language documentation: nvitya.github.io/dq-lang

GitHub repository: github.com/nvitya/dq-lang

The compiler and the core language are already fairly complete. Recently, most of my work has focused on extending the DQ standard library and fixing compiler issues discovered while writing real DQ programs.

For a quick look at representative DQ code, I recommend the NanoNet socket implementation: stdpkg/nanonet/nano_sockets.dq

Prebuilt release packages are available for Linux and Windows here, so the compiler should be straightforward to try without building it from source.

So far, I have designed and developed DQ alone. The next major step is expanding the standard library and testing the language through more real-world projects.

I would appreciate feedback on the language design, syntax, compiler, documentation, and overall direction. I am also interested in finding developers who like the project and may want to help build its libraries, tools, and community.

nvitya.github.io
u/Mean-Decision-3502 — 1 month ago
▲ 0 r/programmer+1 crossposts

Code Readability Comparison

I'm developing the programming language DQ. I'm not doing this just because (with AI help) I can. I started developing my own language because I couldn't find one that had all the critical features I need. One of those critical features is human readability.

My LLVM-based DQ compiler, although some important parts are still missing, is already usable to some extent. I wanted to check its performance, so I created some simple benchmarks. I decided to compare DQ with a few other languages, so I implemented these benchmarks in those languages in exactly the same way.

I find it very helpful and thought-provoking to look at exactly the same solutions in different languages, so I'd like to share my impressions on them.

Note: Please look at the following code snippets side by side, without syntax highlighting.

Please share your thoughts.

Python

darr = []

def FillArray(maxval):
    global darr
    darr.clear()
    for i in range(maxval):
        darr.append(i)

def FillArrayPtr(maxval):
    global darr
    darr = [0] * maxval
    for i in range(maxval):
        darr[i] = i

def CalcSum():
    result = 0
    arrlen = len(darr)
    for i in range(arrlen):
        result += darr[i]
    return result

def CalcSumPtr():
    result = 0
    arrlen = len(darr)
    for i in range(arrlen):
        result += darr[i]
    return result

My Impressions:

  • I think Python is the winner in pure readability. It is close to the absolute minimum.
  • In the FillArray versions, global darr may not be obvious to beginners.
  • In for i in range(maxval), it is not immediately obvious that i starts at 0 and ends at maxval - 1.
  • darr = [0] * maxval is compact, but it looks very similar to 0 * maxval while doing something very different. Still, it is not far from natural human thinking: take this [0] value maxval times.
  • If you only look from a distance, you cannot easily tell which functions return values and which do not.

DQ

var darr : [*]int32;

function FillArray(maxval : int32):
    darr.Clear();
    for i : int32 = 0 count maxval:
        darr.Append(i);
    endfor
endfunc

function FillArrayPtr(maxval : int32):
    darr.SetLength(maxval);
    var pi32 : ^int32 = &darr[0];
    for i : int32 = 0 count maxval:
        pi32[i]^ = i;
    endfor
endfunc

function CalcSum() -> int64:
    result = 0;
    var arrlen : int32 = darr.length;
    for i : int = 0 count arrlen:
        result += darr[i];
    endfor
endfunc

function CalcSumPtr() -> int64:
    result = 0;
    var arrlen : int32  = darr.length;
    var pi32   : ^int32 = &darr[0];
    for i : int = 0 count arrlen:
        result += pi32[i]^;
    endfor
endfunc

My Impressions (I try to be objective here too):

  • DQ requires more text than Python because it is more explicit. Type annotations are mandatory everywhere.
  • The block closers make it clearer where blocks end, and they also indicate what kind of block is ending.
  • In the for loop, it is obvious where i starts, and count means it will be incremented maxval times. I find this fairly natural. (The for in DQ also has to and while variants.)
  • The semicolons add some noise.
  • The lines end with either `;` or `:` there is only a very little difference between them. Looks weird (but the compiler checks them properly)
  • The implicit result variable shortens some functions nicely.

Pascal

var
    darr: array of int32;

procedure FillArray(maxval: int32);
var
    i : int32;
    len, cap : int32;
begin
    SetLength(darr, 0);
    len := 0;
    cap := 0;
    for i := 0 to maxval - 1 do
    begin
        if len >= cap then
        begin
            if cap = 0 then cap := 1 else cap := cap * 2;
            SetLength(darr, cap);
        end;
        darr[len] := i;
        Inc(len);
    end;
    SetLength(darr, len);
end;

procedure FillArrayPtr(maxval: int32);
var
    i    : int32;
    pi32 : ^int32;
begin
    SetLength(darr, maxval);
    pi32 := @darr[0];
    for i := 0 to maxval - 1 do
    begin
        pi32[i] := i;
    end;
end;

function CalcSum : int64;
var
    i, arrlen : int32;
begin
    result := 0;
    arrlen := Length(darr);
    for i := 0 to arrlen - 1 do
    begin
        result += darr[i];
    end;
end;

function CalcSumPtr : int64;
var
    i, arrlen : int32;
    pi32      : ^int32;
begin
    result := 0;
    arrlen := Length(darr);
    pi32   := @darr[0];
    for i := 0 to arrlen - 1 do
    begin
        result += pi32[i];
    end;
end;

My Impressions:

  • Unfortunately, to get comparable performance in FreePascal, FillArray becomes fairly long because of the allocation handling. That makes this part less comparable, although the rest still is.
  • There are semicolons everywhere.
  • Local variables are defined in a separate block. That has both advantages and disadvantages. For example, you know where to look for a local variable first.
  • In the for loop, you can see clearly where i starts and where it ends, not "one less than the end."
  • Length(darr) is not especially comfortable to use.
  • Some people think end is much longer than }. To me, it still feels like a single token, and I can read it about as quickly as the single-symbol versions.
  • It also has the convenient implicit result variable.

C++

vector<int32_t>  darr;

void FillArray(int32_t maxval) {
    darr.clear();
    for (int32_t i = 0; i < maxval; ++i) {
        darr.push_back(i);
    }
}

void FillArrayPtr(int32_t maxval) {
    darr.resize(maxval);
    int32_t *  pi32 = darr.data();
    for (int32_t i = 0; i < maxval; ++i) {
        pi32[i] = i;
    }
}

int64_t CalcSum() {
    int64_t  result = 0;
    int32_t  arrlen = darr.size();
    for (int32_t i = 0; i < arrlen; ++i) {
        result += darr[i];
    }
    return result;
}

int64_t CalcSumPtr() {
    int64_t    result = 0;
    int32_t    arrlen = darr.size();
    int32_t *  pi32   = darr.data();
    for (int32_t i = 0; i < arrlen; ++i) {
        result += pi32[i];
    }
    return result;
}

My Impressions:

  • For these tasks, I find the C++ version fairly readable too.
  • I find it unnatural when the type precedes the identifier. I don't read that form easily. I always align variables into columns in C++, and that helps.
  • C++ has a good and fast toolkit for FillArray, so it is almost as compact as Python.
  • If you look at the C-style for from a distance, a lot of things are packed into one expression. When reading it, I slow down to verify every piece.
  • Here too, the semicolons add some noise.

Rust

#[allow(non_upper_case_globals)]

static mut darr: Vec<i32> = Vec::new();

fn fill_array(maxval: i32) {
    unsafe {
        darr.clear();
        for i in 0..maxval {
            darr.push(black_box(i));
        }
    }
}

fn fill_array_ptr(maxval: i32) {
    unsafe {
        darr.resize(maxval as usize, 0);
        let ptr = darr.as_mut_ptr();
        for i in 0..maxval {
            *ptr.add(i as usize) = i;
        }
    }
}

fn calc_sum() -> i64 {
    let mut result: i64 = 0;
    unsafe {
        for i in 0..darr.len() {
            result += black_box(darr[i] as i64);
        }
    }
    result
}

fn calc_sum_ptr() -> i64 {
    let mut result: i64 = 0;
    unsafe {
        let ptr = darr.as_ptr();
        for i in 0..darr.len() {
            result += black_box(*ptr.add(i) as i64);
        }
    }
    result
}

My Impressions:

  • To get exactly the same behavior as the others, unfortunately unsafe blocks are required here because of the global darr. Try to ignore those for the readability discussion.
  • The code may be short, but I read it slowly. You have to concentrate on small differences, and the symbol density is high.
  • The variable identifiers do not align naturally into columns, and I find that unpleasant.
  • A large amount of noise is added to the actual code: mut, as, and additional type hints.
  • In for i in 0..darr.len(), there are a lot of dots grouped together. The interval end is exclusive, and that is not something I would necessarily infer at a glance.
  • I find the way return values are signaled easy to miss.
reddit.com
u/Mean-Decision-3502 — 2 months ago

Code Readability Comparison

I'm developing the programming language DQ. I'm not doing this just because (with AI help) I can. I started developing my own language because I couldn't find one that had all the critical features I need. One of those critical features is human readability.

My LLVM-based DQ compiler, although some important parts are still missing, is already usable to some extent. I wanted to check its performance, so I created some simple benchmarks. I decided to compare DQ with a few other languages, so I implemented these benchmarks in those languages in exactly the same way.

I find it very helpful and thought-provoking to look at exactly the same solutions in different languages, so I'd like to share my impressions on them.

Note: Please look at the following code snippets side by side, without syntax highlighting.

Please share your thoughts.

Python

darr = []

def FillArray(maxval):
    global darr
    darr.clear()
    for i in range(maxval):
        darr.append(i)

def FillArrayPtr(maxval):
    global darr
    darr = [0] * maxval
    for i in range(maxval):
        darr[i] = i

def CalcSum():
    result = 0
    arrlen = len(darr)
    for i in range(arrlen):
        result += darr[i]
    return result

def CalcSumPtr():
    result = 0
    arrlen = len(darr)
    for i in range(arrlen):
        result += darr[i]
    return result

My Impressions:

  • I think Python is the winner in pure readability. It is close to the absolute minimum.
  • In the FillArray versions, global darr may not be obvious to beginners.
  • In for i in range(maxval), it is not immediately obvious that i starts at 0 and ends at maxval - 1.
  • darr = [0] * maxval is compact, but it looks very similar to 0 * maxval while doing something very different. Still, it is not far from natural human thinking: take this [0] value maxval times.
  • If you only look from a distance, you cannot easily tell which functions return values and which do not.

DQ

var darr : [*]int32;

function FillArray(maxval : int32):
    darr.Clear();
    for i : int32 = 0 count maxval:
        darr.Append(i);
    endfor
endfunc

function FillArrayPtr(maxval : int32):
    darr.SetLength(maxval);
    var pi32 : ^int32 = &darr[0];
    for i : int32 = 0 count maxval:
        pi32[i]^ = i;
    endfor
endfunc

function CalcSum() -> int64:
    result = 0;
    var arrlen : int32 = darr.length;
    for i : int = 0 count arrlen:
        result += darr[i];
    endfor
endfunc

function CalcSumPtr() -> int64:
    result = 0;
    var arrlen : int32  = darr.length;
    var pi32   : ^int32 = &darr[0];
    for i : int = 0 count arrlen:
        result += pi32[i]^;
    endfor
endfunc

My Impressions:

  • DQ requires more text than Python because it is more explicit. Type annotations are mandatory everywhere.
  • The block closers make it clearer where blocks end, and they also indicate what kind of block is ending.
  • In the for loop, it is obvious where i starts, and count means it will be incremented maxval times. I find this fairly natural. (The for in DQ also has to and while variants.)
  • The semicolons add some noise.
  • The implicit result variable shortens some functions nicely.

Pascal

var
    darr: array of int32;

procedure FillArray(maxval: int32);
var
    i : int32;
    len, cap : int32;
begin
    SetLength(darr, 0);
    len := 0;
    cap := 0;
    for i := 0 to maxval - 1 do
    begin
        if len >= cap then
        begin
            if cap = 0 then cap := 1 else cap := cap * 2;
            SetLength(darr, cap);
        end;
        darr[len] := i;
        Inc(len);
    end;
    SetLength(darr, len);
end;

procedure FillArrayPtr(maxval: int32);
var
    i    : int32;
    pi32 : ^int32;
begin
    SetLength(darr, maxval);
    pi32 := @darr[0];
    for i := 0 to maxval - 1 do
    begin
        pi32[i] := i;
    end;
end;

function CalcSum : int64;
var
    i, arrlen : int32;
begin
    result := 0;
    arrlen := Length(darr);
    for i := 0 to arrlen - 1 do
    begin
        result += darr[i];
    end;
end;

function CalcSumPtr : int64;
var
    i, arrlen : int32;
    pi32      : ^int32;
begin
    result := 0;
    arrlen := Length(darr);
    pi32   := @darr[0];
    for i := 0 to arrlen - 1 do
    begin
        result += pi32[i];
    end;
end;

My Impressions:

  • Unfortunately, to get comparable performance in FreePascal, FillArray becomes fairly long because of the allocation handling. That makes this part less comparable, although the rest still is.
  • There are semicolons everywhere.
  • Local variables are defined in a separate block. That has both advantages and disadvantages. For example, you know where to look for a local variable first.
  • In the for loop, you can see clearly where i starts and where it ends, not "one less than the end."
  • Length(darr) is not especially comfortable to use.
  • Some people think end is much longer than }. To me, it still feels like a single token, and I can read it about as quickly as the single-symbol versions.
  • It also has the convenient implicit result variable.

C++

vector<int32_t>  darr;

void FillArray(int32_t maxval) {
    darr.clear();
    for (int32_t i = 0; i < maxval; ++i) {
        darr.push_back(i);
    }
}

void FillArrayPtr(int32_t maxval) {
    darr.resize(maxval);
    int32_t *  pi32 = darr.data();
    for (int32_t i = 0; i < maxval; ++i) {
        pi32[i] = i;
    }
}

int64_t CalcSum() {
    int64_t  result = 0;
    int32_t  arrlen = darr.size();
    for (int32_t i = 0; i < arrlen; ++i) {
        result += darr[i];
    }
    return result;
}

int64_t CalcSumPtr() {
    int64_t    result = 0;
    int32_t    arrlen = darr.size();
    int32_t *  pi32   = darr.data();
    for (int32_t i = 0; i < arrlen; ++i) {
        result += pi32[i];
    }
    return result;
}

My Impressions:

  • For these tasks, I find the C++ version fairly readable too.
  • I find it unnatural when the type precedes the identifier. I don't read that form easily. I always align variables into columns in C++, and that helps.
  • C++ has a good and fast toolkit for FillArray, so it is almost as compact as Python.
  • If you look at the C-style for from a distance, a lot of things are packed into one expression. When reading it, I slow down to verify every piece.
  • Here too, the semicolons add some noise.

Rust

#[allow(non_upper_case_globals)]

static mut darr: Vec<i32> = Vec::new();

fn fill_array(maxval: i32) {
    unsafe {
        darr.clear();
        for i in 0..maxval {
            darr.push(black_box(i));
        }
    }
}

fn fill_array_ptr(maxval: i32) {
    unsafe {
        darr.resize(maxval as usize, 0);
        let ptr = darr.as_mut_ptr();
        for i in 0..maxval {
            *ptr.add(i as usize) = i;
        }
    }
}

fn calc_sum() -> i64 {
    let mut result: i64 = 0;
    unsafe {
        for i in 0..darr.len() {
            result += black_box(darr[i] as i64);
        }
    }
    result
}

fn calc_sum_ptr() -> i64 {
    let mut result: i64 = 0;
    unsafe {
        let ptr = darr.as_ptr();
        for i in 0..darr.len() {
            result += black_box(*ptr.add(i) as i64);
        }
    }
    result
}

My Impressions:

  • To get exactly the same behavior as the others, unfortunately unsafe blocks are required here because of the global darr. Try to ignore those for the readability discussion.
  • The code may be short, but I read it slowly. You have to concentrate on small differences, and the symbol density is high.
  • The variable identifiers do not align naturally into columns, and I find that unpleasant.
  • A large amount of noise is added to the actual code: mut, as, and additional type hints.
  • In for i in 0..darr.len(), there are a lot of dots grouped together. The interval end is exclusive, and that is not something I would necessarily infer at a glance.
  • I find the way return values are signaled easy to miss.
reddit.com
u/Mean-Decision-3502 — 2 months ago

New Programming Language: Block Syntax Survey

Hi,

I’m developing a new programming language called DQ.

At the moment, the language supports two block styles:

BRACES: classic C-style blocks using { and }.

ENDWORD: Python-like block starts using :, but with mandatory closing words. The closing word depends on what it closes, for example: while ... endwhile. Some frequently used closing words are shortened, for example: object...endobj.

Indentation is not enforced in either mode. Currently, the two block styles can be mixed freely. 

I am considering introducing compiler directives that could be placed at the beginning of each source file:

#opt blockmode=any             // like now: both styles are allowed
#opt blockmode=braces          // warn when ENDWORD blocks are used
#opt blockmode=strict_braces   // error when ENDWORD blocks are used
#opt blockmode=endword         // warn when BRACES blocks are used
#opt blockmode=strict_endword  // error when BRACES blocks are used

Example code using BRACES block mode:

use libc/stdio;
use ./langdemo_mod as ldm  only(CONST1);  // only(), exclude() and "--" control global scope merging

#if false
//Contents of the "langdemo_mod.dq"
const CONST1 : int = 42;
const CONST2 : float = 3.14;
#endif

object OBase {
  cnt1 : int = 0;
  cnt2 : int = 10;
  name : cstring[32];

  function *Create(aname : cstring) {  // constructor
    name = aname;
  }

  function *Destroy() {
    @.printf("%s destroy\n", &name[0]);
  }

  function Count() [[virtual]] {
    cnt1 += 1;
  }

  function Print() {
    @.printf('%s: cnt1=%d, cnt2=%d\n', &name[0], cnt1, cnt2);
  }
}


object OChild(OBase) {
  function Count() [[override]] {
    inherited;
    cnt2 += 1;
  }
}

var obase   <- OBase('OBase');                                        
var ochild  : OChild = null;

function ObjTest() {
  ochild = new OChild('OChild');

  obase.Count();
  ochild.Count();

  obase.Print();
  ochild.Print();

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &ochild.name[0]));

  delete ochild;
}

function cstr_add(dst : cstring, src : cstring) {
  var ps     : ^cchar = &src[0];
  var psend  : ^cchar = ps[sizeof(src)];  // [] does not dereference
  var pd     : ^cchar = &dst[0];
  var pdend  : ^cchar = pd + sizeof(dst) - 1; 
  pd += len(dst);
  var pdstart : ^cchar = pd;

  while pd < pdend  and ps < psend  and ps^ <> 0 {
    pd^ = ps^;
    pd += 1;
    ps += 1;
  }

  if pd <> pdstart {
    pd^ = 0; // terminate
  }
}

[[external]] function putchar(c : cchar) -> int;  // from libc

function WriteStr(s : cstring) {
  var pc : ^cchar = &s[0];
  while pc^ <> 0 {
    putchar(pc^);
    pc += 1;
  }
}

function *Main() -> int {

  var s : cstring[128] = "";
  cstr_add(s, "Hello");
  cstr_add(s, " World!\n");
  WriteStr(s);

  if 3 / 2 * 10 == 15 {
    printf('The language is friendly.\n');
  } else {
    printf('The language is evil.\n');
  }

  printf("@langdemo_mod.CONST1 = %d\n", CONST1);
  printf("@langdemo_mod.CONST2 = %.3f\n", u/ldm.CONST2);

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &ochild.name[0]));

  ObjTest();

  for i : int = 0 to 5 {
    printf(' %d:', i);
    for j : int = 0  count i  step 2 { printf(' %d', j); }
    printf('\n');
  }

  var arr : [5]int = [2, 3, 5, 7, 11];
  printf('primes:');
  for i : int = 0  while i < len(arr)  { printf(' %d', arr[i]); }
  printf('\n');

  return 0;
}

Example code using ENDWORD block mode:

use libc/stdio;
use ./langdemo_mod as ldm  only(CONST1);  // only(), exclude() and "--" control global scope merging

#if false
//Contents of the "langdemo_mod.?"
const CONST1 : int = 42;
const CONST2 : float = 3.14;
#endif

object OBase:
  cnt1 : int = 0;
  cnt2 : int = 10;
  name : cstring[32];

  function *Create(aname : cstring):  // constructor
    name = aname;
  endfunc

  function *Destroy():
    @.printf("%s destroy\n", &name[0]);
  endfunc

  function Count() [[virtual]]:
    cnt1 += 1;
  endfunc

  function Print():
    @.printf('%s: cnt1=%d, cnt2=%d\n', &name[0], cnt1, cnt2);
  endfunc
endobj

object OChild(OBase):
  function Count() [[override]]:
    inherited;
    cnt2 += 1;
  endfunc
endobj

var obase   <- OBase('OBase');     // '<-' = embedded allocation (global data segment here)
                                   // no automatic destructor call for global embedded objects
var ochild  : OChild = null;

function ObjTest():
  ochild = new OChild('OChild');

  obase.Count();
  ochild.Count();

  obase.Print();
  ochild.Print();

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &ochild.name[0]));

  delete ochild;
endfunc

function cstr_add(dst : cstring, src : cstring):
  var ps     : ^cchar = &src[0];
  var psend  : ^cchar = ps[sizeof(src)];  // [] does not dereference
  var pd     : ^cchar = &dst[0];
  var pdend  : ^cchar = pd + sizeof(dst) - 1; 
  pd += len(dst);
  var pdstart : ^cchar = pd;

  while pd < pdend  and ps < psend  and ps^ <> 0:
    pd^ = ps^;
    pd += 1;
    ps += 1;
  endwhile

  if pd <> pdstart:
    pd^ = 0; // terminate
  endif
endfunc

[[external]] function putchar(c : cchar) -> int;  // from libc

function WriteStr(s : cstring):
  var pc : ^cchar = &s[0];
  while pc^ <> 0:
    putchar(pc^);
    pc += 1;
  endwhile
endfunc

function *Main() -> int:

  var s : cstring[128] = "";
  cstr_add(s, "Hello");
  cstr_add(s, " World!\n");
  WriteStr(s);

  if 3 / 2 * 10 == 15:
    printf('The language is friendly.\n');
  else:
    printf('The language is evil.\n');
  endif

  printf("@langdemo_mod.CONST1 = %d\n", CONST1);
  printf("@langdemo_mod.CONST2 = %.3f\n", u/ldm.CONST2);

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &ochild.name[0]));

  ObjTest();

  for i : int = 0 to 5:
    printf(' %d:', i);
    for j : int = 0  count i  step 2:   printf(' %d', j);  endfor
    printf('\n');
  endfor

  var arr : [5]int = [2, 3, 5, 7, 11];
  printf('primes:');
  for i : int = 0  while i < len(arr):  printf(' %d', arr[i]); endfor
  printf('\n');

  return 0;
endfunc

If you were using the DQ language, which block style would you prefer?

Do you think these blockmode compiler directives are useful, or would it be better to keep the language simpler and always allow both styles?

Should I also introduce a third block style, similar to Python: : starts a block, indentation is mandatory, and there is no endxxx closing word?

reddit.com
u/Mean-Decision-3502 — 3 months ago

A Human-Friendly Systems Programming Language — Looking for Feedback

Hi,

I’ve created a new programming language called “?” for now. I’ll reveal its real name later.

My main motivation was to create a universal language that could replace C/C++, FreePascal, and Python for many use cases. I actively use all three of these languages.

I have already put a lot of effort into researching, designing, and implementing the “?” language. At this point, I feel that I have created something promising that really works.

Before I put even more effort into the language and go public with it, I would like to hear more opinions from real people.

I think it would not be enough for this project to be only “a little successful”. For the effort to make sense, it should have the potential to become “very successful”. I believe it might have that potential. If that happens, I will not be able to handle everything alone, so I will need to organize the development and maintenance properly. It will be an open-source project.

The “?” language is not fully finalized yet, and there are still several features that I would like to add to the compiler. However, it has reached a state where the language is already usable and can demonstrate its main ideas and syntax.

The most important current and planned features of the “?” programming language are:

  • Very good human readability
  • Statically typed, with a strict bool type
  • Case-sensitive
  • Compiled to machine code using LLVM
  • A ?-run utility for compile-and-run usage, giving it a script-like feeling
  • Simple C interoperability; the runtime uses libc
  • int / uint use the native machine width; int32 is used for an explicit 32-bit integer
  • Supports C-style preprocessor directives such as #ifdef, but without macros
  • Supports short embedded directives with syntax like #{ifdef ...} ... #{endif}
  • No makefiles are required, for example: #linklib('z') can be written directly in the source code
  • Safe arithmetic rules, for example: 3 / 2 * 10 == 15
  • Two block modes:
    • : ... endXXX blocks, similar to Python style but with explicit closers and no forced indentation
    • { } blocks, similar to C style
  • Statements are closed with ;
  • Carefully designed operator precedence
  • Distinct boolean and bitwise operators, for example and and AND
  • Modify-assignment operators, for example: x += 1; and y =AND= 3;
  • Inline conditionals with iif(), for example: var i : int = iif(strptr <> null, strptr^.x, -1);
  • Support for objects with single inheritance and virtual functions
  • Object variables are references, but objects can still be embedded in BSS, on the stack, or inside other objects
  • Optional single-word namespaces using the @ symbol, for example: @stdio.printf()
  • No self. is needed inside object functions/methods
  • Namespace qualification, such as @stdio., is required to access outside symbols from object methods
  • A well-defined package and module system with flexible namespace merging
  • The “?” runtime library modules are distributed in source-code form
  • Fast compilation using a single-pass forward parser and precompiled module interfaces
  • Manual memory control, with RAII and an ensure statement planned
  • Native C string support
  • Function overloading and default parameters
  • Pointers using the ^ symbol
  • Pointer arithmetic with +, -, and []
  • The [] operator does not dereference pointers automatically
  • Struct pointers are automatically dereferenced on member access with .
  • Function arguments can be passed by reference using ref, refin, refout, and refnull

--– CODE EXAMPLE BEGIN ---

use libc/stdio;
use ./langdemo_mod as ldm  only(CONST1);  // only(), exclude() and "--" control global scope merging

#if false

//Contents of the "langdemo_mod.?"

const CONST1 : int = 42;
const CONST2 : float = 3.14;

#endif

object OBase:
  cnt1 : int = 0;
  cnt2 : int = 10;
  name : cstring[32];

  function *Create(aname : cstring):  // constructor
    name = aname;
  endfunc

  function *Destroy():
    @.printf("%s destroy\n", &amp;name[0]);
  endfunc

  function Count() [[virtual]]:
    cnt1 += 1;
  endfunc

  function Print():
    @.printf('%s: cnt1=%d, cnt2=%d\n', &amp;name[0], cnt1, cnt2);
  endfunc
endobj

object OChild(OBase):
  function Count() [[override]]:
    inherited;
    cnt2 += 1;
  endfunc
endobj

var obase   &lt;- OBase('OBase');     // '&lt;-' = embedded allocation (global data segment here)
                                   // no automatic destructor call for global embedded objects
var ochild  : OChild = null;

function ObjTest():
  ochild = new OChild('OChild');

  obase.Count();
  ochild.Count();

  obase.Print();
  ochild.Print();

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &amp;ochild.name[0]));

  delete ochild;
endfunc

function cstr_add(dst : cstring, src : cstring):
  var ps     : ^cchar = &amp;src[0];
  var psend  : ^cchar = ps[sizeof(src)];  // [] does not dereference
  var pd     : ^cchar = &amp;dst[0];
  var pdend  : ^cchar = pd + sizeof(dst) - 1; // leave one char for the terminating
  pd += len(dst);
  var pdstart : ^cchar = pd;

  while pd &lt; pdend  and ps &lt; psend  and ps^ &lt;&gt; 0:
    pd^ = ps^;
    pd += 1;
    ps += 1;
  endwhile

  if pd &lt;&gt; pdstart:
    pd^ = 0; // terminate
  endif
endfunc

[[external]] function putchar(c : cchar) -&gt; int;  // from libc

function WriteStr(s : cstring):
  var pc : ^cchar = &amp;s[0];
  while pc^ &lt;&gt; 0:
    putchar(pc^);
    pc += 1;
  endwhile
endfunc

function *Main() -&gt; int:

  var s : cstring[128] = "";
  cstr_add(s, "Hello");
  cstr_add(s, " World!\n");
  WriteStr(s);

  if 3 / 2 * 10 == 15:
    printf('The language is friendly.\n');
  else:
    printf('The language is evil.\n');
  endif

  printf("@langdemo_mod.CONST1 = %d\n", CONST1);
  printf("@langdemo_mod.CONST2 = %.3f\n", @ldm.CONST2);

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &amp;ochild.name[0]));

  ObjTest();

  for i : int = 0 to 5:
    printf(' %d:', i);
    for j : int = 0  count i  step 2:   printf(' %d', j);  endfor
    printf('\n');
  endfor

  var arr : [5]int = [2, 3, 5, 7, 11];
  printf('primes:');
  for i : int = 0  while i &lt; len(arr)  { printf(' %d', arr[i]); }
  printf('\n');

  return 0;
endfunc

/* OUTPUT:

Hello World!
The language is friendly.
@langdemo_mod.CONST1 = 42
@langdemo_mod.CONST2 = 3.140
ochild.name: OChild is null!
OBase: cnt1=1, cnt2=10
OChild: cnt1=1, cnt2=11
ochild.name: OChild
OChild destroy
 0:
 1: 0
 2: 0 2
 3: 0 2 4
 4: 0 2 4 6
 5: 0 2 4 6 8
primes: 2 3 5 7 11

*/

--– CODE EXAMPLE END ---

After reading the description and the demo code, do you think this language has the potential to become widely used? What are its strongest and weakest points?

I would be interested in your opinions, especially from people who have experience with C, C++, Pascal, Python, compiler design, embedded programming, or language design in general.

reddit.com
u/Mean-Decision-3502 — 3 months ago

A Human-Friendly Systems Programming Language — Looking for Feedback

Hi,

I’ve created a new programming language called “?” for now. I’ll reveal its real name later.

My main motivation was to create a universal language that could replace C/C++, FreePascal, and Python for many use cases. I actively use all three of these languages.

I have already put a lot of effort into researching, designing, and implementing the “?” language. At this point, I feel that I have created something promising that really works.

Before I put even more effort into the language and go public with it, I would like to hear more opinions from real people.

I think it would not be enough for this project to be only “a little successful”. For the effort to make sense, it should have the potential to become “very successful”. I believe it might have that potential. If that happens, I will not be able to handle everything alone, so I will need to organize the development and maintenance properly. It will be an open-source project.

The “?” language is not fully finalized yet, and there are still several features that I would like to add to the compiler. However, it has reached a state where the language is already usable and can demonstrate its main ideas and syntax.

The most important current and planned features of the “?” programming language are:

  • Very good human readability
  • Statically typed, with a strict bool type
  • Case-sensitive
  • Compiled to machine code using LLVM
  • A ?-run utility for compile-and-run usage, giving it a script-like feeling
  • Simple C interoperability; the runtime uses libc
  • int / uint use the native machine width; int32 is used for an explicit 32-bit integer
  • Supports C-style preprocessor directives such as #ifdef, but without macros
  • Supports short embedded directives with syntax like #{ifdef ...} ... #{endif}
  • No makefiles are required, for example: #linklib('z') can be written directly in the source code
  • Safe arithmetic rules, for example: 3 / 2 * 10 == 15
  • Two block modes:
    • : ... endXXX blocks, similar to Python style but with explicit closers and no forced indentation
    • { } blocks, similar to C style
  • Statements are closed with ;
  • Carefully designed operator precedence
  • Distinct boolean and bitwise operators, for example and and AND
  • Modify-assignment operators, for example: x += 1; and y =AND= 3;
  • Inline conditionals with iif(), for example: var i : int = iif(strptr <> null, strptr^.x, -1);
  • Support for objects with single inheritance and virtual functions
  • Object variables are references, but objects can still be embedded in BSS, on the stack, or inside other objects
  • Optional single-word namespaces using the @ symbol, for example: @stdio.printf()
  • No self. is needed inside object functions/methods
  • Namespace qualification, such as @stdio., is required to access outside symbols from object methods
  • A well-defined package and module system with flexible namespace merging
  • The “?” runtime library modules are distributed in source-code form
  • Fast compilation using a single-pass forward parser and precompiled module interfaces
  • Manual memory control, with RAII and an ensure statement planned
  • Native C string support
  • Function overloading and default parameters
  • Pointers using the ^ symbol
  • Pointer arithmetic with +, -, and []
  • The [] operator does not dereference pointers automatically
  • Struct pointers are automatically dereferenced on member access with .
  • Function arguments can be passed by reference using ref, refin, refout, and refnull

--– CODE EXAMPLE BEGIN ---

use libc/stdio;
use ./langdemo_mod as ldm  only(CONST1);  // only(), exclude() and "--" control global scope merging

#if false

//Contents of the "langdemo_mod.?"

const CONST1 : int = 42;
const CONST2 : float = 3.14;

#endif

object OBase:
  cnt1 : int = 0;
  cnt2 : int = 10;
  name : cstring[32];

  function *Create(aname : cstring):  // constructor
    name = aname;
  endfunc

  function *Destroy():
    @.printf("%s destroy\n", &amp;name[0]);
  endfunc

  function Count() [[virtual]]:
    cnt1 += 1;
  endfunc

  function Print():
    @.printf('%s: cnt1=%d, cnt2=%d\n', &amp;name[0], cnt1, cnt2);
  endfunc
endobj

object OChild(OBase):
  function Count() [[override]]:
    inherited;
    cnt2 += 1;
  endfunc
endobj

var obase   &lt;- OBase('OBase');     // '&lt;-' = embedded allocation (global data segment here)
                                   // no automatic destructor call for global embedded objects
var ochild  : OChild = null;

function ObjTest():
  ochild = new OChild('OChild');

  obase.Count();
  ochild.Count();

  obase.Print();
  ochild.Print();

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &amp;ochild.name[0]));

  delete ochild;
endfunc

function cstr_add(dst : cstring, src : cstring):
  var ps     : ^cchar = &amp;src[0];
  var psend  : ^cchar = ps[sizeof(src)];  // [] does not dereference
  var pd     : ^cchar = &amp;dst[0];
  var pdend  : ^cchar = pd + sizeof(dst) - 1; // leave one char for the terminating
  pd += len(dst);
  var pdstart : ^cchar = pd;

  while pd &lt; pdend  and ps &lt; psend  and ps^ &lt;&gt; 0:
    pd^ = ps^;
    pd += 1;
    ps += 1;
  endwhile

  if pd &lt;&gt; pdstart:
    pd^ = 0; // terminate
  endif
endfunc

[[external]] function putchar(c : cchar) -&gt; int;  // from libc

function WriteStr(s : cstring):
  var pc : ^cchar = &amp;s[0];
  while pc^ &lt;&gt; 0:
    putchar(pc^);
    pc += 1;
  endwhile
endfunc

function *Main() -&gt; int:

  var s : cstring[128] = "";
  cstr_add(s, "Hello");
  cstr_add(s, " World!\n");
  WriteStr(s);

  if 3 / 2 * 10 == 15:
    printf('The language is friendly.\n');
  else:
    printf('The language is evil.\n');
  endif

  printf("@langdemo_mod.CONST1 = %d\n", CONST1);
  printf("@langdemo_mod.CONST2 = %.3f\n", @ldm.CONST2);

  printf("ochild.name: %s \n", iif(ochild == null, "OChild is null!", &amp;ochild.name[0]));

  ObjTest();

  for i : int = 0 to 5:
    printf(' %d:', i);
    for j : int = 0  count i  step 2:   printf(' %d', j);  endfor
    printf('\n');
  endfor

  var arr : [5]int = [2, 3, 5, 7, 11];
  printf('primes:');
  for i : int = 0  while i &lt; len(arr)  { printf(' %d', arr[i]); }
  printf('\n');

  return 0;
endfunc

/* OUTPUT:

Hello World!
The language is friendly.
@langdemo_mod.CONST1 = 42
@langdemo_mod.CONST2 = 3.140
ochild.name: OChild is null!
OBase: cnt1=1, cnt2=10
OChild: cnt1=1, cnt2=11
ochild.name: OChild
OChild destroy
 0:
 1: 0
 2: 0 2
 3: 0 2 4
 4: 0 2 4 6
 5: 0 2 4 6 8
primes: 2 3 5 7 11

*/

--– CODE EXAMPLE END ---

After reading the description and the demo code, do you think this language has the potential to become widely used? What are its strongest and weakest points?

I would be interested in your opinions, especially from people who have experience with C, C++, Pascal, Python, compiler design, embedded programming, or language design in general.

reddit.com
u/Mean-Decision-3502 — 3 months ago