From 833c7182de5acca25a01dac5ef5abea9b691c0a2 Mon Sep 17 00:00:00 2001 From: Abdussamed Date: Wed, 3 Apr 2024 04:50:41 +0300 Subject: [PATCH] Expol --- Hemex.js | 525 ++++++++++++++++++++++++++++++++++++++++++++++++++ document.html | 11 ++ expol.js | 359 ++++++++++++++++++++++++++++++++++ 3 files changed, 895 insertions(+) create mode 100644 Hemex.js create mode 100644 document.html create mode 100644 expol.js diff --git a/Hemex.js b/Hemex.js new file mode 100644 index 0000000..138eff8 --- /dev/null +++ b/Hemex.js @@ -0,0 +1,525 @@ +export default function Hemex() +{ + +}; +Hemex.EOL = "\n"; +/** + * Hemex variable white space chars + * @type {Number[]} + */ +Hemex.WhiteSpace = [ + 9,10,11,12,13,32,133 +]; +/** + * Current cursor position + * @type {Number} + */ +Hemex.prototype.offset = 0; +/** + * Mapping offset points + * @type {Number[]} + */ +Hemex.prototype.offsetMap = []; +Hemex.prototype.beginPosition = function(){ + this.offsetMap.push( + this.getLastPosition() + ) +} +/** + * Adding current position to offset map + */ +Hemex.prototype.acceptPosition = function(){ + let t = this.offsetMap.pop(); + this.setLastPosition(t) +} +/** + * Get text range current and parent offsets + * @returns {[Number,Number]} + */ +Hemex.prototype.positionRange = function(){ + let len = this.offsetMap.length; + if(len == 0) + { + return [0,this.offset] + }else if(len == 1){ + return [this.offset,this.offsetMap[len - 1]] + }else{ + return [this.offsetMap[len - 2],this.offsetMap[len - 1]] + } +} +/** + * Get text range between current offset and parent offset + * @returns {String} + */ +Hemex.prototype.getPositionRange = function(){ + let u = this.positionRange(); + return this.text.slice(u[0],u[1]) +} +/** + * Cancel current position and return to parent offset + */ +Hemex.prototype.rejectPosition = function(){ + this.offsetMap.pop() +} +/** + * Get current layer of position from last offset of map + * @returns {Number} + */ +Hemex.prototype.getLastPosition = function(){ + return this.offsetMap.length == 0 ? this.offset : this.offsetMap.slice(-1)[0] +} +/** + * Set last position offset from offset map last layer + * @param {Number} n + */ +Hemex.prototype.setLastPosition = function(n){ + if(this.offsetMap.length == 0) + this.offset = n + else this.offsetMap[this.offsetMap.length - 1] = n +} +/** + * Get current layer of position from last offset of map + * Some as getLastPosition() + * @returns {Number} + */ +Hemex.prototype.getOffset = function(){ + return this.getLastPosition() +} +/** + * Set last position offset from offset map last layer and return it value + * @param {Number} n + * @returns {Number} + */ +Hemex.prototype.setOffset = function(n){ + this.setLastPosition(n); + return this.getLastPosition() +} +/** + * Get text length + * @type {Number} + */ +Hemex.prototype.length = 0; + +/** + * Hemex lexing data + * @type {String} + */ +Hemex.prototype.text = ""; + +/** + * set lexing data + * @param {String} text + * @returns {void} + */ +Hemex.prototype.setText = function(text){ + this.offset = 0; + this.length = text.length; + this.offsetMap = []; + this.text = text; +} + +/** + * get lexing all data + * @returns {String} + */ +Hemex.prototype.getText = function(){ + return this.text; +} +/** + * Get one character from cursor + * @param {Number} n + * @returns {String} + */ +Hemex.prototype.getChar = function(n){ + return this.text.charAt(n?this.getOffset()+n:this.getOffset()) +} +/** + * Boolean + * @param {Number} n + * @returns {String} + */ +Hemex.prototype.isChar = function(b){ + return this.getChar() == b +} +/** + * Dump all data from cursor position to end of char + * @param {Number} n + */ +Hemex.prototype.dump = function(n){ + return this.text.slice(this.getOffset(),this.getOffset()+n) +} +/** + * Control coming end of line + * @returns {Bollean} + */ +Hemex.prototype.isEnd = function(){ + return this.length > this.getOffset() +} +/** + * Forward one char + */ +Hemex.prototype.nextChar = function(){ + this.setOffset(this.getOffset() + 1); +} +/** + * Forward n char + */ +Hemex.prototype.toChar = function(n){ + this.setOffset(this.getOffset() + n); +} +/** + * Reading while end of line + * @returns {String} + */ +Hemex.prototype.getLine = function(){ + return this.of(function(){ + switch(this.getChar()) + { + case Hemex.EOL: return false; + default: return true; + } + }.bind(this)) +} +/** + * Read all data until the function returns false + * @param {Boolean} p + * @param {(char:String)=>Boolean} e + * @returns {String} + */ +Hemex.prototype.of = function(e,p){ + let k = [],count=0; + while(this.isEnd()){ + if(e(p,count)) k.push(this.getChar()); + else return k.join(''); + count++; + this.nextChar(); + }; + return k.length == 0 ? false : k.join('') +} +Hemex.prototype.each = function(e,p){ + let k = []; + while(this.isEnd()) + if(!e(p)) return; + else this.nextChar(); +} +Hemex.prototype.while = function(e,p){ + let k = []; + while(this.isEnd()) + if(!e(p)) return; +} +/** + * Controlling for current char type + * @param {Boolean} reverse + * @returns {Boolean} + */ +Hemex.prototype.isNumber = function(reverse){ + let c = this.getChar().charCodeAt(0); + let result = c >= 48 && c <= 57; + return reverse ? !result : result; +} +/** + * Read all data until char type is not number + * @param {Boolean} reverse + * @returns {String} + */ +Hemex.prototype.readNumbers = function(reverse){ + return this.of(this.isNumber.bind(this),reverse) +} +/** + * Controlling for current char type + * @param {Boolean} reverse + * @returns {Boolean} + */ +Hemex.prototype.isBigLetter = function(reverse){ + let c = this.getChar().charCodeAt(0); + let result = c >= 97 && c <= 122; + return reverse ? !result : result; +} +/** + * Controlling for current char type + * @param {Boolean} reverse + * @returns {Boolean} + */ +Hemex.prototype.isSmallLetter = function(reverse){ + let c = this.getChar().charCodeAt(0); + let result = c >= 65 && c <= 90; + return reverse ? !result : result; +} +/** + * Controlling for current char type + * @param {Boolean} reverse + * @returns {Boolean} + */ +Hemex.prototype.isLetter = function(reverse){ + let result = this.isSmallLetter() || this.isBigLetter() + return reverse ? !result : result; +} +/** + * Controlling for current char type + * @param {Boolean} reverse + * @returns {Boolean} + */ +Hemex.prototype.isLetterRegex = function(reverse){ + let result = /^[\p{L}]$/u.test(this.getChar()); + return reverse ? !result : result; +} + + + + + +/** + * Read all data until char type is not letter + * @param {Boolean} reverse + * @returns {String} + */ +Hemex.prototype.readLetters = function(reverse){ + return this.of(this.isLetter.bind(this),reverse) +} +/** + * Controlling for current char type + * @param {Boolean} reverse + * @returns {Boolean} + */ +Hemex.prototype.isWhiteSpace = function(reverse){ + let c = this.getChar(),ct = c.charCodeAt(0); + let result = ( + c == '\n' || + c == '\r' || + c == '\t' || + c == ' ' || + Hemex.WhiteSpace.includes(ct) + ) + return reverse ? !result : result; +} +/** + * Read all data until char type is not white space + * @param {Boolean} reverse + * @returns {String} + */ +Hemex.prototype.readWhiteSpace = function(reverse){ + return this.of(this.isWhiteSpace.bind(this),reverse) +} +/** + * Controlling data + * @param {Boolean} reverse + * @returns {String} + */ +Hemex.prototype.include = function(words,next){ + this.beginPosition(); + for(let i = 0; i{ + switch(c = this.getChar()) + { + case '0': + case '1':{ + data.push(c); + break; + } + case '2': + case '3': + case '4': + case '5': + case '6': + case '7':{ + if(base >= 8){ + data.push(c); + break; + }else return false; + } + case '8': + case '9':{ + if(base >= 10){ + data.push(c); + break; + }else return false; + } + case 'A': + case 'a': + case 'B': + case 'b': + case 'C': + case 'c': + case 'D': + case 'd': + /* case 'E': case 'e': */ + case 'F': + case 'f':{ + if(base >= 16){ + data.push(c); + break; + }else return false; + } + case '.':{ + if(!nextDot){ + if(data.length == 0){ + data.push("0"); + }else data.push("."); + nextDot = true; + isFloat = true; + }else{ + throw new Error("Float number in Double dot"); + }; + break; + } + case 'E': + case 'e':{ + if(this.getChar(1)!='+'){ + if(base == 16){ + data.push(c); + break; + }else return false; + }; + if(data.length == 0){ + this.rejectPosition(); + return false; + }; + data.push('e'); + this.nextChar(); + if(this.getChar()=='+' || this.getChar()=='-'){ + data.push(char()); + this.nextChar(); + }; + let result = null; + this.each(()=>{ + switch(this.getChar()){ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9':{ + data.push(this.getChar()); + this.nextChar(); + break; + } + default:{ + return false; + } + } + }) + } + default:{ + return false; + } + }; + return true + }); + return data.length == 0 ? false : [data.join(''),base] +} + +Hemex.prototype.syntaxs = new Map(); +/** + * + * @param {string} name + * @param {(hmx:Hemex, result: (result:any) => any,...args:any[]) => any} callback + */ +Hemex.prototype.syntax = function(name, callback){ + this.syntaxs.set(name, callback) +} + +Hemex.prototype.give = function(name, ...args){ + let sandbox = this.syntaxs.get(name); + if(sandbox) + { + let res = undefined; + this.beginPosition(); + if(sandbox( + this, + arg => { + res = arg + }, + ...args + )) + { + this.acceptPosition(); + return res; + }else{ + this.rejectPosition(); + return res; + } + } +} +/** + * @param {Error} message + */ +Hemex.prototype.throw = function(message){ + throw new Error(message) +}; \ No newline at end of file diff --git a/document.html b/document.html new file mode 100644 index 0000000..c6a4807 --- /dev/null +++ b/document.html @@ -0,0 +1,11 @@ + + + + + + Document + + + + + \ No newline at end of file diff --git a/expol.js b/expol.js new file mode 100644 index 0000000..75108c0 --- /dev/null +++ b/expol.js @@ -0,0 +1,359 @@ +import Hemex from "./Hemex.js" + +const style = document.createElement("style") +document.head.append(style) + +style.sheet.insertRule(` + .expol-container{ + box-sizing: border-box; + padding: 5px; + border: solid 1px black; + overflow: hidden; + border-radius: 5px; + } +`) +style.sheet.insertRule(` + .expol-container .expol{ + box-sizing: border-box; + position: relative; + min-height: 1em; + font-family: inherit;3em + font-size: inherit; + font-weight: inherit; + white-space: nowrap; + } +`) + +/** + * @type {CSSRule} + */ +let fontRule = Array.from(style.sheet.cssRules).filter(e => e.selectorText == '.expol-container .expol')[0] + +function updateFont(family,size,weight) +{ + fontRule.style.fontFamily = family + fontRule.style.fontSize = size + fontRule.style.fontWeight = weight +} + +style.sheet.insertRule(` + .expol .viewport{ + position: absolute; + top: 0px; + left:0; + right:0; + height:0; + width: 100%; + height: 100%; + padding: 0 + margin: 0; + color: black; + z-index: 0; + font-family: inherit; + font-size: inherit; + font-weight: inherit; + } +`) +style.sheet.insertRule(` + .expol .textinput{ + position: absolute; + top: 0px; + left:0; + right:0; + height:0; + width: 100%; + height: 100%; + padding: 0; + margin: 0; + color: transparent; + z-index: 1; + font-family: inherit; + font-size: inherit; + font-weight: inherit; + outline: none; + caret-color: black; + } +`) +style.sheet.insertRule(` + .expol pre{ + margin: 0; + font-family: inherit; + display: inline; + } +`) +style.sheet.insertRule(` + .expol .col1{ + color: #00a902; + } +`) +style.sheet.insertRule(` + .expol .col2{ + color: #ff0000; + } +`) +style.sheet.insertRule(` + .expol .col3{ + color: black; + } +`) +style.sheet.insertRule(` + .expol .col4{ + color: black; + opacity: 0.5; + } +`) +style.sheet.insertRule(` + .expol .col5{ + text-decoration: underline; + color: #0016ff; + } +`) + +updateFont("monospace","2em","500") + +let container = document.createElement("div") +container.classList.add("expol-container") +document.body.appendChild(container) + +let expol = document.createElement("div") +expol.classList.add("expol") +container.appendChild(expol) + +let viewport = document.createElement("pre") +viewport.classList.add("viewport") +expol.appendChild(viewport) + +let textinput = document.createElement("pre") +textinput.classList.add("textinput") +textinput.contentEditable = true +expol.appendChild(textinput) +textinput.addEventListener("keyup",synctoViewPort) +textinput.addEventListener("keypress",synctoViewPort) +textinput.addEventListener("keydown",synctoViewPort) +textinput.addEventListener("paste",(pasteevent)=>{ + let text = pasteevent.clipboardData.getData('text/plain') + textinput.innerText = text + synctoViewPort() + pasteevent.preventDefault() +}) + +function synctoViewPort() +{ + let fragment = renderColorizer(textinput.innerText) + for (const child of Array.from(viewport.childNodes)) + { + child.remove() + } + viewport.append(fragment) +} + +function renderColorizer(text) +{ + let fragment = document.createDocumentFragment() + let hmx = new Hemex() + hmx.setText(text) + let blank = true + + let query = [] + let term = [] + + hmx.syntax("term",function(hmx,result){ + if(hmx.isChar('-')) + { + hmx.nextChar() + let name = hmx.of(() => hmx.isLetterRegex() || hmx.isNumber() || hmx.includes(['-','_'])) + let operator = 'has' + let value = "" + if(name) + { + let pre = document.createElement("pre") + pre.classList.add("col1") + pre.setAttribute("role","name") + let textDoc = document.createTextNode('-' + name) + pre.appendChild(textDoc); + fragment.appendChild(pre) + }else{ + let pre = document.createElement("pre") + pre.classList.add("col1") + pre.setAttribute("role","name") + let textDoc = document.createTextNode('-'); + pre.appendChild(textDoc); + fragment.appendChild(pre) + } + + let k + if(k = hmx.includes([':','>','<','>=','<=','!='])) + { + operator = k[0] + let pre = document.createElement("pre") + pre.classList.add("col2") + pre.setAttribute("role","operator") + let textDoc = document.createTextNode(k[0]) + pre.appendChild(textDoc) + fragment.appendChild(pre) + hmx.nextChar() + value = hmx.give('strorword') + } + + query.push({ + query:[name,operator,value] + }) + + return true + } + }) + + hmx.syntax("mention",function(hmx,result){ + if(hmx.isChar('@')) + { + hmx.nextChar() + let name = hmx.of(() => hmx.isLetterRegex() || hmx.isNumber() || hmx.includes(['-','_'])) + + if(name) + { + let pre = document.createElement("pre") + pre.classList.add("col5") + pre.setAttribute("role","mention") + let textDoc = document.createTextNode('@' + name) + pre.appendChild(textDoc); + fragment.appendChild(pre) + }else{ + let pre = document.createElement("pre") + pre.classList.add("col5") + pre.setAttribute("role","mention") + let textDoc = document.createTextNode('@'); + pre.appendChild(textDoc); + fragment.appendChild(pre) + } + + query.push({ + mention: name + }) + + return true + } + }) + + // cute --username:"absorbe" + + hmx.syntax("strorword",function(hmx,result){ + let k = hmx.includes(['\'','"','`']) + if(k.length == 0 || k == false){ + let name = hmx.of(() => !hmx.isWhiteSpace()) + if(name) + { + + let pre = document.createElement("pre") + pre.classList.add("col3") + pre.setAttribute("role","value") + let textDoc = document.createTextNode(name||""); + pre.appendChild(textDoc) + fragment.appendChild(pre) + result(name||false) + + } + return true + }else{ + let char = hmx.getChar() + hmx.nextChar() + let name = hmx.of(() => !hmx.isChar(char)) + let kt = ""; + if(name) + { + result(name||false) + + kt = char + name; + if(hmx.isChar(char)){ + hmx.nextChar() + kt += char; + } + }else{ + kt += char; + hmx.nextChar() + } + + let pre = document.createElement("pre") + pre.classList.add("col3") + pre.setAttribute("role","value") + let textDoc = document.createTextNode(kt) + pre.appendChild(textDoc) + fragment.appendChild(pre) + return true + } + }) + + hmx.syntax("scope",function(hmx,result){ + let description = [] + hmx.while(()=>{ + switch(hmx.getChar()) + { + case "-":{ + if(description.length) + { + let pre = document.createElement("pre") + pre.classList.add("col4") + pre.setAttribute("role","description") + let textDoc = document.createTextNode(description.join('')) + pre.appendChild(textDoc) + fragment.appendChild(pre) + + if(description.join('').trim().length) term.push(description.join('')) + description = [] + } + if(blank) + { + hmx.give("term") + return true + } + } + case "@":{ + if(description.length) + { + let pre = document.createElement("pre") + pre.classList.add("col4") + pre.setAttribute("role","description") + let textDoc = document.createTextNode(description.join('')) + pre.appendChild(textDoc) + fragment.appendChild(pre) + + if(description.join('').trim().length) term.push(description.join('')) + description = [] + } + if(blank) + { + hmx.give("mention") + return true + } + } + default:{ + blank = hmx.isWhiteSpace() + description.push(hmx.getChar()) + hmx.nextChar() + return true + } + } + }) + + if(description.length) + { + if(description.join('').trim().length) term.push(description.join('')) + + let pre = document.createElement("pre") + pre.classList.add("col4") + pre.setAttribute("role","description") + let textDoc = document.createTextNode(description.join('')) + pre.appendChild(textDoc) + fragment.appendChild(pre) + } + + query.push({ + term + }) + }) + + hmx.give('scope') + + console.log("Query:",query) + + return fragment +} \ No newline at end of file