Skip to content

Instantly share code, notes, and snippets.

@jacoobes
Created December 23, 2024 19:24
Show Gist options
  • Save jacoobes/dfd524dff2ab314a6b44e9384072bf50 to your computer and use it in GitHub Desktop.
Save jacoobes/dfd524dff2ab314a6b44e9384072bf50 to your computer and use it in GitHub Desktop.
Incomplete but browser compatible edn parsr
class EDNParser {
constructor(ednString) {
this.edn = ednString;
this.index = 0;
}
parse() {
this.skipWhitespace();
const element = this.readElement();
this.skipWhitespace();
if (this.index < this.edn.length) {
throw new Error("Unexpected data after valid EDN element.");
}
return element;
}
readElement() {
this.skipWhitespace();
const char = this.peek();
if (char === undefined) return null;
switch (char) {
case '"': return this.readString();
case '{': return this.readMap();
case '[': return this.readVector();
case '(': return this.readList();
case '#': return this.readTaggedOrSet();
case ':': return this.readKeyword();
case ';': this.skipComment(); return this.readElement();
default:
if (/\d|-|\+/.test(char)) return this.readNumber();
if (/\\/.test(char)) return this.readCharacter();
return this.readSymbol();
}
}
readString() {
this.expect('"');
let str = "";
while (this.peek() !== '"') {
if (this.peek() === "\\") {
this.index++;
const escapeChar = this.edn[this.index];
const escapeMap = { t: "\t", r: "\r", n: "\n", '\\': "\\", '"': '"' };
str += escapeMap[escapeChar] || escapeChar;
} else {
str += this.edn[this.index];
}
this.index++;
}
this.expect('"');
return str;
}
readCharacter() {
this.expect("\\");
const char = this.edn.slice(this.index).match(/^[^\s]+/);
if (!char) throw new Error("Invalid character.");
this.index += char[0].length;
const charMap = { newline: "\n", return: "\r", space: " ", tab: "\t" };
return charMap[char[0]] || char[0];
}
readSymbol() {
const match = this.edn.slice(this.index).match(/^[^\s\(\)\[\]\{\}"#:;]+/);
if (!match) throw new Error("Invalid symbol.");
this.index += match[0].length;
return match[0];
}
readKeyword() {
this.expect(":");
const match = this.edn.slice(this.index).match(/^[^\s\(\)\[\]\{\}"#:;]+/);
if (!match) throw new Error("Invalid keyword.");
this.index += match[0].length;
return `:${match[0]}`;
}
readNumber() {
const match = this.edn.slice(this.index).match(/^-?\d+(\.\d+)?([eE][+-]?\d+)?[M]?/);
if (!match) throw new Error("Invalid number.");
this.index += match[0].length;
return parseFloat(match[0]);
}
readList() {
this.expect("(");
const list = [];
while (this.peek() !== ")") {
list.push(this.readElement());
}
this.expect(")");
return list;
}
readVector() {
this.expect("[");
const vector = [];
while (this.peek() !== "]") {
vector.push(this.readElement());
}
this.expect("]");
return vector;
}
readMap() {
this.expect("{");
const map = {};
while (this.peek() !== "}") {
const key = this.readElement();
const value = this.readElement();
map[key] = value;
}
this.expect("}");
return map;
}
readTaggedOrSet() {
this.expect("#");
if (this.peek() === "{") return this.readSet();
const tag = this.readSymbol();
const element = this.readElement();
return { tag, element };
}
readSet() {
this.expect("{");
const set = new Set();
while (this.peek() !== "}") {
set.add(this.readElement());
}
this.expect("}");
return set;
}
skipComment() {
while (this.peek() && this.peek() !== "\n") {
this.index++;
}
this.index++; // Skip the newline
}
skipWhitespace() {
while (/\s|,/.test(this.peek())) this.index++;
}
peek() {
return this.edn[this.index];
}
expect(char) {
if (this.edn[this.index] !== char) {
throw new Error(`Expected '${char}' but found '${this.edn[this.index]}'.`);
}
this.index++;
}
}
// Example usage:
const ednStr = `{:name "John", :age 0, :hobbies ["reading" "cycling"]}`;
const parser = new EDNParser(ednStr);
console.log(parser.parse());
@jacoobes
Copy link
Author

output =

Object { ":name": "John", ":age": 0, ":hobbies": (2) [] }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment