Skip to content

Commit c5cb5da

Browse files
committed
Adding keywords script
1 parent 6bdf3d5 commit c5cb5da

3 files changed

Lines changed: 1074 additions & 83 deletions

File tree

scripts/createKeywords.js

Lines changed: 123 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ const path = require('path');
4040
// OTHER changes
4141
// draw has been changed to draw_ on the righthand side
4242
// Gobal functions currently have _ at end, which they didn't in the original file: keyPressed, keyReleased, keyTyped, mouseClicked, mouseDragged, mouseMoved, mousePressed, mouseReleased, mouseWheel, settings, setup
43-
// pixelHeight was listed as a function with _ at the right end. I removed it.
43+
// pixelHeight was there twice. I removed it from the generated code
4444
// PVector was listed twice as FUNCTION1 in base and KEYWORD5 in generated output. I keps the latter.
45+
// PShader.set was `PShader FUNCTION2 PShader_set_`. Changed to `set FUNCTION2 PShader_set_`
46+
// PFont.list was listed as FUNCTION1. I changed it to be FUNCTION2 like the other class methods.
4547

4648
const folder = path.join(
4749
__dirname,
@@ -59,81 +61,99 @@ const folder = path.join(
5961
**/
6062
const ignore = ['width', 'height', 'x', 'y', 'z', 'Object'];
6163

64+
/**
65+
A list of global functions that need to have special handling
66+
**/
67+
const globalFuncs = [
68+
'keyPressed()',
69+
'keyReleased()',
70+
'keyTyped()',
71+
'mouseClicked()',
72+
'mouseDragged()',
73+
'mouseMoved()',
74+
'mousePressed()',
75+
'mouseReleased()',
76+
'mouseWheel()',
77+
'settings()',
78+
'setup()',
79+
'draw()'
80+
];
81+
6282
/**
6383
This script creates the keywords.txt file that is used to do syntax highligting
6484
in the Processing IDE.
6585
**/
6686
const createKeywords = () => {
67-
// TEMPORARY: To be used for diffing
68-
const currentKeywords = parseKeywordsFile('current_keywords.txt');
69-
const absIndex = currentKeywords.findIndex((k) => k[0] === 'abs');
70-
const needToGenerate = currentKeywords.slice(absIndex);
71-
7287
// Load the base keywords
73-
const baseKeywords = parseKeywordsFile('keywords_base.txt');
88+
const [baseKeywords, baseContents] = parseKeywordsFile('keywords_base.txt');
7489

7590
// Load all json files
7691
const entries = fs.readdirSync(folder).map((file) => {
77-
const entry = require(path.join(folder, file));
78-
return Object.assign(
92+
let entry = require(path.join(folder, file));
93+
entry = Object.assign(
7994
{
8095
filename: file,
81-
basename: path.basename(file, '.json'),
82-
clean: cleanName(entry)
96+
basename: path.basename(file, '.json')
8397
},
8498
entry
8599
);
100+
entry.leftName = leftName(entry);
101+
entry.rightName = rightName(entry);
102+
entry.token = getToken(entry);
103+
return entry;
86104
});
87105

88106
// Sort by listing in original keywords.txt
89107
entries.sort(sortByClassAndGlobal);
90108

91109
// Run through and add if not already in base
92-
const newKeywords = [];
110+
const keywords = [];
93111
for (let i = 0; i < entries.length; i++) {
94112
const entry = entries[i];
95-
if (entry.name === 'PVector') {
96-
console.log('here', shouldInclude(baseKeywords, entry));
97-
}
98113
if (shouldInclude(baseKeywords, entry)) {
99-
newKeywords.push([entry.clean, 'CATEGORY', entry.basename]);
114+
keywords.push([entry.leftName, entry.token, entry.rightName]);
100115
}
101116
}
102117

103-
// Log to compare objects
104-
for (let i = 0; i < needToGenerate.length; i++) {
105-
const a = needToGenerate[i][2];
106-
const b = newKeywords[i] ? newKeywords[i][2] : null;
107-
console.log(a == b, `| ${a} | ${b} |`);
108-
}
118+
const pad = (str, size) => (str ? str.padEnd(size, ' ') : str);
119+
120+
// COMPARE TO OLD VERSION
121+
/*
122+
const [currentKeywords] = parseKeywordsFile('keywords_old.txt');
123+
const absIndex = currentKeywords.findIndex((k) => k[0] === 'abs');
124+
const needToGenerate = currentKeywords.slice(absIndex);
125+
console.log('Old', needToGenerate.length, 'New', keywords.length);
109126
110-
console.log(
111-
'Old length',
112-
needToGenerate.length,
113-
'New Length',
114-
newKeywords.length
115-
);
116-
117-
// output rest of stuff from newKeywords
118-
if (newKeywords.length > needToGenerate.length) {
119-
for (let i = needToGenerate.length; i < newKeywords.length; i++) {
120-
console.log('EXTRA', needToGenerate[i]);
127+
for (let i = 0; i < needToGenerate.length; i++) {
128+
const a = needToGenerate[i];
129+
const b = keywords[i];
130+
const match = a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
131+
132+
if (!match) {
133+
console.log(
134+
pad(match.toString(), 7),
135+
pad(a[0], 25),
136+
pad(b[0], 25),
137+
pad(a[1], 10),
138+
pad(b[1], 10),
139+
pad(a[2], 25),
140+
pad(b[2], 25)
141+
);
121142
}
122143
}
123-
};
144+
*/
124145

125-
const sortByClassAndGlobal = (a, b) => {
126-
// If a is not a class, but b is, a comes first
127-
// if (!a.classanchor && b.classanchor) {
128-
// return -1;
129-
// }
146+
// Save to keywords.txt file
147+
const combined = baseContents + '\n\n' + generateKeywordsFile(keywords);
130148

131-
// If a is a class, but b is not, a comes last
132-
// else if (a.classanchor && !b.classanchor) {
133-
// return 1;
134-
// }
149+
fs.writeFileSync(path.join(__dirname, 'keywords.txt'), combined);
150+
console.log('keywords.txt file written to scripts folder');
151+
};
135152

136-
// Otherwise, compare apples to apples
153+
/**
154+
Sorts the entries based on the basename
155+
**/
156+
const sortByClassAndGlobal = (a, b) => {
137157
const strippedA = a.basename.replace(/_$/, '').toLowerCase();
138158
const strippedB = b.basename.replace(/_$/, '').toLowerCase();
139159

@@ -149,7 +169,7 @@ const sortByClassAndGlobal = (a, b) => {
149169
};
150170

151171
/**
152-
Looks to see if the entry is in the keywords base.
172+
Looks to see if the entry is in the base keywords.
153173
**/
154174
const shouldInclude = (keywords, entry) => {
155175
// Don't include ignored names
@@ -159,7 +179,7 @@ const shouldInclude = (keywords, entry) => {
159179

160180
// See if entry is in the keywords base
161181
for (let i = 0; i < keywords.length; i++) {
162-
if (keywords[i][0] == entry.clean && keywords[i][2] == entry.basename) {
182+
if (keywords[i][0] == entry.leftName && keywords[i][2] == entry.basename) {
163183
return false;
164184
}
165185
}
@@ -168,9 +188,12 @@ const shouldInclude = (keywords, entry) => {
168188
return true;
169189
};
170190

191+
/**
192+
Parse a keywords file into an array of arrays
193+
**/
171194
const parseKeywordsFile = (filename) => {
172-
const txt = fs.readFileSync(path.join(__dirname, filename)).toString();
173-
const lines = txt.split('\n');
195+
const content = fs.readFileSync(path.join(__dirname, filename)).toString();
196+
const lines = content.split('\n');
174197
const keywords = [];
175198

176199
for (let i = 0; i < lines.length; i++) {
@@ -180,7 +203,15 @@ const parseKeywordsFile = (filename) => {
180203
}
181204
}
182205

183-
return keywords;
206+
return [keywords, content];
207+
};
208+
209+
const generateKeywordsFile = (keywords) => {
210+
let str = '';
211+
for (let i = 0; i < keywords.length; i++) {
212+
str += `${keywords[i][0]}\t${keywords[i][1]}\t${keywords[i][2]}\n`;
213+
}
214+
return str;
184215
};
185216

186217
/**
@@ -195,36 +226,61 @@ const specialCases = {
195226
/**
196227
Makes the clean version of the name for the left-hand side of the file
197228
**/
198-
const cleanName = (entry) => {
199-
// Constants, classes and primitives are just using the name
200-
if (
201-
entry.type === 'class' ||
202-
entry.category === 'constants' ||
203-
(entry.category === 'Data' && entry.subcategory === 'Primitive')
204-
) {
205-
return entry.name;
206-
}
207-
229+
const operatorRegex = /\s\([a-zA-Z\s]+\)$/;
230+
const leftName = (entry) => {
208231
// Special handling
209232
if (specialCases[entry.name]) {
210233
return specialCases[entry.name];
211234
}
212235

213236
// Operators are renamed: += (add assign) => +=
214-
if (
215-
entry.subcategory === 'Operators' ||
216-
entry.subcategory == 'Bitwise Operators' ||
217-
entry.subcategory == 'Relational Operators' ||
218-
entry.subcategory == 'Logical Operators' ||
219-
(entry.category === 'structure' && entry.subcategory === '')
220-
) {
221-
return entry.name.replace(/\([a-zA-Z\s]+\)$/, '').trim();
237+
if (entry.name.match(operatorRegex)) {
238+
return entry.name.replace(operatorRegex, '');
222239
}
223240

224241
// Functions and class methods are renamed: float() => float
225242
if (entry.type === 'function' || entry.type === 'method') {
226243
return entry.name.replace(/\(\)$/, '');
227244
}
245+
246+
// Variables are renamed: pixels[] => pixels
247+
if (entry.type === 'field' || entry.type === 'other') {
248+
return entry.name.replace(/\[\]$/, '');
249+
}
250+
251+
// Just use name (Constants, classes, primitives, etc)
252+
return entry.name;
253+
};
254+
255+
/**
256+
Make a name suitable for the right hand side
257+
**/
258+
const rightName = (entry) => {
259+
// global functions appear as just the name in the right
260+
if (globalFuncs.includes(entry.name)) {
261+
return entry.name.replace(/\(\)$/, '');
262+
}
263+
264+
// Everything else uses the basename (fill_, PVector_add_, etc)
265+
return entry.basename;
266+
};
267+
268+
/**
269+
Gets the syntax category. Based on the old pearl script.
270+
**/
271+
const getToken = (entry) => {
272+
if (globalFuncs.includes(entry.name)) {
273+
return 'FUNCTION4';
274+
} else if (entry.type === 'class') {
275+
return 'KEYWORD5';
276+
} else if (entry.type === 'method') {
277+
return 'FUNCTION2';
278+
} else if (entry.type === 'other') {
279+
return 'KEYWORD4';
280+
} else if (entry.type === 'field') {
281+
return 'KEYWORD2';
282+
}
283+
return 'FUNCTION1';
228284
};
229285

230286
createKeywords();

0 commit comments

Comments
 (0)