easycoder 251105.1__py2.py3-none-any.whl → 251215.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
easycoder/ec_classes.py CHANGED
@@ -1,4 +1,5 @@
1
- import sys
1
+ import sys, paramiko
2
+ from typing import Optional, Any
2
3
 
3
4
  class FatalError(BaseException):
4
5
  def __init__(self, compiler, message):
@@ -12,7 +13,7 @@ class NoValueError(FatalError):
12
13
  def __init__(self, compiler, record):
13
14
  super().__init__(compiler, f'Variable {record["name"]} does not hold a value')
14
15
 
15
- class AssertionError:
16
+ class RuntimeAssertionError:
16
17
  def __init__(self, program, msg=None):
17
18
  code = program.code[program.pc]
18
19
  lino = code['lino']
@@ -22,7 +23,7 @@ class AssertionError:
22
23
  print(message)
23
24
  sys.exit()
24
25
 
25
- class RuntimeError:
26
+ class RuntimeError(BaseException):
26
27
  def __init__(self, program, message):
27
28
  if program == None:
28
29
  sys.exit(f'Runtime Error: {message}')
@@ -56,11 +57,283 @@ class Token:
56
57
  def __init__(self, lino, token):
57
58
  self.lino = lino
58
59
  self.token = token
59
-
60
- class Object():
61
- """Dynamic object that allows arbitrary attribute assignment"""
62
- def __setattr__(self, name: str, value) -> None:
63
- self.__dict__[name] = value
64
-
65
- def __getattr__(self, name: str):
66
- return self.__dict__.get(name)
60
+
61
+ ###############################################################################
62
+ # This is the set of generic EasyCoder objects (values and variables)
63
+
64
+ ###############################################################################
65
+ # A multipurpose value object. Holds a single value, with domain and type information
66
+ class ECValue():
67
+ def __init__(self, domain: Optional[str] = None, type: Optional[str] = None,
68
+ content: Any = None, name: Optional[str] = None):
69
+ object.__setattr__(self, 'domain', domain)
70
+ object.__setattr__(self, 'type', type)
71
+ object.__setattr__(self, 'content', content)
72
+ object.__setattr__(self, 'name', name)
73
+ object.__setattr__(self, 'properties', {})
74
+ object.__setattr__(self, 'locked', False)
75
+ object.__setattr__(self, '_attrs', {}) # Store dynamic attributes
76
+
77
+ def __setattr__(self, name: str, value: Any) -> None:
78
+ """Allow setting any attribute dynamically."""
79
+ if name in ('domain', 'type', 'content', 'name', 'properties', 'locked', '_attrs'):
80
+ object.__setattr__(self, name, value)
81
+ else:
82
+ # Store dynamic attributes in _attrs dict
83
+ self._attrs[name] = value
84
+
85
+ def __getattr__(self, name: str) -> Any:
86
+ """Retrieve dynamic attributes or return None if not found."""
87
+ if name == '_attrs':
88
+ return object.__getattribute__(self, '_attrs')
89
+ return self._attrs.get(name)
90
+
91
+ def setDomain(self, domain):
92
+ self.domain = domain
93
+
94
+ def getDomain(self):
95
+ return self.domain
96
+
97
+ def setType(self, type):
98
+ self.type = type
99
+
100
+ def getType(self):
101
+ return self.type
102
+
103
+ def setContent(self, content):
104
+ self.content = content
105
+
106
+ def getContent(self):
107
+ return self.content
108
+
109
+ def setValue(self, type=None, content=None):
110
+ self.type = type
111
+ self.content = content
112
+
113
+ def setProperty(self, key, value):
114
+ self.properties[key] = value
115
+
116
+ def getProperty(self, key):
117
+ return self.properties.get(key, None)
118
+
119
+ def setName(self, name):
120
+ self.name = name
121
+
122
+ def getName(self):
123
+ return self.name
124
+
125
+ def lock(self):
126
+ self.locked = True
127
+
128
+ def isLocked(self):
129
+ return self.locked
130
+
131
+ ###############################################################################
132
+ # The base class for all EasyCoder variable types
133
+ class ECObject():
134
+ def __init__(self):
135
+ self.locked: bool = False
136
+ self.elements: int = 0
137
+ self.index: Optional[int] = None
138
+ self.values: Optional[list] = None
139
+ self.name: Optional[str] = None
140
+
141
+ # Set the index for the variable
142
+ def setIndex(self, index: int) -> None:
143
+ self.index = index
144
+
145
+ # Get the index for the variable
146
+ def getIndex(self):
147
+ return self.index
148
+
149
+ # Lock the variable
150
+ def setLocked(self):
151
+ self.locked = True
152
+
153
+ # Check if the variable is locked
154
+ def isLocked(self):
155
+ return self.locked
156
+
157
+ # Set the value at the current index
158
+ def setValue(self, value):
159
+ if self.values is None:
160
+ self.index = 0
161
+ self.elements = 1
162
+ self.values = [None]
163
+ if isinstance(value, ECValue): value.setName(self.name)
164
+ self.values[self.index] = value # type: ignore
165
+
166
+ # Get the value at the current index
167
+ def getValue(self):
168
+ if self.values is None: return None
169
+ return self.values[self.index] # type: ignore
170
+
171
+ # Get all the values
172
+ def getValues(self):
173
+ return self.values
174
+
175
+ # Set the number of elements in the variable
176
+ def setElements(self, elements):
177
+ if self.elements == 0:
178
+ self.values = [None] * elements
179
+ self.elements = elements
180
+ self.index = 0
181
+ if elements == self.elements:
182
+ pass
183
+ elif elements > self.elements:
184
+ self.values.extend([None] * (elements - self.elements)) # pyright: ignore[reportOptionalMemberAccess]
185
+ else:
186
+ del self.values[elements:] # pyright: ignore[reportOptionalSubscript]
187
+ self.index = 0
188
+ self.elements = elements
189
+
190
+ # Get the number of elements in the variable
191
+ def getElements(self):
192
+ return self.elements
193
+
194
+ # Check if the object has a runtime value. Default is False
195
+ def hasRuntimeValue(self):
196
+ return False
197
+
198
+ # Check if the object is mutable. Default is False
199
+ def isMutable(self):
200
+ return False
201
+
202
+ # Check if the object is clearable
203
+ def isClearable(self):
204
+ return False
205
+
206
+ # Get the content of the value at the current index
207
+ def getContent(self):
208
+ if not self.hasRuntimeValue(): return None
209
+ v = self.getValue()
210
+ if v is None: return None
211
+ return v.getContent()
212
+
213
+ # Get the type of the value at the current index
214
+ def getType(self):
215
+ if not self.hasRuntimeValue(): return None
216
+ v = self.getValue()
217
+ if v is None: return None
218
+ return v.getType()
219
+
220
+ # Check if the object is empty. Default is True
221
+ def isEmpty(self):
222
+ return True
223
+
224
+ # Set the name of the object
225
+ def setName(self, name):
226
+ self.name = name
227
+
228
+ # Get the name of the object
229
+ def getName(self):
230
+ return self.name
231
+
232
+ # Check if the object can have properties
233
+ def hasProperties(self):
234
+ return False
235
+
236
+ ###############################################################################
237
+ # A generic variable object that can hold a mutable value
238
+ class ECVariable(ECObject):
239
+ def __init__(self):
240
+ super().__init__()
241
+ self.properties = {}
242
+
243
+ # Set the content of the value at the current index
244
+ def setContent(self, content):
245
+ if self.values is None:
246
+ self.index = 0
247
+ self.elements = 1
248
+ self.values = [None]
249
+ self.values[self.index] = content # type: ignore
250
+
251
+ # Set the value to a given ECValue
252
+ def setValue(self, value):
253
+ if self.values is None:
254
+ self.index = 0
255
+ self.elements = 1
256
+ self.values = [None]
257
+ if self.index >= self.elements: raise RuntimeError(None, 'Index out of range') # type: ignore
258
+ self.values[self.index] = value # type: ignore
259
+
260
+ # Report if the object is clearable
261
+ def isClearable(self):
262
+ return True
263
+
264
+ # This object has a runtime value
265
+ def hasRuntimeValue(self):
266
+ return True
267
+
268
+ # This object is mutable.
269
+ def isMutable(self):
270
+ return True
271
+
272
+ # Reset the object to empty state
273
+ def reset(self):
274
+ self.setValue(ECValue())
275
+
276
+ # Check if the object can have properties
277
+ def hasProperties(self):
278
+ return True
279
+
280
+ # Set a specific property on the object
281
+ def setProperty(self, name, value):
282
+ self.properties[name] = value
283
+
284
+ # Check if the object has a specific property
285
+ def hasProperty(self, name):
286
+ return name in self.properties
287
+
288
+ # Get a specific property
289
+ def getProperty(self, name):
290
+ return self.properties[name]
291
+
292
+ ###############################################################################
293
+ # A file variable
294
+ class ECFile(ECObject):
295
+ def __init__(self):
296
+ super().__init__()
297
+
298
+ ###############################################################################
299
+ # An SSH variable
300
+ class ECSSH(ECObject):
301
+ def __init__(self):
302
+ super().__init__()
303
+
304
+ # Set up the SSH connection
305
+ def setup(self, host=None, user=None, password=None):
306
+ ssh = paramiko.SSHClient()
307
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
308
+ try:
309
+ ssh.connect(host, username=user, password=password, timeout=10) # type: ignore
310
+ self.setValue(ssh)
311
+ self.sftp = ssh.open_sftp()
312
+ return True
313
+ except:
314
+ return False
315
+
316
+ # Get the SFTP client
317
+ def getSFTP(self):
318
+ return self.sftp
319
+
320
+ ###############################################################################
321
+ # A stack variable
322
+ class ECStack(ECObject):
323
+
324
+ def __init__(self):
325
+ super().__init__()
326
+ self.values: Optional[list[list[Any]]] = None # List of stacks, each holding any type
327
+
328
+ def push(self, item: Any) -> None:
329
+ if self.values is None:
330
+ self.index = 0
331
+ self.elements = 1
332
+ self.values = [[]]
333
+ assert self.index is not None # Type narrowing: index is always set when values exists
334
+ self.values[self.index].append(item)
335
+
336
+ def pop(self) -> Any:
337
+ if self.values is None or self.index is None or self.values[self.index] is None or len(self.values[self.index]) == 0:
338
+ return None
339
+ return self.values[self.index].pop()
easycoder/ec_compiler.py CHANGED
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  from .ec_classes import FatalError
2
3
  from .ec_value import Value
3
4
  from .ec_condition import Condition
@@ -84,7 +85,8 @@ class Compiler:
84
85
  return self.program.code[pc]
85
86
 
86
87
  # Add a command to the code list
87
- def addCommand(self, command):
88
+ def addCommand(self, command, debug=True):
89
+ command['debug'] = debug
88
90
  command['bp'] = False
89
91
  self.code.append(command)
90
92
 
@@ -132,16 +134,16 @@ class Compiler:
132
134
  print(warning)
133
135
 
134
136
  # Get the symbol record for the current token (assumes it is a symbol name)
135
- def getSymbolRecord(self):
136
- token = self.getToken()
137
- if not token in self.symbols:
138
- FatalError(self, f'Undefined symbol name "{token}"')
137
+ def getSymbolRecord(self, name=None):
138
+ if name == None: name = self.getToken()
139
+ if not name in self.symbols:
140
+ FatalError(self, f'Undefined symbol name "{name}"')
139
141
  return None
140
- symbol = self.symbols[token]
142
+ symbol = self.symbols[name]
141
143
  if symbol == None: return None
142
- symbolRecord = self.code[symbol]
143
- symbolRecord['used'] = True
144
- return symbolRecord
144
+ record = self.code[symbol]
145
+ record['used'] = True
146
+ return record
145
147
 
146
148
  # Add a value type
147
149
  def addValueType(self):
@@ -150,40 +152,56 @@ class Compiler:
150
152
  # Test if a given value is in the value types list
151
153
  def hasValue(self, type):
152
154
  return type in self.valueTypes
153
-
154
- # Compile a program label (a symbol ending with ':')
155
- def compileLabel(self, command):
156
- return self.compileSymbol(command, self.getToken())
155
+
156
+ # Instantiate an object of the given class name
157
+ def instantiate(self, classname):
158
+ # Search through all loaded modules for the class
159
+ items = sys.modules.items()
160
+ for module_name, module in items:
161
+ if module is None:
162
+ continue
163
+ try:
164
+ if hasattr(module, classname):
165
+ cls = getattr(module, classname)
166
+ # Verify it's actually a class
167
+ if isinstance(cls, type):
168
+ # Attempt to instantiate
169
+ try:
170
+ return cls()
171
+ except TypeError as ex:
172
+ raise FatalError(self, f"Object instantiation error: {ex}")
173
+ except Exception:
174
+ continue
175
+ return None
157
176
 
158
177
  # Compile a variable
159
- def compileVariable(self, command, extra=None):
160
- return self.compileSymbol(command, self.nextToken(), extra)
178
+ def compileVariable(self, command, classname):
179
+ return self.compileSymbol(command, self.nextToken(), classname)
161
180
 
162
181
  # Compile a symbol
163
- def compileSymbol(self, command, name, extra=None):
182
+ def compileSymbol(self, command, name, classname):
164
183
  try:
165
- v = self.symbols[name]
166
- except:
167
- v = None
168
- if v:
169
- FatalError(self, f'Duplicate symbol name "{name}"')
170
- return False
171
- self.symbols[name] = self.getCodeSize()
172
- command['program'] = self.program
173
- command['type'] = 'symbol'
184
+ self.symbols[name]
185
+ raise FatalError(self, f'Duplicate symbol name "{name}"')
186
+ except: pass
174
187
  command['name'] = name
175
- command['elements'] = 1
176
- command['index'] = 0
177
- command['value'] = [None]
188
+ command['classname'] = classname
189
+ command['program'] = self.program
178
190
  command['used'] = False
179
- command['debug'] = False
180
- command['import'] = None
181
- command['locked'] = False
182
- command['extra'] = extra
183
- if 'keyword' in command: command['hasValue'] = self.hasValue(command['keyword'])
184
- self.addCommand(command)
191
+ self.symbols[name] = self.getCodeSize()
192
+ if classname != ':':
193
+ object = self.instantiate(classname)
194
+ command['object'] = object
195
+ if object != None:
196
+ command['type'] = 'symbol'
197
+ object.setName(name) # type: ignore
198
+ self.addCommand(command, False)
185
199
  return True
186
200
 
201
+ # Compile a program label (a symbol ending with ':')
202
+ def compileLabel(self, command):
203
+ return self.compileSymbol(command, self.getToken(), ':')
204
+
187
205
  # Compile the current token
188
206
  def compileToken(self):
189
207
  self.warnings = []
@@ -192,8 +210,8 @@ class Compiler:
192
210
  if not token:
193
211
  return False
194
212
  if len(self.code) == 0:
195
- if self.program.parent == None and self.program.usingGraphics:
196
- cmd = {'domain': 'graphics', 'keyword': 'init', 'debug': False}
213
+ if self.program.parent == None and self.program.graphics:
214
+ cmd = {'domain': 'graphics', 'keyword': 'init'}
197
215
  self.code.append(cmd)
198
216
  mark = self.getIndex()
199
217
  for domain in self.program.getDomains():
@@ -203,8 +221,6 @@ class Compiler:
203
221
  command['domain'] = domain.getName()
204
222
  command['lino'] = self.tokens[self.index].lino
205
223
  command['keyword'] = token
206
- command['type'] = None
207
- command['debug'] = True
208
224
  result = handler(command)
209
225
  if result:
210
226
  return result
@@ -234,7 +250,7 @@ class Compiler:
234
250
  while True:
235
251
  token = self.tokens[self.index]
236
252
  # keyword = token.token
237
- if self.debugCompile: print(self.script.lines[token.lino])
253
+ if self.debugCompile: print(f'{token.lino + 1}: {self.script.lines[token.lino]}')
238
254
  # if keyword != 'else':
239
255
  if self.compileOne() == True:
240
256
  if self.index == len(self.tokens) - 1:
easycoder/ec_condition.py CHANGED
@@ -16,7 +16,7 @@ class Condition:
16
16
  for domain in self.compiler.program.getDomains():
17
17
  condition = domain.compileCondition()
18
18
  if condition != None:
19
- condition.domain= domain.getName()
19
+ condition.domain = domain.getName()
20
20
  return condition
21
21
  self.rewindTo(mark)
22
22
  return None