From fc7e6e6827c3e9f2a29ec1c6ec957bab9f195841 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Nov 2016 19:33:17 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + compiler/generate/index.js | 0 compiler/index.js | 2 + compiler/parse/__test__.js | 35 ++++++++++ compiler/parse/index.js | 134 +++++++++++++++++++++++++++++++++++++ mocha.opts | 1 + package.json | 32 +++++++++ 7 files changed, 206 insertions(+) create mode 100644 .gitignore create mode 100644 compiler/generate/index.js create mode 100644 compiler/index.js create mode 100644 compiler/parse/__test__.js create mode 100644 compiler/parse/index.js create mode 100644 mocha.opts create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..9daa8247da --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules diff --git a/compiler/generate/index.js b/compiler/generate/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/compiler/index.js b/compiler/index.js new file mode 100644 index 0000000000..773d68506b --- /dev/null +++ b/compiler/index.js @@ -0,0 +1,2 @@ +export { default as parse } from './parse/index.js'; +export { default as generate } from './generate/index.js'; diff --git a/compiler/parse/__test__.js b/compiler/parse/__test__.js new file mode 100644 index 0000000000..8ab9ce573a --- /dev/null +++ b/compiler/parse/__test__.js @@ -0,0 +1,35 @@ +import * as assert from 'assert'; +import parse from './index.js'; + +describe( 'parse', () => { + it( 'is a function', () => { + assert.equal( typeof parse, 'function' ); + }); + + it( 'parses a single element', () => { + const template = `test`; + + assert.deepEqual( parse( template ), { + start: 0, + end: 17, + type: 'Fragment', + children: [ + { + start: 0, + end: 17, + type: 'Element', + name: 'span', + attributes: {}, + children: [ + { + start: 6, + end: 10, + type: 'Text', + data: 'test' + } + ] + } + ] + }); + }); +}); diff --git a/compiler/parse/index.js b/compiler/parse/index.js new file mode 100644 index 0000000000..79853d968f --- /dev/null +++ b/compiler/parse/index.js @@ -0,0 +1,134 @@ +import { locate } from 'locate-character'; + +const validNameChar = /[a-zA-Z0-9_$]/; + +export default function parse ( template ) { + let i = 0; + + const root = { + start: 0, + end: template.length, + type: 'Fragment', + children: [] + }; + + const stack = [ root ]; + let current = root; + + function error ( message ) { + const { line, column } = locate( template, i ); + throw new Error( `${message} (${line}:${column})` ); + } + + function match ( str ) { + return template.slice( i, i + str.length ) === str; + } + + function fragment () { + const char = template[i]; + + while ( char === ' ' ) { + i += 1; + } + + if ( char === '<' ) { + return tag; + } + + if ( match( '{{' ) ) { + return mustache; + } + + return text; + } + + function tag () { + const start = i++; + let char = template[ i ]; + + const isClosingTag = char === '/'; + + if ( isClosingTag ) { + // this is a closing tag + i += 1; + char = template[ i ]; + } + + // TODO handle cases like
  • one
  • two + + let name = ''; + + while ( validNameChar.test( char ) ) { + name += char; + i += 1; + char = template[i]; + } + + if ( isClosingTag ) { + if ( char !== '>' ) error( `Expected '>'` ); + + i += 1; + current.end = i; + stack.pop(); + current = stack[ stack.length - 1 ]; + + return fragment; + } + + const element = { + start, + end: null, // filled in later + type: 'Element', + name, + attributes: {}, + children: [] + }; + + current.children.push( element ); + stack.push( element ); + + current = element; + + if ( char === '>' ) { + i += 1; + return fragment; + } + + return attributes; + } + + function text () { + const start = i; + + let data = ''; + + while ( i < template.length && template[i] !== '<' && !match( '{{' ) ) { + data += template[ i++ ]; + } + + current.children.push({ + start, + end: i, + type: 'Text', + data + }); + + return fragment; + } + + function attributes () { + const char = template[i]; + if ( char === '>' ) { + i += 1; + return fragment; + } + } + + let state = fragment; + + while ( i < template.length ) { + state = state(); + } + + return root; +} diff --git a/mocha.opts b/mocha.opts new file mode 100644 index 0000000000..ef3568a0f4 --- /dev/null +++ b/mocha.opts @@ -0,0 +1 @@ +--require reify diff --git a/package.json b/package.json new file mode 100644 index 0000000000..1675e4e9b5 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "svelte", + "version": "1.0.0", + "description": "The frameworkless UI framework", + "main": "dist/svelte-compiler.js", + "scripts": { + "test": "mocha --opts mocha.opts --recursive ./**/__test__.js" + }, + "repository": { + "type": "git", + "url": "git+https://gitlab.com/Rich-Harris/svelte.git" + }, + "keywords": [ + "UI", + "framework", + "templates", + "templating" + ], + "author": "Rich Harris", + "license": "MIT", + "bugs": { + "url": "https://gitlab.com/Rich-Harris/svelte/issues" + }, + "homepage": "https://gitlab.com/Rich-Harris/svelte#README", + "devDependencies": { + "mocha": "^3.1.2", + "reify": "^0.4.0" + }, + "dependencies": { + "locate-character": "^2.0.0" + } +}