NedoLang: Beginning
Alone Coder
In September 2016 I got up at work
the task is to find a domestic compiler
intermediate language for ARM processors,
which can be used in military apparatus─
tour. We are developing a new system and
downloadable programs in high language
level would help us a lot.
We couldn’t supply all kinds of Phytons.
- they only work on Windows. Was in
theory version of the compiler for ARM in cos─
same version of Astra Linux for ARM (on ka─
Which computer should I run it on?). And "prog─
frames from the Internet" in military equipment
cannot be used, everything must be returned
with a decimal number in accordance with
GOSTs. Of course you could print it out
millions of lines of GCC source and submit as
your program... with all the bookmarks :)
In the last issue of Info Guide we talked about─
compilers judged. Perhaps I read those
the articles are more detailed than you - I translated them─
dil :) After that there was a picture in my head,
which was not there 10 years ago when I wanted
write an OS and started collecting ideas for systems─
programming topics in one folder.
There wasn't much hope that the ZX would sing─
native versions of Oberon and ZX Like are available
Pascal, the full Hitech C source is not ours─
elk, but for the z88dk and SDCC compilers it’s easy
there won't be enough memory. Although, by the way, he was a decom─
sawedTurbo Pascal source, but it
written in assembler, it’s hard to develop this─
bottom. Even the authors of iS-DOS did not make the norm─
High-level language for development
for your system.
Why not write a product that is useful
both here and there?
* * *
Just at this time I in general terms
finished a 3D engine in the style of Total Eclipse
(I showed it at the nearest party
NedoPC ), and my head became somewhat freer.
In general, for the sake of experiment, I sat down on─
wrote the simplest procedural language, ka─
who came to mind. It compiled to
assembly text and transferred to assembler─
Blair does all the work with tags. First he has
There weren't even types. The hardest thing was
- calculation of expressions. And that’s not difficult. A
everything worked in the Delphi form and gave
code like this:
;PUSHNUM 1
PUSH HL
LD HL,0+1
;POPVAR _nemain_v1
LD [_nemain_v1],HL
POP HL
;PUSHNUM 2
PUSH HL
LD HL,0+2
;PUSHNUM 2
PUSH HL
LD HL,0+2
;OPERATION +
POP DE
ADD HL,DE
;POPVAR _nemain_v2
LD [_nemain_v2],HL
POP HL
;CALL tnemain
CALL tnemain
;PUSHNUM 2
PUSH HL
LD HL,0+2
;NEG
XOR A
SUB L
LD L,A
SBC A,H
SUB L
LD H,A
;POPVAR _main_b
LD [_main_b],HL
POP HL
У каждого регистра была своя роль. Это
что-то вроде кодогенератора в C Warp.
Важно отметить, что это уже изначально
был именно язык высокого уровня, а не ас─
семблер с процедурным синтаксисом типа
"Sphinx C--" by Peter Cellik,"Springboard"
С. Veremeenko, "PLM" Alexandra Koryushkina
or "K65" yKK for Atari 2600. Planned─
there were only compiler hints for bo─
more efficient code generation.
I had long-standing problems with the C language,
therefore the first versions of the compiler had
the syntax is very far from it.
To begin with, already at the lexer level the source─
day was perceived as an alternation of words and
single-character characters. This is extremely le─
Easy to disassemble. There were only a couple of exceptions
type of brackets, but they did not create large
difficulties - simply ignored "pus─
that" word.
Function parameters are fundamentally not from─
differed from local labels, in the parameters
there was an assignment operator, and when I wrote─
types curled - and type. And they were kept (and
are still stored) just like a loka─
real variables. Recursion was done and things were done─
is achieved by saving old pas on the stack─
parameters (during a call) and old locations─
nal variables (during entry-exit).
When I sketched out the interface for calling the function─
tions (with named parameters), I'm still─
hall compiler for a couple of spektrumists. Mind─
Well, there weren’t any special reviews - after all, it’s real─
but in this compiler nothing was possible
collect. I didn’t even check that re─
resulting assembly text of the assembly─
rushes. And whatit works...How does it even work?
check? I could only check that each─
The current turn of the language is translated into concrete─
This is a complete turn of assembly code. But this is not true─
No need to fix it forever, otherwise you won’t get it
nice output code!
Then, thinking in the direction of large projects─
comrade, I introduced the concept of nested simples─
names, so inside the function there is another
the function looked like... well, imagine
imagine how you access a file in another
directories:
../dir2/filename
It’s the same with functions, only instead of
dots were also used for slashes:
.func2.variable
Gorgeous? I came up with it myself :) And the use
_variable to indicate global re─
my neighbor Grisha suggested them, before this glo─
point variables were marked with$ or
# on the right.
In fact, it is strange that in C and UNIX,
who seemed to write the same people─
di, completely different understanding of spaces
names (and at the same time call formats).
But I immediately had to give up
.func2.variable in the call parameters - if─
too long for real work. But first─
there just wasn't a chancedo
differently - because even if you remember the name
(more precisely, the full path) of the called function and
stick variable to it, then inside pa─
The parameter can also be a function call, and we
Let's immediately forget the current name! Yes, yes, in whom─
the filer had recursion and lines in the locale─
ny variables. How will it be self-compiled─
lyrate when it becomes a product?
The lines were first placed inAnsiString. This
same as Delphi. But they couldn’t lie there for long─
li.I did not plan to implement object-based
oriented programming. (In general
I didn’t plan a lot of things, but then I showed up─
there was an opportunity. Maybe I’ll come to OOP.)
It was logical to assume that the lines in
in the end they will lie statically
allocated array with length as maximum
ID. But saving such an array
on the stack... The stack is not rubber. So I
decided to remove arrays from local variables─
nykh, and the dirty work of gluing the marks is not
turn to assembler.
At the first stage, the tongue was held as
possible in small sizes. The source consisted of
parser (about1000 lines) and code generator─
tora (about500). System on ZX Spectrum
was assumed to be constantly hanging in pa─
mint together with a text editor and one
current source. I even assumed
that 48Kenough for such a service. Why
48K? And this is so as not to support banking in
compiler.
And now in detail:
09.14-16.2016 - project started. During the day─
it says “written by the compiler”. Judging by
logam #mhm, first version from 14th
weighed only200 lines. She was compiling
source text from one window field to pseudo─
docode in another field of the same window. But from it
Only files*.res and .dpr were saved.
09/22/2016 - sent the first release to
rating for spektrumists:
┌────────────────────────── ──────────────┐
Hello, comrades!
I present to your attention a new language
programming :) The language is optimized for
ease of compilation (renamed Oberon:)
as follows:
1) a symbol is always expected after a word,
it can be read immediately, no “peeping”
forward one character."
2) all characters are single-byte. Therefore, when
analysis of some"<" no need to look
to the next character. All rare operations
are encoded using escape codes (> for >=,
< for<=,= for !=,* for <<,/ for >> ),
they are already sent to the parser in the same way─
free.
3) there is not a single one reserved
words. The command field only expects
team. Therefore the assignment is done
command=var(expression) (for variables)
or command*value(expression) (for cells
memory).
4) there is no label table. This is a cart
relies on assembler. More precisely, in the current
implementation has a list of labels, but only
in order to generate them beautifully in co─
nce of the source (but you can just as successfully ge─
keep them in the middle using org's
reassignable labels). Because of this lo─
local and global labels differ with
using postfix#.
5) there is no C-like function call.Pa─
parameters are transferred by name, directly to the cell
memory.
6) recursion is described explicitly - with help─
I'm using program brackets
var:int(parameter)command
(this is how all local variables are described,
which are affected by recursion). After
recursive call locales are considered to be used─
protected, they can only be used outside
theseprogram brackets.
7) constants in expressions are described
explicitly - via=(constexpr)
Features:
- nested comments.
- no semicolons.
Jambs:
- loop onlywhile (correctable).
- labels for transitions are local only
(correctable).
- no functions with a return value.
- no types. Now all variables are of type
u16. Any ideas on how to implement the functions?
and types, without complicating the compiler too much?
This is what the source text looks like:
int(a)
proc(nemain)
(
par:int(v1) ( {recursive param}
par:int(v2) ( {recursive param}
=a# (=(1>5)+2*5)
=a# (*(22/#33/-v1)--6*7+-a#)
*a# ("as")
)
)
)
proc(main)(
int(b)
int(c)
call(nemain,v1(1),v2(2+2))
=b (-2)
{=a# (0)}
while ((a#<5)|bff)(
=a# (a#+%1)
goto(mylabel)
while (c=0) =c(1)
:mylabel:
=b (b+a#)
)
) {proc}
Вот как выглядит результат:
ORG #8000
begin:
;.CSEG
tnemain:
;PUSHVAR _nemain_nemain_v1
PUSH HL
LD HL,[_nemain_nemain_v1]
;PUSHVAR _nemain_nemain_v2
PUSH HL
LD HL,[_nemain_nemain_v2]
;PUSHNUM(1>5)
PUSH HL
LD HL,0+(1>5)
;PUSHNUM 2
PUSH HL
LD HL,0+2
;PUSHNUM 5
PUSH HL
LD HL,0+5
;OPERATION *
LD B,L
POP HL
ADD HL,HL
DJNZ $-1
;OPERATION +
POP DE
ADD HL,DE
;POPVAR __a
LD [__a],HL
POP HL
;PUSHNUM 22
PUSH HL
LD HL,0+22
;PUSHNUM #33
PUSH HL
LD HL,0+#33
;OPERATION /
POP DE
CALL DIV_DE_HL
;PUSHVAR _nemain_v1
PUSH HL
LD HL,[_nemain_v1]
;NEG
XOR A
SUB L
LD L,A
SBC A,H
SUB L
LD H,A
;OPERATION /
POP DE
CALL DIV_DE_HL
;PEEK
LD A,[HL]
INC HL
LD H,[HL]
LD L,A
;PUSHNUM 6
PUSH HL
LD HL,0+6
;NEG
XOR A
SUB L
LD L,A
SBC A,H
SUB L
LD H,A
;PUSHNUM 7
PUSH HL
LD HL,0+7
;OPERATION *
POP BC
CALL MUL_BC_HL
;OPERATION -
EX DE,HL
POP HL
OR A
SBC HL,DE
;PUSHVAR __a
PUSH HL
LD HL,[__a]
;NEG
XOR A
SUB L
LD L,A
SBC A,H
SUB L
LD H,A
;OPERATION +
POP DE
ADD HL,DE
;POPVAR __a
LD [__a],HL
POP HL
;PUSHVAR __a
PUSH HL
LD HL,[__a]
;PUSHNUM "as"
PUSH HL
LD HL,0+"as"
;POKE
EX DE,HL
POP HL
LD [HL],E
INC HL
LD [HL],D
POP HL
;POPVAR _nemain_nemain_v2
LD [_nemain_nemain_v2],HL
POP HL
;POPVAR _nemain_nemain_v1
LD [_nemain_nemain_v1],HL
POP HL
RET
...
;.DSEG
__a:
DW 0
_nemain_v1:
DW 0
_nemain_v2:
DW 0
_main_b:
DW 0
_main_c:
DW 0
end:
└─────────────────── ─────────────────────┘
Attached to the letter was the source code of the compilation─
ra. Fate turned out that this was the first
the release version was preserved only in
letter. But all the following versions are stock─
went to the archives.
09/23/2016:
- added export of comments and numbers
lines into the body of the output assembler file
- added arrays asint(A[15]). Access
to cells so far only through calculation with
using the constant[WORDSIZE] and times─
addressing@var
- changed=(constexpr) на [constexpr]
- добавилif()then()else()
05.10.2016 - добавлена поддержка имено─
ванных пространств имён (командаmodule ):
module(vasya)(
int(a2)
int(array[15])
proc(dup)(
int(p1)
int(p2)
if(p1#0)then(=p2(p1))else()
*(@array$+[7*WORDSIZE])(555)
=p2(*(@array$+p1*[WORDSIZE]))
return(p1+p1)
)
...
) {module}
) {end}
Из этого выдавался код в таком стиле:
;PUSHNUM 0
PUSH HL
LD HL,0+0
;OPERATION =
POP DE
OR A
SBC HL,DE
JR Z,$+5
LD HL,#FFFF
;JNZ l.vasya.dup.0
LD A,L
OR H
POP HL
;END COUNT
JP NZ,l.vasya.dup.0
;line 8
;BEGIN COUNT
;PUSHVAR _.vasya.dup.p1
PUSH HL
LD HL,[_.vasya.dup.p1]
;POPVAR _.vasya.dup.p2
LD [_.vasya.dup.p2],HL
POP HL
;END COUNT
l.vasya.dup.0:
;JUMP l.vasya.dup.1
JP l.vasya.dup.1
l.vasya.dup.1:
11.10.2016:
- types appeared(INT and UINT) - different─
is determined by the presence of a sign for constants. It is inconvenient that
int/uint check is in two places - in
compile_command and incompile_var. It would be necessary
prefix character to be processed as
team? Then there will be no intersection along
first letter. (Then I simply selected reading
like in a separate procedure, and much later -
inside reading id so name
type is in the labels table.)
- call commandCALL replaced with ?
-ELSE was replaced with ~ , because it was obligatory─
line (later I rolled back this change)
10/12/2016:
- added types BOOL, CHAR, STRING (but
not supported yet)
- type checking appeared according to the table me─
current Before this, there were hopes to remove it altogether
table of labels:
type control can be entrusted to the assistant─
mbler, if you assign a type to each word
but for this you need to know the types from somewhere
variables, both during assignment and reading─
ni!
cases:
1. function parameter - adding a type is easy
(inside /**/)
2. reading the global
3. record global
4.reading locale
5. recording a non-changing locale (you can
combine with definition)
6. record changing locale
7. function call - adding a type is easy
(inside /**/)
you can _xxxxxxx in the context of the term dawn─
reserve not for globals, but for hints
compiler (in C it will be empty)
or duplicate the entire set of types with
underlining? (how then to expand the system─
topic of types?)
or encode the type in the variable name
(how then to extend the type system?)
ivar
uvar - not in Hungarian notation?
lvar
cvar - not in Hungarian notation
bvar - in Hung. notation is bool or byte
fvar - I would like for flag (sometimes
in Hungarian notation)
pvar - type is not written in Hungarian notation
pointer?
This is all a bad style of Hungarian note─
tions (good is the class of the value or its
dimension)
- variables and parameters are declared together
mandoy+ , and local variables are recursive─
functions are designed asstacked (with
call too):
func(nemain)stacked(uint(z1)int(z2))
(
+uint(v1) {parameter}
+int(v2) {parameter}
(
=a$(?dup(p1(+15)p2(11))){call function}
*a$("as"){write memory}
)
)
- in the compiler you have separate procedures─
comment water(emitcomment) and debugging─
new information(emithint)
- occupancy is checked in the code generator
registers (previously the same commands
always compiled the same), but practically
technically this is not used, the current meaning is─
always inhl or de
- added left shift and
right by 1 bit (prefix< , > )
- added typeBYTE for variables
10/13/2016:
- types of functions
- typeBYTE is also supported in the function parameters─
ctions, branching by type transferred from com─
peeler into code generator
- depth is checked in the code generator
stack
- modules are highlightedemits (outputtogether─
niy) andemitdirectives (output of AC directives
samplers, which were assumed to be machine─
independent)
- the code generator is divided into procedures,
not checking register occupancy (access
compiler), and those checking them and
generating code directly (lower
level - inaccessible to the compiler)
10/14/2016:
- added typeLONG for variables.Longs
made equal in size to two ints. Was
The question is, in what order should the halves be stored?
on the stack, I chose the low part on top
(this is how you can add and subtract, having only
three registers)
- the lexer is allocated to the modulereads, comp─
lator is allocated to the modulecompile (in the main
the module remains GUI)
10/17/2016:
- typeLONG is supported for functions
- in the lesson procedure code generator
registers emitgethl, emitgetde, emitgetbc
replaced byemitgetreg with the parameter, add─
a byte context with a different order is includeduse of registers. All possible co─
The register positions looked like this:
-
hl
hl,de
a
c,a
hl,a
hl,c,a
bc,hl,de
bc,hl
10/18/2016
- in the code generator into separate procedures
the register number is transmitted, which we
get viagetnewreg (top of stack) or
getoldreg (previous value) - now
"more" and "less" comparisons are made
the same
- a separate process was made for comparisons─
subtraction fool that only produces flags
- part of the code generator available to computers─
lator (machine-independent), allocated in
moduleemitcommands
- in the header of the recursive functions the word
stacked with a list of local variables
replaced with local (I still couldn't
come up with a code that is processed equally─
both wordsstacked - here and when calling
ve)
10.19.2016:
- added renumbering of registers (mo─
dulregs ), now from the point of view of the code─
notator, all registers are equal. But in
the regs module itself is still limited in volume─
possible register states
- in an 8-bit context, possible states─
changing registers now (decided to save with
usingpush bc...pop af ):
-
b
a
b,a
10.20.2016:
- registers are not allocated according to the list
possible states, and by priority (hl,de,
bc for 16-bit context,a, h for bye─
of the same context)
- studied the ARM Thumb command system and was
shocked that there is no easy way to use─
watconstant. Postponed ARM for later when
I'll debug the Z80. The compiler package includes─
There was a memo on ARM commands.
The question arose - how to compile for
multiple processors?
a) branches in each procedure emit...
(other options, other command codes, other
register name tables)
the number of registers should not differ
b) a separate code generator as a program
it will be inconvenient to debug data transfer from
compiler to code generator
and will slow down
c) objectsemitasmz80,emitasmarm,heritage─
from one classemitasm
it would be inconvenient to rewrite the compiler in
your own language
d) structure with addresses of all procedures
emit...
is filled ininitz80andinitarm(call
one of the two)
e) code for each target in separate
files, a separate executable is generated
how to collect from different files? through copy─
roving? or collect all machine dependent
inclusions in one main module?
I chose the last option.
10/21/2016 - bugfixes (especially
prettySLA instead of SRL )
Share your thoughts about the article