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