e# language vision as an 'embryonic development' story
In this post I present my vision of e# reflected through history of assemblers. For each stage in the assemblers’ history I am providing presumptive equivalents in e#, incrementing the complexity of each next example.
Statements, reused from the previous step are grayed out to focus you attention on new features. Bold indicates possible keywords of e#-tool language.
I hope this way of expressing will help the reader to understand my idea and an approach on its implementation.
‘Ancient’ programming
Instruction codes are directly placed to the current location of the executable image as binary values.
|
|
B'00 0000 0000 0000' |
B'00 0000 0000 0000' |
B'10 1000 0000 0000' |
|
Mnemonic assembler
Instead of writing in binary codes, commands are replaced with their mnemonic names. Mnemonics are defined with keyword instruction and implemented as a placement of proper instructions codes
|
instruction nop { (B'00 0000 0000 0000'); }; |
instruction goto(bit11 addr) |
{(B'10 1000 0000 0000' | ( addr & B'111 1111 1111')); }; |
nop; |
nop; |
goto 0; |
|
Symbolic assembler
On this stage addresses are replaced with symbolic names and instructions operate on names rather then on addresses. A new essence is introduced – label. Instruction goto renamed to go due to change in notation and it is defined for the label only, thus developer can not apply it for a data address.
|
instruction nop { (B'00 0000 0000 0000'); } |
instruction label.go |
{(B'10 1000 0000 0000' | (this & B'111 1111 1111' )); }; |
|
label there; |
nop; nop; |
there.go; |
|
Relocatable assembler
This approach has introduced sections – logical portions of code with unspecified absolute addresses1. A new essence is introduced – section. Section is started by specifying its class (type) and optionally name. Section scope is denoted by braces. Named sections are accessible as build-time objects, some of their properties (such as size, absolute addresses) can be made available as run-time constants.
|
instruction nop { (B'00 0000 0000 0000'); }; |
instruction label.go |
{ (B'10 1000 0000 0000' | (this & B'00 0111 1111 1111' )); }; |
instruction file.inc |
{ (B'00 1010 1000 0000' | (this & B'00 0111 1111' )); }; |
data mydata { file myvar; }; |
code { |
label there; |
nop; nop; |
there.go; |
myvar.inc; }; |
|
Infix assembler
Assemblers of this kind have drifted from operation mnemonics and fixed-column format toward to more ‘natural’ syntax, especially for assignment and arithmetic operations. This example illustrates alternative definition of instruction inc2.
|
instruction nop {(B'00 0000 0000 0000'); }; |
instruction label.go |
{(B'10 1000 0000 0000' | (this & B'00 0111 1111 1111' )); }; |
instruction file.inc |
{(B'00 1010 1000 0000' | (this & B'00 0000 0111 1111' )); }; |
instruction file '+=' (const 1) { this.inc; } |
data { file myvar; }; |
code { |
label there; |
nop; nop; |
myvar += 1; |
there.go; }; |
|
Meta-assembler Meta-assemblers were aimed making the language device independent by providing a separate, replaceable device and instruction set definitions. New concepts are introduced – device definition and application’s target device.
|
device PIC16F84; |
instruction nop {(B'00 0000 0000 0000'); }; |
instruction label.go |
{(B'10 1000 0000 0000' | (this & B'00 0111 1111 1111' )); }; |
instruction file.inc |
{(B'00 1010 1000 0000' | (this & B'00 0000 0111 1111' )); }; |
instruction file '+=' (const 1) |
{ this.inc; } |
end device; |
application myapp for PIC16F84; |
data { file myvar; }; |
code { |
label there; |
nop; nop; |
myvar += 1; |
there.go; }; |
end application; |
|
Object-oriented meta-assembler3
This stage contributes object-oriented approach to the device definition and application programming. Each device resource is defined as an object of a class, these classes forms a hierarchy and encapsulate implementation details.
Application defines and uses build-time abstractions. Mapping of these abstractions to MCU resources is explicitly provided by the developer.
|
device PIC16F84 extends PIC16; |
instruction nop {(B'00 0000 0000 0000'); }; |
class label |
instruction go |
{(B'10 1000 0000 0000' | (this & B'00 0111 1111 1111' )); }; |
end class; |
class subroutine extends label |
instruction call |
{(B'10 0000 0000 0000' | (this & B'00 0111 1111 1111' )); };
|
end class; |
class file |
constructor { reserve(1); } |
instruction inc |
{(B'00 1010 1000 0000' | (this & B'00 0000 0111 1111' )); }; |
instruction '+=' (const 1) |
{ this.inc; } |
end class; |
end device; |
application myapp for PIC16F84; |
class mycount extends private file |
public instruction inc; |
end class; |
data { mycount myvar; }; |
code { |
label there; |
nop; nop; |
myvar += 1; |
there.go; } |
end application; |
|
e#
Proposed language will support capabilities of all mentioned assemblers and in addition will provide ability to program builder behavior for all kinds of activities: resource allocation, image verification, simulation, programming (the device), documenting and others. This example illustrates statements for simulation-time verification and documentation4. Device definition provides code for validating currently selected bank (page) against the bank (page) intended for access and raises a error if a mismatch is detected. This technique will help to detect banking errors on early stage. Application uses simulation-time code to validate initial state of a variable.
|
devicePIC16F84 extends PIC16; |
instruction nop {(B'00 0000 0000 0000'); }; |
class label |
instruction go |
{(B'10 1000 0000 0000' | (this & B'00 0111 1111 1111' )); } |
sim { if ! pageCheck(this) then raise error('"go" paging error); } |
doc { 'GO is an unconditional branch. The eleven bit immediate value is loaded into PC bits <10:0>. The upper bits of PC are loaded from PCLATH<4:3>. GOTO is a two cycle instruction.'}; |
end class; |
class subroutine extends label |
instruction call |
{(B'10 0000 0000 0000' | (this & B'00 0111 1111 1111' )); } |
sim { if ! pageCheck(this) then |
raise error('call paging error'); } |
doc { 'Call Subroutine. First, the 13-bit return address (PC+1) is pushed onto the stack…'}; |
end class; |
class file |
constructor { reserve(1); } |
instruction inc |
{(B'00 1010 1000 0000' | (this & B'00 0000 0111 1111' )); }; |
sim { if ! bankCheck(this) then raise error('banking error'); } |
instruction '+=' (const 1) |
{ this.inc; } |
end class; |
end device; |
application myapp for PIC16F84; |
class mycount extends private file |
doc { 'mycount is a GPR with restricted access') |
public instruction inc; |
end class; |
data mydata { mycount myvar; }; |
if pic.banks[3].present then mydata.bank := pic.banks[3]; |
code { |
sim { if myvar.value <> 0 then |
debug.print 'myvar is not initialized!'; } |
label there; |
nop; nop; |
myvar += 1; |
there.go; } |
end application; |
|
1In the traditional approaches a linker (loader) picks a ‘good enough’ space for each section and places codes in that space. Then it goes trough a fixup-table and adjusts instructions according to actual addresses. e# will implement a different technique – since all build steps (assembling, linking, etc) will be performed in a single run, the linker routine will operate on the same internal representation of object created by the compiler routine
2In this example I did not use postfix form ‘++’ intentionally, in my opinion this kind of syntax does not improve readability.
3I am not aware about existence of such in the real world
4Documentation samples are taken from PICmicro?„? Mid-Range MCU Family Reference Manual
Post a Comment