%lex %% \n+ return 'NEWLINE'; \s+ /* skip */; '=' return '='; '{' return '{'; '}' return '}'; \-?[0-9]+ return 'NUMBER'; 'circle' return 'CIRCLE'; 'square' return 'SQUARE'; 'resize' return 'RESIZE'; 'move' return 'MOVE'; 'animate' return 'ANIMATE'; 'rotate' return 'ROTATE'; 'origin' return 'ORIGIN'; [a-z]+ return 'COLOR_NAME'; [A-Z]+ return 'SHAPE_NAME'; /lex %start shapes %% shapes : statements { console.log('parsed successfully!'); } ; statements : statement | statements 'NEWLINE' statement ; statement : /* empty */ | shape_declaration | shape_move | shape_resize | shape_animate ; shape_declaration : 'SHAPE_NAME' '=' shape_definition { $3.id = $1; document.body.append($3); } ; shape_definition : 'COLOR_NAME' shape { $$ = document.createElement('div'); $$.style.width = $$.style.height = '100px'; $$.style.position = 'absolute'; $$.style.backgroundColor = $1; $$.style.transition = 'transform 1s ease-in-out, width 1s ease-in-out, height 1s ease-in-out'; $$.style.transform = 'none'; if ($2 === 'circle') { $$.style.borderRadius = '50%'; } } ; shape : 'CIRCLE' | 'SQUARE' ; shape_move : 'MOVE' 'SHAPE_NAME' 'NUMBER' 'NUMBER' { { const shape = document.getElementById($2); shape.style.left = $3; shape.style.top = $4; } } ; shape_resize : 'RESIZE' 'SHAPE_NAME' 'NUMBER' { { const shape = document.getElementById($2); shape.style.width = shape.style.height = $3; } } ; shape_animate : 'ANIMATE' 'SHAPE_NAME' animation_block { { const shape = document.getElementById($2); let isInitialState = true; const { width, height, transform } = shape.style; setInterval(() => { Object.assign(shape.style, isInitialState ? $3 : { width, height, transform }); isInitialState = !isInitialState; }, 1000); } } ; animation_block : '{' animations '}' { $$ = $2; } | '{' 'NEWLINE' animations 'NEWLINE' '}' { $$ = $3; } ; animations : animation { $$ = $1 } | animations 'NEWLINE' animation { { const lTransform = $1.transform || ''; const rTransform = $3.transform || ''; $$ = { ...$1, ...$3, transform: `${lTransform} ${rTransform}` }; } } ; animation : 'MOVE' 'NUMBER' 'NUMBER' { $$ = { transform: `translate(${$2}px, ${$3}px)` }; } | 'RESIZE' 'NUMBER' { $$ = { width: `${$2}px`, height: `${$2}px` }; } | 'ROTATE' 'NUMBER' origin { $$ = { transform: `rotate(${$2}deg)`, transformOrigin: $3 || 'center' }; } ; origin : /* empty */ | 'ORIGIN' 'NUMBER' 'NUMBER' { $$ = `${$2}px ${$3}px`;} ;