Skip to content

Commit 1b90565

Browse files
committed
Wrote basic encoder / decoder
1 parent 3f23475 commit 1b90565

File tree

4 files changed

+433
-0
lines changed

4 files changed

+433
-0
lines changed

Source/Node.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
2+
import { printify } from './Stringify.js'
3+
4+
5+
const { entries } = Object;
6+
7+
8+
export default class Node {
9+
10+
#properties = {}
11+
#items = []
12+
13+
name = 'Root'
14+
15+
16+
/**
17+
* Append one or more items to the node.
18+
*/
19+
20+
push ( ... items ){
21+
this.#items.push(...items);
22+
}
23+
24+
25+
/**
26+
* Pop the last value on the node.
27+
*/
28+
29+
pop (){
30+
return this.#items.pop();
31+
}
32+
33+
34+
/**
35+
* Shift one or more items into the node.
36+
*/
37+
38+
unshift ( ... items ){
39+
this.#items.unshift(...items);
40+
}
41+
42+
43+
/**
44+
* Shift an item from the node.
45+
*/
46+
47+
shift (){
48+
return this.#items.shift();
49+
}
50+
51+
52+
/**
53+
* Appends a named item to the node.
54+
* If the index is given, inserts
55+
* it at that position instead.
56+
*
57+
* @param name Name of the item.
58+
* @param value Value of the item.
59+
* @param index Position of insertion.
60+
*/
61+
62+
assign ( name , value , index ){
63+
64+
const { items } = this;
65+
66+
index ??= items.length - 1;
67+
68+
let item = { name , value };
69+
70+
if(value instanceof Node){
71+
value.name = name;
72+
item = value;
73+
}
74+
75+
items.splice(index,0,item);
76+
this.#properties[name] = index;
77+
}
78+
79+
80+
/**
81+
* Returns the item at the given position.
82+
* Negative indexes start from the end.
83+
* @param index Index of the item.
84+
*/
85+
86+
item ( index ){
87+
return this.items.at(index)
88+
}
89+
90+
91+
/**
92+
* Returns the value or node uniquely
93+
* associated with the given name.
94+
* @param name Name of the property.
95+
*/
96+
97+
named ( name ){
98+
99+
const item = this.item(this.#properties[key]);
100+
101+
if(item instanceof Node)
102+
return item
103+
104+
return item?.value
105+
}
106+
107+
108+
/**
109+
* A list of all items.
110+
*/
111+
112+
get items (){
113+
return this.#items
114+
}
115+
116+
117+
/**
118+
* The nodes item count.
119+
*/
120+
121+
get size (){
122+
return this.items.length;
123+
}
124+
125+
126+
/**
127+
* Stringifies this node and all of it's value.
128+
*/
129+
130+
toString (){
131+
return printify(this);
132+
133+
}
134+
135+
136+
/**
137+
* Generate a node from raw graph data.
138+
*/
139+
140+
static from ( data ){
141+
142+
const { name , value } = data;
143+
144+
const graph = new Node;
145+
graph.#items = value;
146+
graph.name = name ?? 'Root';
147+
148+
for ( const [ index , item ] of entries(value) )
149+
if(typeof item === 'object')
150+
integrate(index,item);
151+
152+
153+
function integrate ( index , item ){
154+
155+
const { value , name } = item;
156+
157+
graph.#properties[name] = index;
158+
159+
if(Array.isArray(value)){
160+
161+
const { length } = value;
162+
163+
switch ( true ){
164+
case length < 1 :
165+
graph.#items[index] = null;
166+
return
167+
case length === 1 :
168+
graph.#items[index] = value[0];
169+
return
170+
default:
171+
graph.#items[index] = Node.from(item);
172+
return
173+
}
174+
}
175+
}
176+
177+
return graph
178+
}
179+
}

Source/Parse.js

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
2+
import Node from './Node.js'
3+
4+
5+
const Tokens = [
6+
[ /^{/ , '{' , 1 ] ,
7+
[ /^}/ , '}' , 1 ] ,
8+
[ /^\/\*/ , '/*' , 2 ] ,
9+
[ /^\*\// , '*/' , 2 ] ,
10+
[ /^"/ , '"' , 1 ] ,
11+
[ /^[a-z][a-z0-9_]*/i , 'Name' ] ,
12+
[ /^(\+|\-)?[0-9]+(\.[0-9]+)?/ , 'Number' ] ,
13+
[ /^\\\S/ , 'Escaped' ] ,
14+
]
15+
16+
17+
18+
19+
export default function parse ( string ){
20+
21+
const tree = buildAST(parseNumbers(dropUnknown(normalizeStrings(toTokens(string)))));
22+
23+
return Node.from(tree);
24+
}
25+
26+
27+
function buildAST ( tokens ){
28+
29+
const root = { value : [] };
30+
31+
let parent = root ,
32+
stack = [] ;
33+
34+
for ( const token of tokens ){
35+
36+
const [ type , value ] = token;
37+
38+
39+
// console.log(type,parent,stack)
40+
41+
switch (type){
42+
case 'String' :
43+
case 'Number' :
44+
parent.value.push(value);
45+
continue
46+
case 'Name' :
47+
48+
const child = { name : value , value : [] };
49+
parent.value.push(child);
50+
51+
stack.push(parent);
52+
parent = child;
53+
54+
continue
55+
case '}' :
56+
57+
switch ( parent.value.length ){
58+
case 0 :
59+
parent.value = null;
60+
break;
61+
case 1 :
62+
parent.value = parent.value[0];
63+
break;
64+
}
65+
66+
parent = stack.pop();
67+
continue
68+
}
69+
}
70+
71+
72+
return root
73+
}
74+
75+
76+
function findToken ( string ){
77+
78+
for ( const [ pattern , token , length ] of Tokens)
79+
if(length){
80+
if(pattern.test(string))
81+
return [ token , length ]
82+
} else {
83+
84+
const value = string
85+
.match(pattern)
86+
?.at(0);
87+
88+
if(value)
89+
return [ token , value.length , value ]
90+
}
91+
}
92+
93+
function * toTokens ( string ){
94+
95+
while ( true ){
96+
97+
matchNext:
98+
99+
if(string.length < 1)
100+
return
101+
102+
const match = findToken(string);
103+
104+
if(match){
105+
106+
const [ token , take , value ] = match;
107+
108+
yield [ token , value ]
109+
110+
string = string.substring(take);
111+
112+
} else {
113+
yield [ 'Unknown' , string.at(0) ]
114+
string = string.substring(1);
115+
}
116+
}
117+
}
118+
119+
120+
function * normalizeStrings ( tokens ){
121+
122+
let combined , process ;
123+
124+
125+
const normal = ( token ) => {
126+
127+
const [ type ] = token;
128+
129+
if(type === '"'){
130+
process = string;
131+
combined = '';
132+
return
133+
}
134+
135+
return token
136+
}
137+
138+
139+
const string = ( token ) => {
140+
141+
const [ type , value ] = token;
142+
143+
if(type === '"'){
144+
process = normal;
145+
return [ 'String' , combined ]
146+
}
147+
148+
combined += value;
149+
}
150+
151+
152+
process = normal;
153+
154+
for ( const token of tokens ){
155+
156+
const value = process(token);
157+
158+
if(value)
159+
yield value
160+
}
161+
}
162+
163+
164+
function * dropUnknown ( tokens ){
165+
for ( const token of tokens )
166+
if(token[0] !== 'Unknown')
167+
yield token
168+
}
169+
170+
171+
function * parseNumbers ( tokens ){
172+
173+
for ( const token of tokens ){
174+
175+
const [ type , value ] = token;
176+
177+
if(type === 'Number')
178+
token[1] = parseFloat(value);
179+
180+
yield token
181+
}
182+
}

0 commit comments

Comments
 (0)