v8.23++
Browse files- index.html +1 -622
index.html
CHANGED
|
@@ -33,628 +33,7 @@ p { margin-bottom:30px }
|
|
| 33 |
</video>
|
| 34 |
|
| 35 |
|
| 36 |
-
<p>Memelang is an AI-optimized query language that significantly reduces token count and model size for LLM RAG.
|
| 37 |
-
|
| 38 |
-
<div style="position: relative;">
|
| 39 |
-
<a id="copy" href="#" onclick="navigator.clipboard.writeText(document.getElementById('spec').innerText);document.getElementById('copy').style.background='rgb(0,0,255)';setTimeout(()=>document.getElementById('copy').style.background='',500);return false">Copy all code</a>
|
| 40 |
-
<pre><code class="language-python" id="spec">'''
|
| 41 |
-
info@memelang.net | (c)2025 HOLTWORK LLC | Patents Pending
|
| 42 |
-
This script is optimized for prompting LLMs
|
| 43 |
-
|
| 44 |
-
1. MEMELANG USES AXES, LIMIT_AXIS HIGH -> LOW
|
| 45 |
-
| AXIS | SQL ANALOG | RDF ANALOG |
|
| 46 |
-
| ---: | ----------- | ----------- |
|
| 47 |
-
| 3 | Table | Graph |
|
| 48 |
-
| 2 | Primary Key | Subject |
|
| 49 |
-
| 1 | Column | Predicate |
|
| 50 |
-
| 0 | Value | Object |
|
| 51 |
-
|
| 52 |
-
2. EXAMPLE QUERY
|
| 53 |
-
MEMELANG: movies * actor "Mark Hamill",Mark ; movie * ; rating >4 ;;
|
| 54 |
-
SQL SIMPLE: SELECT actor, movie, rating FROM movies WHERE actor IN ('Mark Hamill', 'Mark') AND rating > 4
|
| 55 |
-
SQL LITERAL: SELECT CONCAT_WS(' ', 'movies', t0.id, 'actor', t0.actor, ';', 'movie', t0.movie, ';', 'rating', t0.rating, ';;') AS meme FROM movies AS t0 WHERE t0.actor IN ('Mark Hamill', 'Mark') AND t0.rating > 4
|
| 56 |
-
|
| 57 |
-
3. VARIABLE EXAMPLE ACTOR NAME = MOVIE TITLE
|
| 58 |
-
MEMELANG: movies * actor $x=* ; movie $x ;;
|
| 59 |
-
SQL SIMPLE: SELECT id, actor, movie FROM movies WHERE actor=movie
|
| 60 |
-
SQL LITERAL: SELECT CONCAT_WS(' ', 'movies', t0.id, 'actor', t0.actor, ';', 'movie', t0.movie, ';;') AS meme FROM movies AS t0 WHERE t0.actor=t0.movie
|
| 61 |
-
|
| 62 |
-
4. EXAMPLE JOIN
|
| 63 |
-
MEMELANG: movies * actor "Mark Hamill" ; movie * ; !@ @ @ ; actor * ;;
|
| 64 |
-
MEMELANG ALT: movies $id=* actor "Mark Hamill" ; movie * ; !$id @ @ ; actor !"Mark Hamill" ;;
|
| 65 |
-
SQL SIMPLE: SELECT t0.actor, t0.movie, t1.movie, t1.actor FROM movies AS t0, movies AS t1 WHERE t0.actor = 'Mark Hamill' AND t1.id != t0.id AND t1.movie = t0.movie
|
| 66 |
-
SQL LITERAL: SELECT CONCAT_WS(' ', 'movies', t0.id, 'actor', t0.actor, ';', 'movie', t0.movie, ';', t1.id, 'movie', t1.movie, ';', 'actor', t1.actor, ';;' ) AS meme FROM movies AS t0, movies AS t1 WHERE t0.actor = 'Mark Hamill' AND t1.id != t0.id AND t1.movie = t0.movie
|
| 67 |
-
|
| 68 |
-
5. EXAMPLE TABLE JOIN WITH VARIABLE, ACTOR NAME = MOVIE TITLE
|
| 69 |
-
MEMELANG: actors * age >21; name $n=* ; movies * title $n ;;
|
| 70 |
-
MEMELANG ALT: actors * age >21; name * ; movies * title @ ;;
|
| 71 |
-
SQL SIMPLE: SELECT t0.name, t0.age, t1.title FROM actors AS t0, movies AS t1 WHERE t0.age > 21 AND t1.title = t0.name;
|
| 72 |
-
SQL LITERAL: SELECT CONCAT_WS(' ', 'actors', t0.id, 'age', t0.age, ';', 'name', t0.name, ';', 'movies', t1.id, 'title', t1.title, ';;' ) AS meme FROM actors AS t0, movies AS t1 WHERE t0.age > 21 AND t1.title = t0.name
|
| 73 |
-
'''
|
| 74 |
-
|
| 75 |
-
MEMELANG_VER = 8.23
|
| 76 |
-
|
| 77 |
-
import random, re, json, operator
|
| 78 |
-
from typing import List, Iterator, Iterable, Dict, Tuple, Any, Union
|
| 79 |
-
|
| 80 |
-
Axis, Memelang = int, str
|
| 81 |
-
|
| 82 |
-
SIGIL, WILD, MSAME, VSAME, EMPTY, EOF = '$', '*', '^', '@', '_', None
|
| 83 |
-
SEP_LIMIT, SEP_DATA, SEP_VCTR, SEP_MTRX = ' ', ',', ';', ';;'
|
| 84 |
-
SEP_VCTR_PRETTY, SEP_MTRX_PRETTY = ' ; ', ' ;;\n'
|
| 85 |
-
ELIDE_VSAME = True
|
| 86 |
-
|
| 87 |
-
TOKEN_KIND_PATTERNS = (
|
| 88 |
-
('COMMENT', r'//[^\n]*'),
|
| 89 |
-
('QUOTE', r'"(?:[^"\\]|\\.)*"'), # ALWAYS JSON QUOTE ESCAPE EXOTIC CHARS "John \"Jack\" Kennedy"
|
| 90 |
-
('META', r"'[^']*'"),
|
| 91 |
-
('IGNORE', r'-*\|'),
|
| 92 |
-
('SEP_MTRX', re.escape(SEP_MTRX)),
|
| 93 |
-
('SEP_VCTR', re.escape(SEP_VCTR)),
|
| 94 |
-
('SEP_LIMIT', r'\s+'),
|
| 95 |
-
('SEP_DATA', re.escape(SEP_DATA)),
|
| 96 |
-
('GE', r'>='),
|
| 97 |
-
('LE', r'<='),
|
| 98 |
-
('NOT', r'!=?'),
|
| 99 |
-
('EQL', r'='),
|
| 100 |
-
('GT', r'>'),
|
| 101 |
-
('LT', r'<'),
|
| 102 |
-
('WILD', re.escape(WILD)), # WILDCARD, MATCHES WHOLE VALUE, NEVER QUOTE
|
| 103 |
-
('MSAME', re.escape(MSAME)), # REFERENCES (MTRX_AXIS-1, VCTR_AXIS=-1, LIMIT_AXIS)
|
| 104 |
-
('VSAME', re.escape(VSAME)), # REFERENCES (MTRX_AXIS, VCTR_AXIS-1, LIMIT_AXIS)
|
| 105 |
-
('EMPTY', re.escape(EMPTY)), # EMPTY SET, ANTI-WILD
|
| 106 |
-
('VAR', r'\$[A-Za-z0-9]+'),
|
| 107 |
-
('ALNUM', r'[A-Za-z][A-Za-z0-9]*'), # ALPHANUMERICS ARE UNQUOTED
|
| 108 |
-
('FLOAT', r'-?\d*\.\d+'),
|
| 109 |
-
('INT', r'-?\d+'),
|
| 110 |
-
('MISMATCH', r'.'),
|
| 111 |
-
)
|
| 112 |
-
|
| 113 |
-
MASTER_PATTERN = re.compile('|'.join(f'(?P<{kind}>{pat})' for kind, pat in TOKEN_KIND_PATTERNS))
|
| 114 |
-
|
| 115 |
-
OPR_DICT = {'EQL': operator.eq, 'NOT': operator.ne, 'GT': operator.gt, 'GE': operator.ge, 'LT': operator.lt, 'LE': operator.le}
|
| 116 |
-
OPR_DATA_KINDS = {'EQL','NOT'}
|
| 117 |
-
SEP_KINDS = {'SEP_MTRX','SEP_VCTR','SEP_LIMIT','SEP_DATA',EOF}
|
| 118 |
-
DATA_KINDS = {'ALNUM', 'QUOTE', 'INT', 'FLOAT', 'VAR', 'VSAME', 'MSAME', 'EMPTY','WILD'}
|
| 119 |
-
UNITARY_KINDS = {'ALNUM', 'QUOTE', 'INT', 'FLOAT', 'VSAME', 'MSAME', 'EQL', 'DATUM', 'NOVAR'}
|
| 120 |
-
|
| 121 |
-
EBNF = '''
|
| 122 |
-
LIMIT ::= ([[VAR] OPR] DATUM {SEP_DATA DATUM}) | OPR
|
| 123 |
-
VCTR ::= LIMIT {SEP_LIMIT LIMIT}
|
| 124 |
-
MTRX ::= VCTR {SEP_VCTR VCTR}
|
| 125 |
-
MEME ::= MTRX {SEP_MTRX MTRX}
|
| 126 |
-
'''
|
| 127 |
-
|
| 128 |
-
class Token():
|
| 129 |
-
kind: str
|
| 130 |
-
lexeme: str
|
| 131 |
-
datum: Union[str, float, int]
|
| 132 |
-
def __init__(self, kind: str, lexeme: str):
|
| 133 |
-
self.kind = kind
|
| 134 |
-
self.lexeme = lexeme
|
| 135 |
-
if kind == 'QUOTE': self.datum = json.loads(lexeme)
|
| 136 |
-
elif kind == 'FLOAT': self.datum = float(lexeme)
|
| 137 |
-
elif kind == 'INT': self.datum = int(lexeme)
|
| 138 |
-
else: self.datum = lexeme
|
| 139 |
-
|
| 140 |
-
@property
|
| 141 |
-
def unitary(self) -> bool: return self.kind in UNITARY_KINDS
|
| 142 |
-
def dump(self) -> str|float|int: return self.datum
|
| 143 |
-
def __str__(self) -> Memelang: return self.lexeme
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
TOK_EQL = Token('EQL', '') # ELIDED '='
|
| 147 |
-
TOK_DATUM = Token('DATUM', '') # ELIDED
|
| 148 |
-
TOK_NOVAR = Token('NOVAR', '') # ELIDED
|
| 149 |
-
TOK_NOT = Token('NOT', '!')
|
| 150 |
-
TOK_GT = Token('GT', '>')
|
| 151 |
-
TOK_SEP_DATA = Token('SEP_DATA', SEP_DATA)
|
| 152 |
-
TOK_SEP_LIMIT = Token('SEP_LIMIT', SEP_LIMIT)
|
| 153 |
-
TOK_SEP_VCTR = Token('SEP_VCTR', SEP_VCTR)
|
| 154 |
-
TOK_SEP_MTRX = Token('SEP_MTRX', SEP_MTRX)
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
class Stream:
|
| 158 |
-
def __init__(self, token: Iterable[Token]):
|
| 159 |
-
self.token: Iterator[Token] = iter(token)
|
| 160 |
-
self.buffer: List[Token] = []
|
| 161 |
-
|
| 162 |
-
def peek(self, fwd: int = 1) -> Union[str, None]:
|
| 163 |
-
while(len(self.buffer)<fwd):
|
| 164 |
-
val = next(self.token, EOF)
|
| 165 |
-
if val is EOF: return EOF
|
| 166 |
-
self.buffer.append(val)
|
| 167 |
-
return self.buffer[fwd-1].kind
|
| 168 |
-
|
| 169 |
-
def next(self) -> Token:
|
| 170 |
-
if not self.buffer:
|
| 171 |
-
val = next(self.token, EOF)
|
| 172 |
-
if val is EOF: raise SyntaxError('E_EOF')
|
| 173 |
-
self.buffer.append(val)
|
| 174 |
-
return self.buffer.pop(0)
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
class Node(list):
|
| 178 |
-
opr: Token = TOK_EQL
|
| 179 |
-
def __init__(self, *items: Union['Node', Token], opr:Token|None = None):
|
| 180 |
-
super().__init__(items)
|
| 181 |
-
if opr is not None: self.opr = opr
|
| 182 |
-
|
| 183 |
-
def prepend(self, item):
|
| 184 |
-
self.insert(0, item)
|
| 185 |
-
|
| 186 |
-
def pad(self, padding:Union['Node', Token]) -> None:
|
| 187 |
-
max_len = len(self[0])
|
| 188 |
-
for idx, item in enumerate(self):
|
| 189 |
-
diff = max_len - len(item)
|
| 190 |
-
if diff>0: self[idx] += [padding] * diff
|
| 191 |
-
elif diff<0: raise SyntaxError('E_PAD') # FIRST MUST BE LONGEST
|
| 192 |
-
|
| 193 |
-
@property
|
| 194 |
-
def unitary(self) -> bool: return self.opr.unitary and all(item.unitary for item in self)
|
| 195 |
-
def dump(self) -> List: return [self.opr.dump(), [item.dump() for item in self]]
|
| 196 |
-
def check(self) -> 'Node':
|
| 197 |
-
if len(self)==0: raise SyntaxError('E_NO_LIST')
|
| 198 |
-
return self
|
| 199 |
-
def __str__(self) -> Memelang: return self.opr.lexeme.join(map(str, self))
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
class Data(Node):
|
| 203 |
-
opr: Token = TOK_DATUM
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
DATA_MSAME = Data(Token('MSAME', MSAME))
|
| 207 |
-
DATA_VSAME = Data(Token('VSAME', VSAME))
|
| 208 |
-
DATA_EMPTY = Data(Token('EMPTY', EMPTY))
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
class Limit(Node):
|
| 212 |
-
opr: Token = TOK_EQL # ELIDED '='
|
| 213 |
-
|
| 214 |
-
@property
|
| 215 |
-
def k1(self) -> str: return self[1][0].kind
|
| 216 |
-
|
| 217 |
-
def check(self) -> 'Limit':
|
| 218 |
-
if len(self)!=2: raise SyntaxError('E_NO_LIST')
|
| 219 |
-
if any(t.kind=='WILD' for t in self[1]):
|
| 220 |
-
if self.opr.kind == 'EQL': self.opr=TOK_NOT
|
| 221 |
-
elif self.opr.kind == 'NOT': self.opr=TOK_EQL
|
| 222 |
-
else: self.opr = TOK_GT # WILD MATCHES ANY NUMERIC
|
| 223 |
-
self[1]=DATA_EMPTY
|
| 224 |
-
return self
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
LIMIT_EQL_VSAME = Limit(TOK_NOVAR, DATA_VSAME, opr=TOK_EQL)
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
class Vector(Node):
|
| 231 |
-
opr: Token = TOK_SEP_LIMIT
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
class Matrix(Node):
|
| 235 |
-
opr: Token = TOK_SEP_VCTR
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
def lex(src: Memelang) -> Iterator[Token]:
|
| 239 |
-
for m in MASTER_PATTERN.finditer(src):
|
| 240 |
-
kind = m.lastgroup
|
| 241 |
-
if kind in {'COMMENT','META','IGNORE'}: continue
|
| 242 |
-
if kind == 'MISMATCH': raise SyntaxError('E_TOK')
|
| 243 |
-
yield Token(kind, m.group())
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
def parse(src: Memelang) -> Iterator[Matrix]:
|
| 247 |
-
tokens = Stream(lex(src))
|
| 248 |
-
bound_vars = []
|
| 249 |
-
mtrx = Matrix()
|
| 250 |
-
vctr = Vector()
|
| 251 |
-
limit = Limit()
|
| 252 |
-
while tokens.peek():
|
| 253 |
-
|
| 254 |
-
# LIMIT: Single axis constraint
|
| 255 |
-
|
| 256 |
-
# [VAR]
|
| 257 |
-
data = None
|
| 258 |
-
var = TOK_NOVAR
|
| 259 |
-
if tokens.peek() == 'VAR':
|
| 260 |
-
if tokens.peek(2) in OPR_DICT:
|
| 261 |
-
var = tokens.next()
|
| 262 |
-
bound_vars.append(var.lexeme)
|
| 263 |
-
elif tokens.peek(2) not in SEP_KINDS: raise SyntaxError('E_VAR_NXT')
|
| 264 |
-
|
| 265 |
-
# [OPR]
|
| 266 |
-
if tokens.peek() in OPR_DICT:
|
| 267 |
-
limit.opr=tokens.next()
|
| 268 |
-
if tokens.peek() not in DATA_KINDS:
|
| 269 |
-
if ELIDE_VSAME: data = Data(Token('VSAME', ''))
|
| 270 |
-
else: raise SyntaxError('E_NEVER_SPACE_AFTER_OPR')
|
| 271 |
-
|
| 272 |
-
# DATUM {SEP_DATA DATUM}
|
| 273 |
-
if tokens.peek() in DATA_KINDS:
|
| 274 |
-
data=Data()
|
| 275 |
-
data.append(tokens.next())
|
| 276 |
-
while tokens.peek()=='SEP_DATA':
|
| 277 |
-
data.opr = tokens.next()
|
| 278 |
-
if tokens.peek()=='SEP_LIMIT': raise SyntaxError('E_NEVER_SPACE_AFTER_COMMA')
|
| 279 |
-
if tokens.peek() not in DATA_KINDS: raise SyntaxError('E_DATA_KIND')
|
| 280 |
-
data.append(tokens.next())
|
| 281 |
-
|
| 282 |
-
if data:
|
| 283 |
-
# LOGIC CHECKS
|
| 284 |
-
if any(t.kind == 'VAR' and t.lexeme not in bound_vars for t in data): raise SyntaxError('E_VAR_UNBOUND')
|
| 285 |
-
if len(mtrx)==0 and any(t.kind == 'VSAME' for t in data): raise SyntaxError('E_VSAME_OOB')
|
| 286 |
-
if len(data)>1 and limit.opr.kind not in OPR_DATA_KINDS: raise SyntaxError('E_DATA_OPR')
|
| 287 |
-
|
| 288 |
-
# FINALIZE LIMIT
|
| 289 |
-
limit.append(var)
|
| 290 |
-
limit.append(data)
|
| 291 |
-
vctr.prepend(limit.check()) # LIMIT_AXIS: HIGH -> LOW
|
| 292 |
-
limit=Limit()
|
| 293 |
-
continue
|
| 294 |
-
|
| 295 |
-
# VCTR: Conjunctive vector of axis constraints
|
| 296 |
-
if tokens.peek() == 'SEP_VCTR':
|
| 297 |
-
if vctr: mtrx.append(vctr.check())
|
| 298 |
-
vctr = Vector()
|
| 299 |
-
tokens.next()
|
| 300 |
-
continue
|
| 301 |
-
|
| 302 |
-
# MTRX: Conjunctive matrix of axis constraints
|
| 303 |
-
if tokens.peek() == 'SEP_MTRX':
|
| 304 |
-
if vctr: mtrx.append(vctr.check())
|
| 305 |
-
if mtrx: yield mtrx.check()
|
| 306 |
-
vctr = Vector()
|
| 307 |
-
mtrx = Matrix()
|
| 308 |
-
tokens.next()
|
| 309 |
-
continue
|
| 310 |
-
|
| 311 |
-
if tokens.peek() == 'SEP_LIMIT':
|
| 312 |
-
tokens.next()
|
| 313 |
-
continue
|
| 314 |
-
|
| 315 |
-
raise SyntaxError('E_TOK')
|
| 316 |
-
|
| 317 |
-
if vctr: mtrx.append(vctr.check())
|
| 318 |
-
if mtrx: yield mtrx.check()
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
class Meme(Node):
|
| 322 |
-
opr: Token = TOK_SEP_MTRX
|
| 323 |
-
results: List[List[List[Data]]]
|
| 324 |
-
bindings: Dict[str, Tuple[Axis, Axis, Axis]]
|
| 325 |
-
src: Memelang
|
| 326 |
-
|
| 327 |
-
def __init__(self, src: Memelang):
|
| 328 |
-
self.src = src
|
| 329 |
-
self.bindings = {}
|
| 330 |
-
super().__init__(*parse(src))
|
| 331 |
-
self.check()
|
| 332 |
-
|
| 333 |
-
def check(self) -> 'Meme':
|
| 334 |
-
for mtrx_axis, mtrx in enumerate(self):
|
| 335 |
-
if not isinstance(mtrx, Matrix): raise TypeError('E_TYPE_VCTR')
|
| 336 |
-
for vctr_axis, vctr in enumerate(mtrx):
|
| 337 |
-
if not isinstance(vctr, Vector): raise TypeError('E_TYPE_VCTR')
|
| 338 |
-
for limit_axis, limit in enumerate(vctr):
|
| 339 |
-
if not isinstance(limit, Limit): raise TypeError('E_TYPE_LIMIT')
|
| 340 |
-
if limit[0].kind=='VAR': self.bindings[limit[0].lexeme] = (mtrx_axis, vctr_axis, limit_axis)
|
| 341 |
-
self[mtrx_axis].pad(LIMIT_EQL_VSAME)
|
| 342 |
-
|
| 343 |
-
self.results = [[[[] for limit in vctr] for vctr in mtrx] for mtrx in self]
|
| 344 |
-
|
| 345 |
-
return self
|
| 346 |
-
|
| 347 |
-
def expand(self, data: Data, from_limit_axis: Axis, from_vctr_axis: Axis, from_mtrx_axis: Axis) -> Data:
|
| 348 |
-
expansion=Data()
|
| 349 |
-
coords: Tuple[Axis, Axis, Axis] = ()
|
| 350 |
-
for tok in data:
|
| 351 |
-
coords = ()
|
| 352 |
-
if tok.kind == 'VSAME': coords = (from_mtrx_axis, from_vctr_axis-1, from_limit_axis)
|
| 353 |
-
elif tok.kind == 'MSAME': coords = (from_mtrx_axis-1, -1, from_limit_axis)
|
| 354 |
-
elif tok.kind == 'VAR': coords = self.bindings[tok.lexeme]
|
| 355 |
-
expansion.extend(self.results[coords[0]][coords[1]][coords[2]] if coords else [tok])
|
| 356 |
-
if len(expansion)>1: expansion.opr = TOK_SEP_DATA
|
| 357 |
-
return expansion.check()
|
| 358 |
-
|
| 359 |
-
def store(self):
|
| 360 |
-
for mtrx_axis, mtrx in enumerate(self):
|
| 361 |
-
for vctr_axis, vctr in enumerate(mtrx):
|
| 362 |
-
for limit_axis, limit in enumerate(vctr):
|
| 363 |
-
if not limit.unitary: raise SyntaxError('E_LIMIT_UNIT')
|
| 364 |
-
self.results[mtrx_axis][vctr_axis][limit_axis]=self.expand(limit[1], limit_axis, vctr_axis, mtrx_axis)
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
def intersect(query: Limit, store: Data) -> Data:
|
| 368 |
-
if not store: store=DATA_EMPTY
|
| 369 |
-
|
| 370 |
-
opr_kind, intersection, query_data = query.opr.kind, Data(), [t.datum for t in query[1]]
|
| 371 |
-
|
| 372 |
-
if opr_kind in {'EQL', 'NOT'}:
|
| 373 |
-
if opr_kind == 'EQL': intersection.extend([t for t in store if t.datum in query_data])
|
| 374 |
-
else: intersection.extend([t for t in store if t.datum not in query_data])
|
| 375 |
-
return intersection
|
| 376 |
-
|
| 377 |
-
# RETURN ANY NUMERIC FOR GT EMPTY
|
| 378 |
-
elif EMPTY in query_data:
|
| 379 |
-
intersection.extend([t for t in store if t.kind in {'INT','FLOAT'}])
|
| 380 |
-
return intersection
|
| 381 |
-
|
| 382 |
-
elif len(query_data)!=1 or not isinstance(query_data[0], (int,float)): raise TypeError('E_INTER_NUM')
|
| 383 |
-
|
| 384 |
-
intersection.extend([t for t in store if t.kind in {'INT','FLOAT'} and OPR_DICT[opr_kind](t.datum, query_data[0])])
|
| 385 |
-
return intersection
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
# GENERATE RANDOM MEMELANG DATA
|
| 389 |
-
class Fuzz():
|
| 390 |
-
@staticmethod
|
| 391 |
-
def datum(kind:str, i:int=1) -> Memelang:
|
| 392 |
-
if kind=='ALNUM': return ''.join(random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(i))
|
| 393 |
-
if kind=='QUOTE': return json.dumps(''.join(random.choice(' -_+,./<>[]{}\'"!@#$%^&*()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(i)))
|
| 394 |
-
if kind=='INT': return str(random.randint(-i, i))
|
| 395 |
-
if kind=='FLOAT': return str(random.uniform(-i, i))
|
| 396 |
-
if kind=='VAR': return SIGIL + Fuzz.datum('ALNUM', i)
|
| 397 |
-
|
| 398 |
-
@staticmethod
|
| 399 |
-
def limit(bindings: List[str]|None = None) -> Memelang:
|
| 400 |
-
if not bindings: bindings = []
|
| 401 |
-
var = ''
|
| 402 |
-
do_assign_variable = random.randint(0, 1)
|
| 403 |
-
if do_assign_variable: var += Fuzz.datum('VAR',3)
|
| 404 |
-
|
| 405 |
-
opr = random.choice(['=','!=','>','<','<=','>='])
|
| 406 |
-
|
| 407 |
-
data: str = ''
|
| 408 |
-
if opr in {'=','!=','!'}:
|
| 409 |
-
data_list_len = random.randint(1, 5)
|
| 410 |
-
data_list: List[Any] = []
|
| 411 |
-
for _ in range(data_list_len):
|
| 412 |
-
datum_type = random.randint(1, 10)
|
| 413 |
-
if datum_type == 1: data_list.append(Fuzz.datum('QUOTE',10))
|
| 414 |
-
elif datum_type == 2: data_list.append(Fuzz.datum('INT', 100))
|
| 415 |
-
elif datum_type == 3: data_list.append(Fuzz.datum('FLOAT', 100))
|
| 416 |
-
elif datum_type == 4 and bindings: data_list.append(random.choice(bindings))
|
| 417 |
-
elif datum_type == 5 and VSAME in bindings: data_list.append(VSAME)
|
| 418 |
-
else: data_list.append(Fuzz.datum('ALNUM', 5))
|
| 419 |
-
data += SEP_DATA.join(data_list)
|
| 420 |
-
else:
|
| 421 |
-
data = Fuzz.datum('FLOAT', 100)
|
| 422 |
-
|
| 423 |
-
if var:
|
| 424 |
-
assert opr
|
| 425 |
-
bindings.append(var)
|
| 426 |
-
|
| 427 |
-
return var + opr + data
|
| 428 |
-
|
| 429 |
-
@staticmethod
|
| 430 |
-
def vector(limit_len:int = 4) -> Memelang:
|
| 431 |
-
bindings, vector = [], []
|
| 432 |
-
for i in range(limit_len):
|
| 433 |
-
if i>0: bindings.append(VSAME)
|
| 434 |
-
vector.append(Fuzz.limit(bindings))
|
| 435 |
-
return SEP_LIMIT.join(vector) + SEP_VCTR_PRETTY
|
| 436 |
-
|
| 437 |
-
@staticmethod
|
| 438 |
-
def mtrx_table(col_len:int = 5) -> Memelang:
|
| 439 |
-
return Fuzz.datum('ALNUM',5) + SEP_LIMIT + WILD + SEP_LIMIT + SEP_VCTR_PRETTY.join(Fuzz.datum('ALNUM',5) + Fuzz.limit() for _ in range(col_len)) + SEP_MTRX_PRETTY
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
### SQL ###
|
| 444 |
-
|
| 445 |
-
SQL = str
|
| 446 |
-
Param = int|float|str
|
| 447 |
-
|
| 448 |
-
class SQLUtil():
|
| 449 |
-
@staticmethod
|
| 450 |
-
def escape(token: Token, bindings: dict) -> SQL:
|
| 451 |
-
if token.kind == 'DBCOL': return token.datum
|
| 452 |
-
elif token.kind == 'VSAME': return SQLUtil.escape(bindings[VSAME], bindings)
|
| 453 |
-
elif token.kind == 'VAR':
|
| 454 |
-
if token.lexeme not in bindings: raise SyntaxError('E_VAR_BIND')
|
| 455 |
-
return SQLUtil.escape(bindings[token.lexeme], bindings)
|
| 456 |
-
return '%s'
|
| 457 |
-
|
| 458 |
-
@staticmethod
|
| 459 |
-
def escape2(token: Token, bindings: dict) -> None|Param:
|
| 460 |
-
if token.kind == 'DBCOL': return None
|
| 461 |
-
elif token.kind == 'VSAME': return SQLUtil.escape2(bindings[VSAME], bindings)
|
| 462 |
-
elif token.kind == 'VAR':
|
| 463 |
-
if token.lexeme not in bindings: raise SyntaxError('E_VAR_BIND')
|
| 464 |
-
return SQLUtil.escape2(bindings[token.lexeme], bindings)
|
| 465 |
-
return token.datum
|
| 466 |
-
|
| 467 |
-
@staticmethod
|
| 468 |
-
def compare(alias_col: str, limit: Limit, bindings: dict) -> Tuple[SQL, List[None|Param]]:
|
| 469 |
-
if len(limit[1]) > 1:
|
| 470 |
-
if limit.opr.kind == 'EQL': sym = 'IN'
|
| 471 |
-
elif limit.opr.kind == 'NOT': sym = 'NOT IN'
|
| 472 |
-
else: raise SyntaxError()
|
| 473 |
-
return f'{alias_col} {sym} ('+ ', '.join(SQLUtil.escape(v, bindings) for v in limit[1]) + ')', [SQLUtil.escape2(v, bindings) for v in limit[1]]
|
| 474 |
-
sym = {'EQL':'=','NOT':'!=','GT':'>','GE':'>=','LT':'<','LE':'<='}[limit.opr.kind]
|
| 475 |
-
return f'{alias_col} {sym} {SQLUtil.escape(limit[1][0], bindings)}', [SQLUtil.escape2(limit[1][0], bindings)]
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
class MemeSQL(Meme):
|
| 479 |
-
@classmethod
|
| 480 |
-
def from_output(self, table_name:str, sql_output: str) -> 'MemeSQL':
|
| 481 |
-
lines=[l for l in sql_output.splitlines() if l.startswith('|')]
|
| 482 |
-
if not lines: raise SyntaxError('E_SQL')
|
| 483 |
-
header=[c.strip() for c in lines[0].strip('|').split('|')]
|
| 484 |
-
mtrxs=[]
|
| 485 |
-
for line in lines[1:]:
|
| 486 |
-
cells=[c.strip() for c in line.strip('|').split('|')]
|
| 487 |
-
if len(cells)!=len(header):continue
|
| 488 |
-
rowid=cells[0]
|
| 489 |
-
parts=[(header[i] + SEP_LIMIT + cells[i]) for i in range(1,len(header))]
|
| 490 |
-
mtrxs.append(table_name + SEP_LIMIT + rowid + SEP_LIMIT + SEP_VCTR_PRETTY.join(parts))
|
| 491 |
-
return self(SEP_MTRX_PRETTY.join(mtrxs) + SEP_MTRX_PRETTY)
|
| 492 |
-
|
| 493 |
-
@classmethod
|
| 494 |
-
def from_insert(self, sql_insert: SQL) -> 'MemeSQL':
|
| 495 |
-
m = re.search(r'INSERT\s+INTO\s+(\w+)\s*\((.*?)\)\s*VALUES\s*(.*);', sql_insert, re.I | re.S)
|
| 496 |
-
if not m: raise SyntaxError('E_SQL')
|
| 497 |
-
table_name = m.group(1)
|
| 498 |
-
header = [h.strip() for h in m.group(2).split(',')]
|
| 499 |
-
rows_sql = re.findall(r'\(([^()]*)\)', m.group(3))
|
| 500 |
-
mtrxs = []
|
| 501 |
-
for idx, row in enumerate(rows_sql):
|
| 502 |
-
cells = [c.strip(" '\"") for c in re.findall(r"'[^']*'|[^,]+", row)]
|
| 503 |
-
if len(cells) != len(header): continue
|
| 504 |
-
rowid = cells[0]
|
| 505 |
-
col_tokens = header[1:] if idx == 0 else [MSAME] * (len(header) - 1)
|
| 506 |
-
parts = [f'{col_tokens[i]} {cells[i + 1]}' for i in range(len(col_tokens))]
|
| 507 |
-
mtrxs.append(table_name + SEP_LIMIT + rowid + SEP_LIMIT + SEP_VCTR_PRETTY.join(parts))
|
| 508 |
-
return self(SEP_MTRX_PRETTY.join(mtrxs) + SEP_MTRX_PRETTY)
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
class MemeSQLTable(MemeSQL):
|
| 512 |
-
primary: str = 'id'
|
| 513 |
-
tbl: Axis = 3
|
| 514 |
-
row: Axis = 2
|
| 515 |
-
col: Axis = 1
|
| 516 |
-
val: Axis = 0
|
| 517 |
-
def to_select(self) -> Tuple[SQL, List[Param]]:
|
| 518 |
-
alias_idx: int = 0
|
| 519 |
-
statements: List[SQL] = []
|
| 520 |
-
params: List[Param] = []
|
| 521 |
-
alias = -1
|
| 522 |
-
|
| 523 |
-
for mtrx in self:
|
| 524 |
-
froms, wheres, selects, sqlbind = [], [], [], {}
|
| 525 |
-
prev = {alias:None, self.tbl:None, self.row:None, self.col:None, self.val:None}
|
| 526 |
-
|
| 527 |
-
for vctr in mtrx:
|
| 528 |
-
curr = {alias:prev[alias], self.tbl:None, self.row:None, self.col:None, self.val:None}
|
| 529 |
-
|
| 530 |
-
for axis in (self.tbl, self.row, self.col, self.val):
|
| 531 |
-
if vctr[axis].opr.kind == 'EQL' and vctr[axis].k1 == 'VSAME': curr[axis] = prev[axis]
|
| 532 |
-
elif vctr[axis].unitary: curr[axis] = vctr[axis][1][0]
|
| 533 |
-
elif axis in (self.row, self.val): curr[axis] = None
|
| 534 |
-
else: raise SyntaxError(f'E_SQL_SUPPORT')
|
| 535 |
-
|
| 536 |
-
# JOIN
|
| 537 |
-
if prev[alias] is None or any(curr[axis] is None or prev[axis] != curr[axis] for axis in (self.tbl, self.row)):
|
| 538 |
-
|
| 539 |
-
# TABLE ALIAS
|
| 540 |
-
curr[alias] = f't{alias_idx}'
|
| 541 |
-
froms.append(f'{curr[self.tbl]} AS {curr[alias]}')
|
| 542 |
-
alias_idx += 1
|
| 543 |
-
|
| 544 |
-
# PRIMARY KEY
|
| 545 |
-
if curr[self.row] is None: curr[self.row]=Token('DBCOL', f'{curr[alias]}.{self.primary}')
|
| 546 |
-
if prev[self.row] is not None: sqlbind[VSAME]=prev[self.row]
|
| 547 |
-
if vctr[self.row].k1 != 'EMPTY':
|
| 548 |
-
w, ps = SQLUtil.compare(f'{curr[alias]}.{self.primary}', vctr[self.row], sqlbind)
|
| 549 |
-
wheres.append(w)
|
| 550 |
-
params.extend([p for p in ps if p is not None])
|
| 551 |
-
|
| 552 |
-
if prev[self.tbl] != curr[self.tbl]: selects.append(f"'{curr[self.tbl]}'")
|
| 553 |
-
selects.append(f'{curr[alias]}.{self.primary}')
|
| 554 |
-
|
| 555 |
-
# COLUMN = VALUE
|
| 556 |
-
ccol = curr[self.col].datum
|
| 557 |
-
cacol = f'{curr[alias]}.{ccol}'
|
| 558 |
-
if curr[self.val] is None: curr[self.val]=Token('DBCOL', cacol)
|
| 559 |
-
if prev[self.val] is not None: sqlbind[VSAME]=prev[self.val]
|
| 560 |
-
if vctr[self.val].k1 != 'EMPTY':
|
| 561 |
-
w, ps = SQLUtil.compare(cacol, vctr[self.val], sqlbind)
|
| 562 |
-
wheres.append(w)
|
| 563 |
-
params.extend([p for p in ps if p is not None])
|
| 564 |
-
|
| 565 |
-
selects.extend([f"'{ccol}'", cacol, f"'{SEP_VCTR}'"])
|
| 566 |
-
|
| 567 |
-
# BIND VARS
|
| 568 |
-
for axis in (self.row, self.val):
|
| 569 |
-
if vctr[axis][0].kind == 'VAR': sqlbind[vctr[axis][0].lexeme]=curr[axis]
|
| 570 |
-
|
| 571 |
-
prev = curr.copy()
|
| 572 |
-
|
| 573 |
-
if not wheres: raise SyntaxError('E_EMPTY_WHERE')
|
| 574 |
-
selects.append(f"'{SEP_MTRX}'")
|
| 575 |
-
statements.append(f'SELECT CONCAT_WS(\'{SEP_LIMIT}\', ' + ', '.join(selects) + ') AS meme FROM ' + ', '.join(froms) + ' WHERE ' + ' AND '.join(wheres))
|
| 576 |
-
return ' UNION '.join(statements) + ';', params
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
class MemeSQLTuple(MemeSQL):
|
| 580 |
-
tbl: str = 'vtbl'
|
| 581 |
-
val: Axis|None = 0
|
| 582 |
-
row: Axis = 2
|
| 583 |
-
|
| 584 |
-
def sql_create_table(self) -> SQL:
|
| 585 |
-
return f'CREATE TABLE IF NOT EXISTS {self.tbl} (a3 TEXT, a2 BIGINT, a1 TEXT, a0s TEXT, a0id BIGINT, a0f DOUBLE PRECISION);'
|
| 586 |
-
|
| 587 |
-
def to_select(self) -> Tuple[SQL, List[Param]]:
|
| 588 |
-
|
| 589 |
-
src = -1
|
| 590 |
-
alias_idx: int = 0
|
| 591 |
-
statements: List[SQL] = []
|
| 592 |
-
params: List[Param] = []
|
| 593 |
-
|
| 594 |
-
for mtrx in self:
|
| 595 |
-
froms, wheres, selects, groupbys, sqlbind = [], [], [], [], {}
|
| 596 |
-
|
| 597 |
-
vlen = len(mtrx[0])
|
| 598 |
-
prev = [None] * (vlen + 1)
|
| 599 |
-
|
| 600 |
-
for vctr in mtrx:
|
| 601 |
-
curr = [None] * vlen
|
| 602 |
-
curr.append(f'v{alias_idx}')
|
| 603 |
-
froms.append(f'{self.tbl} AS {curr[src]}')
|
| 604 |
-
|
| 605 |
-
for axis in range(vlen-1, -1, -1):
|
| 606 |
-
if axis != self.val:
|
| 607 |
-
sel_col = f'{curr[src]}.a{axis}'
|
| 608 |
-
whr_col = f'{curr[src]}.a{axis}'
|
| 609 |
-
else:
|
| 610 |
-
axf, axs = f'{curr[src]}.a{axis}f', f'{curr[src]}.a{axis}s'
|
| 611 |
-
sel_col = f'(CASE WHEN {axf} IS NOT NULL THEN {axf}::text ELSE \'"\' || {axs} || \'"\' END)'
|
| 612 |
-
whr_col = axf if vctr[axis].opr.kind in {'GT','LT','GE','LE'} or vctr[axis].k1 in {'INT','FLOAT'} else axs
|
| 613 |
-
|
| 614 |
-
selects.append(sel_col)
|
| 615 |
-
|
| 616 |
-
if (vctr[axis].opr.kind, vctr[axis].k1) == ('EQL', 'VSAME'): curr[axis] = prev[axis]
|
| 617 |
-
elif vctr[axis].unitary: curr[axis] = vctr[axis][1][0]
|
| 618 |
-
else: curr[axis] = Token('DBCOL', whr_col)
|
| 619 |
-
|
| 620 |
-
if prev[axis] is not None: sqlbind[VSAME]=prev[axis]
|
| 621 |
-
if vctr[axis].k1 != 'EMPTY':
|
| 622 |
-
w, ps = SQLUtil.compare(whr_col, vctr[axis], sqlbind)
|
| 623 |
-
wheres.append(w)
|
| 624 |
-
params.extend([p for p in ps if p is not None])
|
| 625 |
-
|
| 626 |
-
if vctr[axis][0].kind == 'VAR': sqlbind[vctr[axis][0].lexeme]=curr[axis]
|
| 627 |
-
|
| 628 |
-
if prev[src] is None or any(prev[axis] != curr[axis] for axis in range(vlen-1, self.row-1, -1)):
|
| 629 |
-
for a in range(vlen-1, self.row-1, -1): groupbys.append(f"{curr[src]}.a{a}")
|
| 630 |
-
|
| 631 |
-
selects.append(f"'{SEP_VCTR}'")
|
| 632 |
-
prev = curr[:]
|
| 633 |
-
alias_idx += 1
|
| 634 |
-
|
| 635 |
-
selects.append(f"'{SEP_MTRX}'")
|
| 636 |
-
groupbystr = ' GROUP BY ' + ', '.join(groupbys) if groupbys else ''
|
| 637 |
-
statements.append(f'SELECT CONCAT_WS(\'{SEP_LIMIT}\', ' + ', '.join(selects) + ') AS meme FROM ' + ', '.join(froms) + ' WHERE ' + ' AND '.join(wheres) + groupbystr)
|
| 638 |
-
|
| 639 |
-
return ' UNION '.join(statements) + ';', params
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
### MAIN ###
|
| 643 |
-
|
| 644 |
-
if __name__ == '__main__':
|
| 645 |
-
memelangs: List[Memelang] = [
|
| 646 |
-
'movies * actor "Mark Hamill",Mark ; movie * ; rating >4 ;;',
|
| 647 |
-
'movies * actor "Mark Hamill" ; movie * ; !@ @ @ ; actor * ;;',
|
| 648 |
-
'movies $id=* actor "Mark Hamill" ; movie * ; !$id @ @ ; actor !"Mark Hamill" ;;',
|
| 649 |
-
'actors * age >21; name $n=* ; movies * title $n ;;',
|
| 650 |
-
'actors * age >21; name * ; movies * title @ ;;'
|
| 651 |
-
]
|
| 652 |
-
if ELIDE_VSAME: memelangs.append('movies * actor "Mark Hamill",Mark ; rating >4 ; movie * ; ! = = ; actor * ;;')
|
| 653 |
-
|
| 654 |
-
for src in memelangs: print(src, MemeSQLTable(src).to_select())
|
| 655 |
-
</code></pre></div>
|
| 656 |
-
|
| 657 |
-
<p><a href="mailto:info@memelang.net">info@memelang.net</a> · <a href="https://patents.google.com/patent/US20250068615A1">Patents Pending</a> · ©2025 HOLTWORK LLC</p>
|
| 658 |
|
| 659 |
</main>
|
| 660 |
</body>
|
|
|
|
| 33 |
</video>
|
| 34 |
|
| 35 |
|
| 36 |
+
<p>Memelang is an AI-optimized query language that significantly reduces token count and model size for LLM RAG. <a href="https://memelang.net/08/">View the code here</a>.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
</main>
|
| 39 |
</body>
|