const TokenStream = require('./tokenStream') const tokenTypes = require('./tokenTypes') module.exports = class Lexer { scan(source) { let pos = 0 let line = 1 let tokenStream = new TokenStream() let allowSpecialCharactersInLiterals = false while (pos < source.length) { if (source[pos].match(/\(/) && !allowSpecialCharactersInLiterals) { tokenStream.tokens.push({ type: tokenTypes.OPAREN, line: line, }) pos++ } else if (source[pos].match(/\)/)) { tokenStream.tokens.push({ type: tokenTypes.CPAREN, line: line, }) pos++ } else if (source[pos].match(/["]/)) { allowSpecialCharactersInLiterals = !allowSpecialCharactersInLiterals tokenStream.tokens.push({ type: tokenTypes.QUOTE, line: line, }) pos++ } else if (source[pos].match(/:/)) { let value = /:([^()'"\s]+)/.exec(source.slice(pos))[1].trim() tokenStream.tokens.push({ type: tokenTypes.ATTRIBUTE, line: line, value: value, }) pos += value.length + 1 // the +1 is to account for the colon } else if (source[pos].match(/\'/)) { let value = /'([^()"\s]+)/.exec(source.slice(pos))[1].trim() tokenStream.tokens.push({ type: tokenTypes.SYMBOL, line: line, value: value, }) pos += value.length + 1 // the +1 is to account for the apostrophe } else if (source[pos].match(/\d/)) { let number = '' while (source[pos] && source[pos].match(/\d/)) { number += source[pos] pos++ } tokenStream.tokens.push({ type: tokenTypes.NUMBER, line: line, value: parseFloat(number), }) } else if (source.slice(pos).match(/^#(t|f)/)) { pos++ tokenStream.tokens.push({ type: tokenTypes.BOOLEAN, line: line, value: source[pos] === 't' ? true : false, }) pos++ } else if (source[pos].match(/\n/)) { line++ pos++ } else if (source[pos].match(/\s/)) { pos++ } else { let endPattern = /[^()"':\s]+/ if (allowSpecialCharactersInLiterals) { endPattern = /[^"']+/ } let value = endPattern.exec(source.slice(pos))[0].trim() if (allowSpecialCharactersInLiterals) { tokenStream.tokens.push({ type: tokenTypes.LITERAL, line: line, value: value, }) } else { tokenStream.tokens.push({ type: tokenTypes.IDENTIFIER, line: line, value: value.trim(), }) } pos += value.length } } tokenStream.tokens.push({ type: tokenTypes.EOF, line: line, }) return tokenStream } }