Skip to content

Commit 662423b

Browse files
committed
Various
1 parent bef692a commit 662423b

26 files changed

+423
-439
lines changed

py/README.md

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,5 @@
1-
### EasyCoder ###
1+
## The System Controller
22

3-
This version of **_EasyCoder_** is written in Python, partly as an exercise and partly to build a viable alternative to Bash or Perl when creating shell scripts. There's currently no specific documentation outside of this ReadMe but many of the features are the same as in the JavaScript version.
3+
Here are all the files that are installed on the system controller, a small computer located in the premises where heating is being managed. Two different controller models are catered for; a Raspberry Pi (Model B onwards) and an Orange Pi Zero 2.
44

5-
The test script benchmark.ecs contains examples of many of the language features.
6-
7-
## How it works ##
8-
9-
The `easycoder.py` script starts by defining all the 'packages' that will be used. Each one of these handles a set of functionality related to a given application domain. Currently there are only two; the 'core' package, which contains things needed by virtually any programming language, like variables, control structures and so on, and - under development - the 'graphics' package, which builds on TKinter to provide a set of features enabling GUI applications to be built quickly without getting bogged down in UI programming.
10-
11-
Then it loads the requested script and calls Program to compile and run it.
12-
13-
### The Program class ###
14-
15-
This contains the script tokenizer, core runtime functions for getting values and conditions and error handlers. It calls the tokenizer then the compiler, then runs the resulting compiled script.
16-
17-
### The Compiler class ###
18-
19-
The compilation strategy is not a textbook one, with parsers and lexers and so on. It's a much simpler technique based on a hypothesis as to the way humans process written text, particularly when it's written in a foreign language. The strategy is to set up a marker, then starting with the first language package work along the incoming tokens, calling compilation modules until either a complete statement has been successfully processed or until one of the modules reports it is unable to perform the compilation. At this point the compiler backs up to the marker and tries again with another language package.
20-
21-
Much of the Compiler class consists of functions to return the current or next token, test the current token to see - for example - if it's a valid symbol in the script or return the 'value' of a token or that of a condition - see **Values and Conditions** below.
22-
23-
### The Core class ###
24-
25-
This class contans compiler and runtime modules for core keywords, values and conditions needed by virtually any programming language. Internally it has the following:
26-
27-
- a `k_xxx()` function to compile each language keyword (where `xxx` is the keyword)
28-
- a `r_xxx()`function to run each language keyword
29-
- a `compileValue()` function to compile 'values'
30-
- a `v_xxx()` function to evaluate each runtime 'value'
31-
- a `compileCondition()` function to compile 'conditions'
32-
- a `c_xxx()` function to evaluate each runtime 'condition'
33-
34-
Each extension package has the same structure and deals with its own vocabulary and syntax. For example, `ec_graphics.py`, which is currently under development.
35-
36-
The individual compiler functions make heavy use of the Compiler class to retrieve tokens and process them. When they successfully complete the compilation of any given language structure they return an 'intermediate code' object with some standard fields such as the name of the package (the 'domain'), the script line number, and other fields that relate to the specific keyword, value or condition. This object goes into the array that becomes the program to be run.
37-
38-
At runtime the **Program** class starts at the beginning of the program array and looks in the first compiled object to find which package it belongs to and the name of the keyword. It then calls the appropriate `r_xxx()` function, which does whatever is necessary for that keyword and returns an updated program counter. This is then used to repeat the process and so on.
39-
40-
### Values and Conditions ###
41-
42-
In EasyCoder a 'value' is anything that resolves to a numeric, string or boolean value, these being the 3 types that can be held in a `variable` - the primary storage type in the `Core` package. Constants are evaluated at compile time but anything else is deferred as its value may not be known until it is accessed. Examples are the contents of a variable, the time, the size or position of something or a value read from a remote server. In all these cases the `compileCondition()` function returns a _specification_ of how to get the value rather than attempting to return the value itself.
43-
44-
Similarly with conditions, these may also not be subject to evaluation at compile time so they are also deferred in the same way.
45-
46-
At runtime the `v_xxx()` and `c_xxx()` functions take the descriptions of the values and conditions and evaluate them. Sometimes there is a degree of recursion involved because some values can have other values as part of their definition. Examples are `left N of String`, which is a single value but references 2 variables.
47-
48-
EasyCoder has the concept of a `valueholder`. Every `variable` is one because it is able to hold a value, though other symbol types such as `file` or `table` (if/when these exist) may not. A symbol that is a valueholder can always be used in place of a constant, but the converse is not always true. For example, in `add X to Y` Y must be a valueholder, so `add X to 5` will cause a compilation error. Likewise, `add X to File` will most likely result in an error unless either `File` is a valueholder (e.g. declared as a `variable`) or another package allows this syntax to be used for its symbol types. On the other hand, `add the size of TheFile to Y` is legal, assuming `MyFile` is a symbol of a type that has a `size` attribute. None of this is very complicated; it's just common sense.
5+
To install RBR on a new system (one of the above), connect it to your internet router with an Ethernet cable then follow the instructions in the user interface at [https://rbrheating.com](https://rbrheating.com). Tap/click on the hamburger icon, choose Help, go to the index and from there to the System Controller page.

py/crondata-empty.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@reboot sleep 10; sh /home/pi/init.sh

py/crondata.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* * * * * sh /home/pi/run.sh
2+
@reboot sleep 10; sh /home/pi/init.sh

py/dhcpcd.template

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
hostname
2+
clientid
3+
persistent
4+
option rapid_commit
5+
option interface_mtu
6+
#option ntp_servers
7+
require dhcp_server_identifier
8+
slaac private
9+
10+
# Static IP configuration:
11+
interface !LAN!
12+
static ip_address=!STATIC_IP!/24
13+
static routers=!GATEWAY!
14+
static domain_name_servers=8.8.8.8

py/ec.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import sys
44
from ec_program import Program
55
from ec_core import Core
6-
from ec_graphics import Graphics
7-
from ec_graphics import Graphics
6+
from ec_p100 import P100
87

98
class EasyCoder:
109

@@ -18,7 +17,7 @@ def __init__(self):
1817
source = f.read()
1918
f.close()
2019

21-
Program(source, [Core, Graphics])
20+
Program(source, [Core, P100])
2221

2322
if __name__ == '__main__':
2423
EasyCoder()

py/ec_classes.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
class FatalError():
44
def __init__(self, compiler, message):
55
compiler.showWarnings()
6-
lino = compiler.tokens[compiler.index].lino + 1
7-
sys.exit(f'Line {lino}: {message}')
6+
lino = compiler.tokens[compiler.index].lino
7+
script = compiler.script.lines[lino].strip()
8+
sys.exit(f'Compile error in {compiler.program.name} at line {lino + 1} ({script}): {message}')
89

910
class RuntimeError:
10-
def __init__(self, message):
11-
print(f'Runtime Error: {message}')
11+
def __init__(self, program, message):
12+
if program == None:
13+
sys.exit(f'Runtime Error: {message}')
14+
else:
15+
code = program.code[program.pc]
16+
lino = code['lino']
17+
script = program.script.lines[lino].strip()
18+
sys.exit(f'Runtime Error in {program.name} at line {lino + 1} ({script}): {message}')
1219

1320
class Script:
1421
def __init__(self, source):

py/ec_compiler.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,8 @@ def nextIsSymbol(self):
8989
self.next()
9090
return self.isSymbol()
9191

92-
def mark(self):
93-
self.marker = self.index
94-
95-
def rewind(self):
96-
self.index = self.marker
92+
def rewindTo(self, index):
93+
self.index = index
9794

9895
def getLino(self):
9996
if self.index >= len(self.tokens):
@@ -119,21 +116,21 @@ def getSymbolRecord(self):
119116
def compileLabel(self, command):
120117
return self.compileSymbol(command, self.getToken(), False)
121118

122-
def compileVariable(self, command, valueHolder):
119+
def compileVariable(self, command, valueHolder=False):
123120
return self.compileSymbol(command, self.nextToken(), valueHolder)
124121

125122
def compileSymbol(self, command, name, valueHolder):
126123
if hasattr(self.symbols, name):
127124
FatalError(self, f'{self.code[self.pc].lino}: Duplicate symbol name "{name}"')
128125
return False
129126
self.symbols[name] = self.getPC()
130-
command['isSymbol'] = True
131-
command['used'] = False
132-
command['valueHolder'] = valueHolder
127+
command['type'] = 'symbol'
128+
command['isValueHolder'] = valueHolder
133129
command['name'] = name
134130
command['elements'] = 1
135131
command['index'] = 0
136132
command['value'] = [None]
133+
command['used'] = False
137134
command['debug'] = False
138135
self.addCommand(command)
139136
return True
@@ -144,20 +141,23 @@ def compileToken(self):
144141
# print(token)
145142
if not token:
146143
return False
147-
self.mark()
144+
mark = self.getIndex()
148145
for domain in self.domains:
149146
handler = domain.keywordHandler(token)
150147
if handler:
151148
command = {}
152149
command['domain'] = domain.getName()
153150
command['lino'] = self.tokens[self.index].lino
154151
command['keyword'] = token
152+
command['type'] = None
155153
command['debug'] = True
156154
result = handler(command)
157155
if result:
158156
return result
157+
else:
158+
self.rewindTo(mark)
159159
else:
160-
self.rewind()
160+
self.rewindTo(mark)
161161
FatalError(self, f'No handler found for "{token}"')
162162
return False
163163

@@ -183,7 +183,7 @@ def compileFrom(self, index, stopOn):
183183
keyword = token.token
184184
# line = self.script.lines[token.lino]
185185
# print(f'{keyword} - {line}')
186-
# if keyword != 'else':
186+
# if keyword != 'else':
187187
if self.compileOne() == True:
188188
if self.index == len(self.tokens) - 1:
189189
return True

py/ec_condition.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ def __init__(self, compiler):
55
self.getToken = compiler.getToken
66
self.nextToken = compiler.nextToken
77
self.peek = compiler.peek
8+
self.getIndex = compiler.getIndex
89
self.tokenIs = compiler.tokenIs
9-
self.mark = compiler.mark
10-
self.rewind = compiler.rewind
10+
self.rewindTo = compiler.rewindTo
1111
self.program = compiler.program
1212

1313
def compileCondition(self):
14-
self.mark()
14+
mark = self.getIndex()
1515
for domain in self.domains:
1616
item = domain.compileCondition()
1717
if item != None:
1818
item['domain'] = domain.getName()
1919
return item
20-
self.rewind()
20+
self.rewindTo(mark)
2121
return None
2222

2323
def testCondition(self, condition):

0 commit comments

Comments
 (0)