-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlexer.js
More file actions
115 lines (96 loc) · 4.07 KB
/
lexer.js
File metadata and controls
115 lines (96 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import INSTRUCTIONS from './data/instructions'
import { BLANK, BLOCK_START, BLOCK_END, COMMENT } from './data/chars'
// Class Lexer is the part that transforms a string containing code into a list of instructions.
// This list is an array that stores objects, with different values :
// - type: the type of the instruction (Name, Meta, Hold...)
// - value: if the instruction requires a string as a parameter, it'll be stored inside this prop
// - params: if the instruction requires different parameters, they'll be stored inside this prop as an array
// - instructions: if the instruction is a block, contains the list of sub-instructions with the same format as described
class Lexer {
// readCode reads a raw string containing the code and returns the list of instructions
readCode(code) {
this.line = -1
this.lines = code.split('\n')
return this.readProgram()
}
// readProgram reads a list of instructions, in a block or in the main stream
readProgram(block = null) {
let instructions = []
let inBlock = block !== null
while (true) {
let line = this.nextLine()
// Test if we reached the end of the file
if (this.line === this.lines.length) {
if (inBlock) this.croak('Reached end of file without closing block. Review all the blocks you created and check if you closed them using the ")" statement.')
else break
}
line = line.trim()
if (inBlock && line === BLOCK_END) break
if (line === BLANK) continue
let instruction = this.readInstruction(line, block)
if (instruction !== COMMENT) instructions.push(instruction)
}
return instructions
}
// readInstruction reads and returns an instruction
readInstruction(line, block) {
let params = line.split(' ')
// Omitting comments, so lines that start with an # character
if (params[0].charAt(0) === COMMENT) return COMMENT
let type = params[0]
params.shift()
// If we're inside an arbitrary block such as Meta, return all the instructions as strings without checking in the language definition
if (block !== null) {
let block_instruction = INSTRUCTIONS.find(i => i.name === block)
if (block_instruction.arbitrary) return this.readStringInstruction(type, params)
}
let instruction = INSTRUCTIONS.find(i => i.name === type)
if (instruction === undefined)
this.croak(`Unknown instruction ${type}. Check the documentation for a complete list of instructions.`)
if (instruction.depends !== undefined && instruction.depends !== block)
this.croak(`Instruction ${instruction.name} depends on a ${instruction.depends} block. Try wrap it inside this block.`)
switch (instruction.type) {
case 'string': return this.readStringInstruction(type, params)
case 'params': return this.readParamsInstruction(type, params)
case 'block': return this.readBlockInstruction(type, params)
}
}
// readStringInstruction returns the object corresponding to a string instruction
readStringInstruction(type, args) {
return {
type,
line: this.line + 1,
value: args.join(' ')
}
}
// readStringInstruction returns the object corresponding to a params instruction
readParamsInstruction(type, args) {
return {
type,
line: this.line + 1,
params: args
}
}
// readStringInstruction returns the object corresponding to a block instruction
// All the instructions inside this block pass through the regular process of readProgram. This allow for multiple block nesting
readBlockInstruction(type, args) {
let start = args.pop()
if (start !== BLOCK_START) this.croak('Excepted a block start with the "(" character')
return {
type,
line: this.line + 1,
params: args,
instructions: this.readProgram(type)
}
}
// nextLine passes to the next code line and returns the current line value
nextLine() {
this.line++
return this.lines[this.line]
}
// croak throws an error
croak(message) {
throw new Error(`Error in parsing Slope code at line ${this.line + 1} : ${message}`)
}
}
export default Lexer