Skip to content

Commit de2b575

Browse files
committed
Initial commit
1 parent 8a55f09 commit de2b575

File tree

9 files changed

+346
-0
lines changed

9 files changed

+346
-0
lines changed

.babelrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"presets": [
3+
"es2015",
4+
"stage-0"
5+
],
6+
"plugins": [
7+
"transform-flow-strip-types"
8+
]
9+
}

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/__tests__/

.eslintrc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "airbnb/base",
3+
"parser": "babel-eslint",
4+
"plugins": [
5+
"flowtype"
6+
],
7+
"rules": {
8+
"no-param-reassign": ["error", { "props": false }],
9+
"no-use-before-define": "off",
10+
"flowtype/space-before-type-colon": ["warn", "never"],
11+
}
12+
}

.flowconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[ignore]
2+
3+
[include]
4+
5+
[libs]
6+
7+
[options]

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
ENV
3+
4+
# Elastic Beanstalk Files
5+
.elasticbeanstalk/*
6+
!.elasticbeanstalk/*.cfg.yml
7+
!.elasticbeanstalk/*.global.yml

package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "another-selector-parser",
3+
"version": "0.0.1",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "NODE_ENV=test ./node_modules/.bin/jest"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "clentfort/another-selector-parser.git"
12+
},
13+
"keywords": [
14+
"css",
15+
"selector",
16+
"parser",
17+
"css",
18+
"selector",
19+
"parser",
20+
"css",
21+
"parser",
22+
"selector",
23+
"parser"
24+
],
25+
"author": "Christian Lentfort @_chris_lent_",
26+
"license": "MIT",
27+
"devDependencies": {
28+
"babel": "^6.5.2",
29+
"babel-jest": "^10.0.2",
30+
"babel-plugin-transform-flow-strip-types": "^6.7.0",
31+
"babel-polyfill": "^6.7.4",
32+
"babel-preset-es2015": "^6.6.0",
33+
"babel-preset-stage-0": "^6.5.0",
34+
"eslint": "^2.7.0",
35+
"eslint-config-airbnb": "^6.2.0",
36+
"eslint-plugin-flowtype": "^2.2.6",
37+
"jest-cli": "^0.10.2"
38+
},
39+
"jest": {
40+
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
41+
"testFileExtensions": [
42+
"js"
43+
],
44+
"moduleFileExtensions": [
45+
"js",
46+
"json"
47+
]
48+
}
49+
}

src/tokenizer/__tests__/index.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
jest.unmock('../');
2+
jest.unmock('../types');
3+
4+
import tokenize from '../';
5+
import { types as tt } from '../types';
6+
7+
describe('tokenize', () => {
8+
it('returns EOF for empty input', () => {
9+
const { value, done } = tokenize('').next();
10+
expect(done).toBe(true);
11+
expect(value).toEqual({ type: tt.eof });
12+
});
13+
14+
it('is down after returning EOF', () => {
15+
const tokenizer = tokenize('');
16+
tokenizer.next();
17+
expect(tokenizer.next().done).toBe(true);
18+
});
19+
20+
it('ignores any comments', () => {
21+
let { value, done } = tokenize('/*abc*/').next();
22+
expect(done).toBe(true);
23+
expect(value).toEqual({ type: tt.eof });
24+
});
25+
});

src/tokenizer/index.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/* @flow */
2+
import { types as tt } from './types';
3+
4+
import type { TokenType } from './types';
5+
6+
type Token = {
7+
type: TokenType;
8+
value?: any;
9+
};
10+
11+
export default function *tokenize(input: string): Generator<Token, Token, void> {
12+
// Current position in the input string
13+
let position = 0;
14+
15+
while (position < input.length) {
16+
// Current char we are looking at
17+
let currentChar = input[position];
18+
19+
let wasWhitespace = false;
20+
if (isWhitespace(currentChar)) {
21+
currentChar = input[++position];
22+
while (isWhitespace(currentChar)) {
23+
currentChar = input[++position];
24+
}
25+
yield { type: tt.whitespace };
26+
// No continue needed here since we consumed all whitespace and a guaranteed to find something meaningful now
27+
}
28+
29+
if (currentChar === '/') {
30+
currentChar = input[++position];
31+
if (currentChar === '*') {
32+
// Inside a comment
33+
console.log('Inside a comment');
34+
currentChar = input[++position];
35+
let nextChar = input[position + 1];
36+
console.log('Current Char', currentChar);
37+
console.log('Next Char', nextChar);
38+
console.log('Start while');
39+
while (!(currentChar === '*' && nextChar === '/')) {
40+
currentChar = input[++position];
41+
nextChar = input[position + 1];
42+
console.log('Current Char', currentChar);
43+
console.log('Next Char', nextChar);
44+
}
45+
console.log('End while');
46+
currentChar = input[position + 2];
47+
position += 2;
48+
console.log('Current Char', currentChar);
49+
} else {
50+
throw new UnexpectedCharacterError('/', currentChar);
51+
}
52+
}
53+
54+
if (currentChar === '[') {
55+
yield { type: tt.bracketL };
56+
++position;
57+
continue;
58+
}
59+
60+
if (currentChar === ']') {
61+
yield { type: tt.bracketR };
62+
++position;
63+
continue;
64+
}
65+
66+
if (currentChar === ':') {
67+
yield { type: tt.colon };
68+
++position;
69+
continue;
70+
}
71+
72+
if (currentChar === ',') {
73+
yield { type: tt.comma };
74+
++position;
75+
continue;
76+
}
77+
78+
if (currentChar === '.') {
79+
yield { type: tt.dot };
80+
++position;
81+
continue;
82+
}
83+
84+
if (currentChar === '>') {
85+
yield { type: tt.greater };
86+
++position;
87+
continue;
88+
}
89+
90+
if (currentChar === '#') {
91+
yield { type: tt.hash };
92+
++position;
93+
continue;
94+
}
95+
96+
if (currentChar === '(') {
97+
yield { type: tt.parenL };
98+
++position;
99+
continue;
100+
}
101+
102+
if (currentChar === ')') {
103+
yield { type: tt.parenR };
104+
++position;
105+
continue;
106+
}
107+
108+
if (currentChar === '%') {
109+
yield { type: tt.percentage };
110+
++position;
111+
continue;
112+
}
113+
114+
if (currentChar === '+') {
115+
yield { type: tt.plus };
116+
++position;
117+
continue;
118+
}
119+
120+
if (currentChar === '~') {
121+
yield { type: tt.tilde };
122+
++position;
123+
continue;
124+
}
125+
126+
if (currentChar === '|') {
127+
currentChar= input[++position];
128+
if (currentChar === '=') {
129+
yield { type: tt.dashmatch };
130+
++position;
131+
continue;
132+
} else {
133+
throw new UnexpectedCharacterError(currentChar, '=');
134+
}
135+
}
136+
137+
if (currentChar === '~') {
138+
currentChar= input[++position];
139+
if (currentChar === '=') {
140+
yield { type: tt.includes };
141+
++position;
142+
continue;
143+
} else {
144+
throw new UnexpectedCharacterError(currentChar, '=');
145+
}
146+
}
147+
148+
if (currentChar === '^') {
149+
currentChar= input[++position];
150+
if (currentChar === '=') {
151+
yield { type: tt.prefixmatch };
152+
++position;
153+
continue;
154+
} else {
155+
throw new UnexpectedCharacterError(currentChar, '=');
156+
}
157+
}
158+
159+
if (currentChar === '*') {
160+
currentChar= input[++position];
161+
if (currentChar === '=') {
162+
yield { type: tt.substringmatch };
163+
++position;
164+
continue;
165+
} else {
166+
throw new UnexpectedCharacterError(currentChar, '=');
167+
}
168+
}
169+
170+
if (currentChar === '$') {
171+
currentChar= input[++position];
172+
if (currentChar === '=') {
173+
yield { type: tt.suffixmatch };
174+
++position;
175+
continue;
176+
} else {
177+
throw new UnexpectedCharacterError(currentChar, '=');
178+
}
179+
}
180+
181+
}
182+
183+
return {
184+
type: tt.eof,
185+
};
186+
}
187+
188+
function isWhitespace(char: string) {
189+
return (char === ' ' || char === "\t" || char === "\r" || char === "\n" || char === "\f");
190+
}
191+
192+
193+
class UnexpectedCharacterError extends Error {
194+
constructor(actual, expected) {
195+
super(`Unexpected char "${actual}", expected "${expected}".`);
196+
}
197+
}
198+
199+
class UnexpectedEofError extends Error {
200+
constructor(expected) {
201+
super(`Unexpected end of input, expected "${expected}".`);
202+
}
203+
}

src/tokenizer/types.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* @flow */
2+
export class TokenType {
3+
label: string;
4+
5+
constructor(label: string) {
6+
this.label = label;
7+
}
8+
}
9+
10+
export const types = {
11+
bracketL: new TokenType('['),
12+
bracketR: new TokenType(']'),
13+
colon: new TokenType(':'),
14+
comma: new TokenType(','),
15+
dashmatch: new TokenType('|='),
16+
dot: new TokenType('.'),
17+
greater: new TokenType('>'),
18+
hash: new TokenType('#'),
19+
ident: new TokenType('ident'),
20+
includes: new TokenType('~='),
21+
num: new TokenType('num'),
22+
parenL: new TokenType('('),
23+
parenR: new TokenType(')'),
24+
percentage: new TokenType('%'),
25+
plus: new TokenType('+'),
26+
prefixmatch: new TokenType('^='),
27+
string: new TokenType('string'),
28+
substringmatch: new TokenType('*='),
29+
suffixmatch: new TokenType('$='),
30+
tilde: new TokenType('~'),
31+
whitespace: new TokenType('whitespace'),
32+
eof: new TokenType('EOF'),
33+
};

0 commit comments

Comments
 (0)