From dc92c18e1dffd4acbab69e76c4bcda238f52da27 Mon Sep 17 00:00:00 2001 From: Kristine Date: Thu, 1 Dec 2016 16:31:32 -0800 Subject: [PATCH 001/286] Update sourceComments documentation in README.md (#1820) Update sourceComments documentation in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f97b5f953..595688fac 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ Used to determine how many digits after the decimal will be allowed. For instanc Type: `Boolean` Default: `false` -`true` enables additional debugging information in the output file as CSS comments +`true` Enables the line number and file where a selector is defined to be emitted into the compiled CSS as a comment. Useful for debugging, especially when using imports and mixins. ### sourceMap Type: `Boolean | String | undefined` @@ -351,7 +351,7 @@ var result = sass.renderSync({ data: 'body{background:blue; a{color:black;}}', outputStyle: 'compressed', outFile: '/to/my/output.css', - sourceMap: true, // or an absolute or relative (to outFile) path + sourceMap: true, // or an absolute or relative (to outFile) path importer: function(url, prev, done) { // url is the path in import as is, which LibSass encountered. // prev is the previously resolved path. From a7e6beadfb48deb7473eb259258a0997a6190016 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Dec 2016 15:04:19 +1100 Subject: [PATCH 002/286] Bump sass-spec@^3.3.6-5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b561d9b2a..911c3484e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,6 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "^3.3.6-3" + "sass-spec": "^3.3.6-5" } } From 4491ab436ee217038460a46f14c15c0368dda456 Mon Sep 17 00:00:00 2001 From: Tri Nguyen Date: Fri, 9 Dec 2016 23:18:19 -0500 Subject: [PATCH 003/286] Check options to be an object (#1825) --- lib/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/index.js b/lib/index.js index cd747dffc..4d880a8f5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -182,6 +182,9 @@ function buildIncludePaths(options) { */ function getOptions(opts, cb) { + if (typeof opts !== 'object') { + throw new Error('Invalid: options is not an object.'); + } var options = clonedeep(opts || {}); options.sourceComments = options.sourceComments || false; From b527d913793cc477f633b03108e5c881bf12c0f2 Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Sat, 10 Dec 2016 13:30:31 +0900 Subject: [PATCH 004/286] Removed top-level return statement from cli (#1827) --- bin/node-sass | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index 574593a61..38cb69195 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -428,5 +428,3 @@ if (options.src) { run(options, emitter); }); } - -return emitter; From 1bb893f386fc9751e317fa8b96f4dbd3c2623534 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Dec 2016 17:11:36 +1100 Subject: [PATCH 005/286] 3.13.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 911c3484e..bfff478c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "3.13.0", + "version": "3.13.1", "libsass": "3.3.6", "description": "Wrapper around libsass", "license": "MIT", From 65321899b750b87c1b280b013ed9e6b098d1ccbf Mon Sep 17 00:00:00 2001 From: xzyfer Date: Mon, 14 Nov 2016 22:45:10 +1100 Subject: [PATCH 006/286] Bump LibSass to 3.4.0 https://github.com/sass/libsass/releases/tag/3.4.0 --- package.json | 4 ++-- src/libsass | 2 +- src/libsass.gyp | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bfff478c4..a32795252 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "3.13.1", - "libsass": "3.3.6", + "libsass": "3.4.0", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -79,6 +79,6 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "^3.3.6-5" + "sass-spec": "^3.4.0" } } diff --git a/src/libsass b/src/libsass index 3ae9a2066..5b92405db 160000 --- a/src/libsass +++ b/src/libsass @@ -1 +1 @@ -Subproject commit 3ae9a2066152f9438aebaaacd12f39deaceaebc2 +Subproject commit 5b92405db76df2acf620fbaf37e3d1c3662af720 diff --git a/src/libsass.gyp b/src/libsass.gyp index fe22917ed..ce5729e0b 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -15,6 +15,7 @@ 'libsass/src/base64vlq.cpp', 'libsass/src/bind.cpp', 'libsass/src/cencode.c', + 'libsass/src/check_nesting.cpp', 'libsass/src/color_maps.cpp', 'libsass/src/constants.cpp', 'libsass/src/context.cpp', From 79e86f32ce3661569748164d26e2f2667a79699a Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 15 Nov 2016 00:51:04 +1100 Subject: [PATCH 007/286] Tweak spec.js to support :only_on and multiple :todo engines --- package.json | 2 ++ test/spec.js | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a32795252..d6dd17b88 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "in-publish": "^2.0.0", "lodash.assign": "^4.2.0", "lodash.clonedeep": "^4.3.2", + "lodash.isarray": "^4.0.0", + "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.3.2", diff --git a/test/spec.js b/test/spec.js index 9d7fee953..03d111fd0 100644 --- a/test/spec.js +++ b/test/spec.js @@ -7,7 +7,9 @@ var assert = require('assert'), ? require('../lib-cov') : require('../lib'), readYaml = require('read-yaml'), - objectMerge = require('object-merge'), + mergeWith = require('lodash.mergewith'), + assign = require('lodash.assign'), + isArray = require('lodash.isarray'), glob = require('glob'), specPath = require('sass-spec').dirname.replace(/\\/g, '/'), impl = 'libsass', @@ -32,7 +34,7 @@ var initialize = function(inputCss, options) { testCase.statusPath = join(folder, 'status'); testCase.optionsPath = join(folder, 'options.yml'); if (exists(testCase.optionsPath)) { - options = objectMerge(options, readYaml.sync(testCase.optionsPath)); + options = mergeWith(assign({}, options), readYaml.sync(testCase.optionsPath), customizer); } testCase.includePaths = [ folder, @@ -41,6 +43,7 @@ var initialize = function(inputCss, options) { testCase.precision = parseFloat(options[':precision']) || 5; testCase.outputStyle = options[':output_style'] ? options[':output_style'].replace(':', '') : 'nested'; testCase.todo = options[':todo'] !== undefined && options[':todo'] !== null && options[':todo'].indexOf(impl) !== -1; + testCase.only = options[':only_on'] !== undefined && options[':only_on'] !== null && options[':only_on']; testCase.warningTodo = options[':warning_todo'] !== undefined && options[':warning_todo'] !== null && options[':warning_todo'].indexOf(impl) !== -1; testCase.startVersion = parseFloat(options[':start_version']) || 0; testCase.endVersion = parseFloat(options[':end_version']) || 99; @@ -59,6 +62,8 @@ var runTest = function(inputCssPath, options) { it(test.name, function(done) { if (test.todo || test.warningTodo) { this.skip('Test marked with TODO'); + } else if (test.only && test.only.indexOf(impl) === -1) { + this.skip('Tests marked for only: ' + test.only.join(', ')); } else if (version < test.startVersion) { this.skip('Tests marked for newer Sass versions only'); } else if (version > test.endVersion) { @@ -109,11 +114,18 @@ var specSuite = { suites: [], options: {} }; + +function customizer(objValue, srcValue) { + if (isArray(objValue)) { + return objValue.concat(srcValue); + } +} + var executeSuite = function(suite, tests) { var suiteFolderLength = suite.folder.split('/').length; var optionsFile = join(suite.folder, 'options.yml'); if (exists(optionsFile)) { - suite.options = objectMerge(suite.options, readYaml.sync(optionsFile)); + suite.options = mergeWith(assign({}, suite.options), readYaml.sync(optionsFile), customizer); } // Push tests in the current suite @@ -143,7 +155,7 @@ var executeSuite = function(suite, tests) { folder: suite.folder + '/' + prevSuite, tests: [], suites: [], - options: suite.options + options: assign({}, suite.options), }, tests.slice(prevSuiteStart, i) ) @@ -159,7 +171,7 @@ var executeSuite = function(suite, tests) { folder: suite.folder + '/' + suiteName, tests: [], suites: [], - options: suite.options + options: assign({}, suite.options), }, tests.slice(prevSuiteStart, tests.length) ) From dd946fdb6838c72f4652741105282b7747b5e5f2 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 15 Nov 2016 00:57:24 +1100 Subject: [PATCH 008/286] Update sourcemap test fixtures to match new LibSass behaviour --- test/fixtures/source-map-embed/expected.css | 2 +- test/fixtures/source-map/expected.map | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/fixtures/source-map-embed/expected.css b/test/fixtures/source-map-embed/expected.css index 827bc93a5..56f2e59a3 100644 --- a/test/fixtures/source-map-embed/expected.css +++ b/test/fixtures/source-map-embed/expected.css @@ -10,4 +10,4 @@ #navbar li a { font-weight: bold; } -/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibWFwcGluZ3MiOiAiQUFBQSxBQUFBLE9BQU8sQ0FBQztFQUNOLEtBQUssRUFBRSxHQUFJO0VBQ1gsTUFBTSxFQUFFLElBQUssR0FDZDs7QUFFRCxBQUFRLE9BQUQsQ0FBQyxFQUFFLENBQUM7RUFDVCxlQUFlLEVBQUUsSUFBSyxHQUN2Qjs7QUFFRCxBQUFRLE9BQUQsQ0FBQyxFQUFFLENBQUM7RUFDVCxLQUFLLEVBQUUsSUFBSyxHQUtiO0VBTkQsQUFHRSxPQUhLLENBQUMsRUFBRSxDQUdSLENBQUMsQ0FBQztJQUNBLFdBQVcsRUFBRSxJQUFLLEdBQ25CIiwKCSJuYW1lcyI6IFtdCn0= */ +/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUFBLEFBQUEsT0FBTyxDQUFDO0VBQ04sS0FBSyxFQUFFLEdBQUc7RUFDVixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJLEdBS1o7RUFORCxBQUdFLE9BSEssQ0FBQyxFQUFFLENBR1IsQ0FBQyxDQUFDO0lBQ0EsV0FBVyxFQUFFLElBQUksR0FDbEIiCn0= */ diff --git a/test/fixtures/source-map/expected.map b/test/fixtures/source-map/expected.map index ca712367e..bd437653e 100644 --- a/test/fixtures/source-map/expected.map +++ b/test/fixtures/source-map/expected.map @@ -4,6 +4,6 @@ "sources": [ "index.scss" ], - "mappings": "AAAA,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,GAAI;EACX,MAAM,EAAE,IAAK,GACd;;AAED,AAAQ,OAAD,CAAC,EAAE,CAAC;EACT,eAAe,EAAE,IAAK,GACvB;;AAED,AAAQ,OAAD,CAAC,EAAE,CAAC;EACT,KAAK,EAAE,IAAK,GAKb;EAND,AAGE,OAHK,CAAC,EAAE,CAGR,CAAC,CAAC;IACA,WAAW,EAAE,IAAK,GACnB", - "names": [] + "names": [], + "mappings": "AAAA,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI,GACb;;AAED,AAAA,OAAO,CAAC,EAAE,CAAC;EACT,eAAe,EAAE,IAAI,GACtB;;AAED,AAAA,OAAO,CAAC,EAAE,CAAC;EACT,KAAK,EAAE,IAAI,GAKZ;EAND,AAGE,OAHK,CAAC,EAAE,CAGR,CAAC,CAAC;IACA,WAAW,EAAE,IAAI,GAClB" } From 8b46b3926dfa2501cad043a708fc4c7a964a0fea Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Dec 2016 20:27:12 +1100 Subject: [PATCH 009/286] 4.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d6dd17b88..f6ecf252f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "3.13.1", + "version": "4.0.0", "libsass": "3.4.0", "description": "Wrapper around libsass", "license": "MIT", From d66eaee71b204cc77091e80440b7efcbd9f5dbe6 Mon Sep 17 00:00:00 2001 From: Dalton Santos Date: Sat, 10 Dec 2016 14:00:03 -0200 Subject: [PATCH 010/286] Drop dependency lodash.isarray --- package.json | 1 - test/spec.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index f6ecf252f..d63d4ad87 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "in-publish": "^2.0.0", "lodash.assign": "^4.2.0", "lodash.clonedeep": "^4.3.2", - "lodash.isarray": "^4.0.0", "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", diff --git a/test/spec.js b/test/spec.js index 03d111fd0..f672c6185 100644 --- a/test/spec.js +++ b/test/spec.js @@ -9,7 +9,6 @@ var assert = require('assert'), readYaml = require('read-yaml'), mergeWith = require('lodash.mergewith'), assign = require('lodash.assign'), - isArray = require('lodash.isarray'), glob = require('glob'), specPath = require('sass-spec').dirname.replace(/\\/g, '/'), impl = 'libsass', @@ -116,7 +115,7 @@ var specSuite = { }; function customizer(objValue, srcValue) { - if (isArray(objValue)) { + if (Array.isArray(objValue)) { return objValue.concat(srcValue); } } From 3c17b033294da0bf234b4228f0bfd9d5f2a1d19a Mon Sep 17 00:00:00 2001 From: ysugimoto Date: Tue, 13 Dec 2016 20:19:20 +0900 Subject: [PATCH 011/286] Fix output buffer is imcomplete working in child_process (#1834) Fix premature exit before the output stream is fully flushed --- bin/node-sass | 11 ++--------- package.json | 3 ++- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index 38cb69195..066a5b8d9 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -10,6 +10,7 @@ var Emitter = require('events').EventEmitter, glob = require('glob'), sass = require('../lib'), render = require('../lib/render'), + stdout = require('stdout-stream'), stdin = require('get-stdin'), fs = require('fs'); @@ -160,15 +161,7 @@ function getEmitter() { } }); - emitter.on('log', function(data) { - console.log(data); - }); - - emitter.on('done', function() { - if (!options.watch && !options.directory) { - process.exit(); - } - }); + emitter.on('log', stdout.write.bind(stdout)); return emitter; } diff --git a/package.json b/package.json index d63d4ad87..6e609a44c 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "node-gyp": "^3.3.1", "npmlog": "^4.0.0", "request": "^2.61.0", - "sass-graph": "^2.1.1" + "sass-graph": "^2.1.1", + "stdout-stream": "^1.4.0" }, "devDependencies": { "coveralls": "^2.11.8", From 5521bb6b870d0275d7e0e090ea6d7d9e71e17264 Mon Sep 17 00:00:00 2001 From: Lachlan Donald Date: Sun, 18 Dec 2016 18:56:23 +1100 Subject: [PATCH 012/286] Add a platform variant for installing a musl binary (#1836) Add a platform variant for installing a musl binary --- lib/extensions.js | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/extensions.js b/lib/extensions.js index 854efbd5e..7d5fd9e88 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -23,6 +23,7 @@ function getHumanPlatform(platform) { case 'darwin': return 'OS X'; case 'freebsd': return 'FreeBSD'; case 'linux': return 'Linux'; + case 'linux_musl': return 'Linux/musl'; case 'win32': return 'Windows'; default: return false; } @@ -171,7 +172,9 @@ function getArgument(name, args) { */ function getBinaryName() { - var binaryName; + var binaryName, + variant, + platform = process.platform; if (getArgument('--sass-binary-name')) { binaryName = getArgument('--sass-binary-name'); @@ -182,8 +185,13 @@ function getBinaryName() { } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryName) { binaryName = pkg.nodeSassConfig.binaryName; } else { + variant = getPlatformVariant(); + if (variant) { + platform += '_' + variant; + } + binaryName = [ - process.platform, '-', + platform, '-', process.arch, '-', process.versions.modules ].join(''); @@ -256,7 +264,7 @@ function getBinaryPath() { } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) { binaryPath = pkg.nodeSassConfig.binaryPath; } else { - binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_/, '/')); + binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_(?=binding\.node)/, '/')); } return binaryPath; @@ -359,6 +367,36 @@ function getVersionInfo(binding) { ].join(eol); } +/** + * Gets the platform variant, currently either an empty string or 'musl' for Linux/musl platforms. + * + * @api public + */ + +function getPlatformVariant() { + var contents = ''; + + if (process.platform !== 'linux') { + return ''; + } + + try { + contents = fs.readFileSync(process.execPath); + + // Buffer.indexOf was added in v1.5.0 so cast to string for old node + // Delay contents.toStrings because it's expensive + if (!contents.indexOf) { + contents = contents.toString(); + } + + if (contents.indexOf('libc.musl-x86_64.so.1') !== -1) { + return 'musl'; + } + } catch (err) { } // eslint-disable-line no-empty + + return ''; +} + module.exports.hasBinary = hasBinary; module.exports.getBinaryUrl = getBinaryUrl; module.exports.getBinaryName = getBinaryName; From 396ad288c5911c892e51ea976f86d67901f07a1a Mon Sep 17 00:00:00 2001 From: xzyfer Date: Mon, 19 Dec 2016 18:14:11 +1100 Subject: [PATCH 013/286] 4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e609a44c..61aaa76d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.0.0", + "version": "4.1.0", "libsass": "3.4.0", "description": "Wrapper around libsass", "license": "MIT", From 8a8ec65006d4fe086586f9092904548edb3a4000 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 20 Dec 2016 20:38:06 +1100 Subject: [PATCH 014/286] Lock a specific version of sass-spec Sass spec releases match 1-1 with LibSass releases and need to be versioned in sync. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61aaa76d9..98eab6991 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,6 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "^3.4.0" + "sass-spec": "3.4.0" } } From 5c83bb72e22afb26d5c70cabbd3b40d0e029c52a Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 20 Dec 2016 21:20:51 +1100 Subject: [PATCH 015/286] 4.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98eab6991..3a9e79bb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.1.0", + "version": "4.1.1", "libsass": "3.4.0", "description": "Wrapper around libsass", "license": "MIT", From 46266f14794fc472b287873b503a13de38d79fc3 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Sun, 8 Jan 2017 01:56:52 +1100 Subject: [PATCH 016/286] LibSass 3.4.3 (#1848) * Bump LibSass to 3.4.3 * Update sourcemap test fixtures to match new LibSass behaviour * Bump sass-spec@v3.4.3 --- package.json | 4 ++-- src/libsass | 2 +- src/libsass.gyp | 3 ++- test/fixtures/source-map-embed/expected.css | 2 +- test/fixtures/source-map/expected.map | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3a9e79bb9..dbffb3a42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.1.1", - "libsass": "3.4.0", + "libsass": "3.4.3", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -81,6 +81,6 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.4.0" + "sass-spec": "3.4.3" } } diff --git a/src/libsass b/src/libsass index 5b92405db..b28de0127 160000 --- a/src/libsass +++ b/src/libsass @@ -1 +1 @@ -Subproject commit 5b92405db76df2acf620fbaf37e3d1c3662af720 +Subproject commit b28de01270a663b3e89427a18816732c4d573c1d diff --git a/src/libsass.gyp b/src/libsass.gyp index ce5729e0b..b0764ab97 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -32,7 +32,7 @@ 'libsass/src/json.cpp', 'libsass/src/lexer.cpp', 'libsass/src/listize.cpp', - 'libsass/src/memory_manager.cpp', + 'libsass/src/memory/SharedPtr.cpp', 'libsass/src/node.cpp', 'libsass/src/output.cpp', 'libsass/src/parser.cpp', @@ -47,6 +47,7 @@ 'libsass/src/sass_util.cpp', 'libsass/src/sass_values.cpp', 'libsass/src/source_map.cpp', + 'libsass/src/subset_map.cpp', 'libsass/src/to_c.cpp', 'libsass/src/to_value.cpp', 'libsass/src/units.cpp', diff --git a/test/fixtures/source-map-embed/expected.css b/test/fixtures/source-map-embed/expected.css index 56f2e59a3..343a10548 100644 --- a/test/fixtures/source-map-embed/expected.css +++ b/test/fixtures/source-map-embed/expected.css @@ -10,4 +10,4 @@ #navbar li a { font-weight: bold; } -/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUFBLEFBQUEsT0FBTyxDQUFDO0VBQ04sS0FBSyxFQUFFLEdBQUc7RUFDVixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJLEdBS1o7RUFORCxBQUdFLE9BSEssQ0FBQyxFQUFFLENBR1IsQ0FBQyxDQUFDO0lBQ0EsV0FBVyxFQUFFLElBQUksR0FDbEIiCn0= */ +/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUFBLEFBQUEsT0FBTyxDQUFDO0VBQ04sS0FBSyxFQUFFLEdBQUc7RUFDVixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQVEsT0FBRCxDQUFDLEVBQUUsQ0FBQztFQUNULGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQUVELEFBQVEsT0FBRCxDQUFDLEVBQUUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJLEdBS1o7RUFORCxBQUdFLE9BSEssQ0FBQyxFQUFFLENBR1IsQ0FBQyxDQUFDO0lBQ0EsV0FBVyxFQUFFLElBQUksR0FDbEIiCn0= */ diff --git a/test/fixtures/source-map/expected.map b/test/fixtures/source-map/expected.map index bd437653e..f96ab927d 100644 --- a/test/fixtures/source-map/expected.map +++ b/test/fixtures/source-map/expected.map @@ -5,5 +5,5 @@ "index.scss" ], "names": [], - "mappings": "AAAA,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI,GACb;;AAED,AAAA,OAAO,CAAC,EAAE,CAAC;EACT,eAAe,EAAE,IAAI,GACtB;;AAED,AAAA,OAAO,CAAC,EAAE,CAAC;EACT,KAAK,EAAE,IAAI,GAKZ;EAND,AAGE,OAHK,CAAC,EAAE,CAGR,CAAC,CAAC;IACA,WAAW,EAAE,IAAI,GAClB" + "mappings": "AAAA,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI,GACb;;AAED,AAAQ,OAAD,CAAC,EAAE,CAAC;EACT,eAAe,EAAE,IAAI,GACtB;;AAED,AAAQ,OAAD,CAAC,EAAE,CAAC;EACT,KAAK,EAAE,IAAI,GAKZ;EAND,AAGE,OAHK,CAAC,EAAE,CAGR,CAAC,CAAC;IACA,WAAW,EAAE,IAAI,GAClB" } From c4277283dcc629de2c9a31107cf0f22ede7f1f4e Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:08:57 +1100 Subject: [PATCH 017/286] Remove the LibSass git submodule --- .gitmodules | 3 --- src/libsass | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 src/libsass diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c5dea1dc4..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "libsass"] - path = src/libsass - url = git://github.com/sass/libsass.git diff --git a/src/libsass b/src/libsass deleted file mode 160000 index b28de0127..000000000 --- a/src/libsass +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b28de01270a663b3e89427a18816732c4d573c1d From 7667dba19d8efa2c599cace98c59e633fb1cad82 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:23:35 +1100 Subject: [PATCH 018/286] Initial import of LibSass Commit LibSass source directly from the 3.4.3 tag --- src/libsass/.editorconfig | 15 + src/libsass/.gitattributes | 2 + src/libsass/.github/CONTRIBUTING.md | 65 + src/libsass/.github/ISSUE_TEMPLATE.md | 29 + src/libsass/.gitignore | 82 + src/libsass/.travis.yml | 45 + src/libsass/COPYING | 25 + src/libsass/GNUmakefile.am | 88 + src/libsass/INSTALL | 1 + src/libsass/LICENSE | 25 + src/libsass/Makefile | 347 ++ src/libsass/Makefile.conf | 52 + src/libsass/Readme.md | 99 + src/libsass/SECURITY.md | 10 + src/libsass/appveyor.yml | 87 + src/libsass/configure.ac | 138 + src/libsass/contrib/libsass.spec | 66 + src/libsass/contrib/plugin.cpp | 60 + src/libsass/docs/README.md | 20 + src/libsass/docs/api-context-example.md | 45 + src/libsass/docs/api-context-internal.md | 155 + src/libsass/docs/api-context.md | 275 ++ src/libsass/docs/api-doc.md | 182 + src/libsass/docs/api-function-example.md | 67 + src/libsass/docs/api-function-internal.md | 8 + src/libsass/docs/api-function.md | 50 + src/libsass/docs/api-importer-example.md | 112 + src/libsass/docs/api-importer-internal.md | 20 + src/libsass/docs/api-importer.md | 86 + src/libsass/docs/api-value-example.md | 55 + src/libsass/docs/api-value-internal.md | 76 + src/libsass/docs/api-value.md | 152 + src/libsass/docs/build-on-darwin.md | 27 + src/libsass/docs/build-on-gentoo.md | 55 + src/libsass/docs/build-on-windows.md | 139 + src/libsass/docs/build-shared-library.md | 35 + src/libsass/docs/build-with-autotools.md | 78 + src/libsass/docs/build-with-makefiles.md | 68 + src/libsass/docs/build-with-mingw.md | 107 + src/libsass/docs/build-with-visual-studio.md | 90 + src/libsass/docs/build.md | 97 + src/libsass/docs/compatibility-plan.md | 48 + src/libsass/docs/contributing.md | 17 + src/libsass/docs/custom-functions-internal.md | 120 + src/libsass/docs/dev-ast-memory.md | 223 ++ src/libsass/docs/implementations.md | 53 + src/libsass/docs/plugins.md | 47 + src/libsass/docs/setup-environment.md | 68 + src/libsass/docs/source-map-internals.md | 51 + src/libsass/docs/trace.md | 26 + src/libsass/docs/triage.md | 17 + src/libsass/docs/unicode.md | 39 + src/libsass/extconf.rb | 6 + src/libsass/include/sass.h | 15 + src/libsass/include/sass/base.h | 92 + src/libsass/include/sass/context.h | 155 + src/libsass/include/sass/functions.h | 108 + src/libsass/include/sass/values.h | 143 + src/libsass/include/sass/version.h | 12 + src/libsass/include/sass/version.h.in | 12 + src/libsass/include/sass2scss.h | 120 + src/libsass/m4/.gitkeep | 0 src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 | 167 + src/libsass/res/resource.rc | 35 + src/libsass/script/bootstrap | 13 + src/libsass/script/branding | 10 + src/libsass/script/ci-build-libsass | 129 + src/libsass/script/ci-install-compiler | 6 + src/libsass/script/ci-install-deps | 23 + src/libsass/script/ci-report-coverage | 32 + src/libsass/script/spec | 5 + src/libsass/script/tap-driver | 652 ++++ src/libsass/script/tap-runner | 1 + src/libsass/script/test-leaks.pl | 103 + src/libsass/src/GNUmakefile.am | 54 + src/libsass/src/ast.cpp | 2451 +++++++++++++ src/libsass/src/ast.hpp | 3091 +++++++++++++++++ src/libsass/src/ast_def_macros.hpp | 55 + src/libsass/src/ast_fwd_decl.hpp | 390 +++ src/libsass/src/b64/cencode.h | 32 + src/libsass/src/b64/encode.h | 77 + src/libsass/src/backtrace.hpp | 76 + src/libsass/src/base64vlq.cpp | 44 + src/libsass/src/base64vlq.hpp | 30 + src/libsass/src/bind.cpp | 289 ++ src/libsass/src/bind.hpp | 14 + src/libsass/src/c99func.c | 54 + src/libsass/src/cencode.c | 102 + src/libsass/src/check_nesting.cpp | 376 ++ src/libsass/src/check_nesting.hpp | 64 + src/libsass/src/color_maps.cpp | 644 ++++ src/libsass/src/color_maps.hpp | 333 ++ src/libsass/src/constants.cpp | 178 + src/libsass/src/constants.hpp | 180 + src/libsass/src/context.cpp | 863 +++++ src/libsass/src/context.hpp | 145 + src/libsass/src/cssize.cpp | 595 ++++ src/libsass/src/cssize.hpp | 78 + src/libsass/src/debug.hpp | 43 + src/libsass/src/debugger.hpp | 798 +++++ src/libsass/src/emitter.cpp | 273 ++ src/libsass/src/emitter.hpp | 92 + src/libsass/src/environment.cpp | 195 ++ src/libsass/src/environment.hpp | 92 + src/libsass/src/error_handling.cpp | 207 ++ src/libsass/src/error_handling.hpp | 195 ++ src/libsass/src/eval.cpp | 1713 +++++++++ src/libsass/src/eval.hpp | 109 + src/libsass/src/expand.cpp | 789 +++++ src/libsass/src/expand.hpp | 83 + src/libsass/src/extend.cpp | 2122 +++++++++++ src/libsass/src/extend.hpp | 51 + src/libsass/src/file.cpp | 418 +++ src/libsass/src/file.hpp | 122 + src/libsass/src/functions.cpp | 1985 +++++++++++ src/libsass/src/functions.hpp | 195 ++ src/libsass/src/inspect.cpp | 1083 ++++++ src/libsass/src/inspect.hpp | 100 + src/libsass/src/json.cpp | 1436 ++++++++ src/libsass/src/json.hpp | 117 + src/libsass/src/kwd_arg_macros.hpp | 28 + src/libsass/src/lexer.cpp | 173 + src/libsass/src/lexer.hpp | 306 ++ src/libsass/src/listize.cpp | 85 + src/libsass/src/listize.hpp | 35 + src/libsass/src/mapping.hpp | 18 + src/libsass/src/memory/SharedPtr.cpp | 116 + src/libsass/src/memory/SharedPtr.hpp | 202 ++ src/libsass/src/node.cpp | 323 ++ src/libsass/src/node.hpp | 120 + src/libsass/src/operation.hpp | 173 + src/libsass/src/output.cpp | 334 ++ src/libsass/src/output.hpp | 54 + src/libsass/src/parser.cpp | 2776 +++++++++++++++ src/libsass/src/parser.hpp | 365 ++ src/libsass/src/paths.hpp | 71 + src/libsass/src/plugins.cpp | 166 + src/libsass/src/plugins.hpp | 57 + src/libsass/src/position.cpp | 163 + src/libsass/src/position.hpp | 123 + src/libsass/src/prelexer.cpp | 1689 +++++++++ src/libsass/src/prelexer.hpp | 477 +++ src/libsass/src/remove_placeholders.cpp | 85 + src/libsass/src/remove_placeholders.hpp | 39 + src/libsass/src/sass.cpp | 93 + src/libsass/src/sass.hpp | 136 + src/libsass/src/sass2scss.cpp | 864 +++++ src/libsass/src/sass_context.cpp | 761 ++++ src/libsass/src/sass_context.hpp | 130 + src/libsass/src/sass_functions.cpp | 145 + src/libsass/src/sass_functions.hpp | 32 + src/libsass/src/sass_util.cpp | 149 + src/libsass/src/sass_util.hpp | 256 ++ src/libsass/src/sass_values.cpp | 354 ++ src/libsass/src/sass_values.hpp | 81 + src/libsass/src/source_map.cpp | 197 ++ src/libsass/src/source_map.hpp | 62 + src/libsass/src/subset_map.cpp | 57 + src/libsass/src/subset_map.hpp | 76 + src/libsass/src/support/libsass.pc.in | 11 + src/libsass/src/to_c.cpp | 74 + src/libsass/src/to_c.hpp | 39 + src/libsass/src/to_value.cpp | 107 + src/libsass/src/to_value.hpp | 49 + src/libsass/src/units.cpp | 197 ++ src/libsass/src/units.hpp | 92 + src/libsass/src/utf8.h | 34 + src/libsass/src/utf8/checked.h | 327 ++ src/libsass/src/utf8/core.h | 329 ++ src/libsass/src/utf8/unchecked.h | 228 ++ src/libsass/src/utf8_string.cpp | 102 + src/libsass/src/utf8_string.hpp | 37 + src/libsass/src/util.cpp | 639 ++++ src/libsass/src/util.hpp | 59 + src/libsass/src/values.cpp | 139 + src/libsass/src/values.hpp | 12 + src/libsass/test/test_node.cpp | 94 + src/libsass/test/test_paths.cpp | 28 + src/libsass/test/test_selector_difference.cpp | 25 + src/libsass/test/test_specificity.cpp | 25 + src/libsass/test/test_subset_map.cpp | 472 +++ src/libsass/test/test_superselector.cpp | 69 + src/libsass/test/test_unification.cpp | 31 + src/libsass/version.sh | 10 + src/libsass/win/libsass.sln | 28 + src/libsass/win/libsass.targets | 115 + src/libsass/win/libsass.vcxproj | 188 + src/libsass/win/libsass.vcxproj.filters | 348 ++ 188 files changed, 43495 insertions(+) create mode 100755 src/libsass/.editorconfig create mode 100755 src/libsass/.gitattributes create mode 100755 src/libsass/.github/CONTRIBUTING.md create mode 100755 src/libsass/.github/ISSUE_TEMPLATE.md create mode 100755 src/libsass/.gitignore create mode 100755 src/libsass/.travis.yml create mode 100755 src/libsass/COPYING create mode 100755 src/libsass/GNUmakefile.am create mode 100755 src/libsass/INSTALL create mode 100755 src/libsass/LICENSE create mode 100755 src/libsass/Makefile create mode 100755 src/libsass/Makefile.conf create mode 100755 src/libsass/Readme.md create mode 100755 src/libsass/SECURITY.md create mode 100755 src/libsass/appveyor.yml create mode 100755 src/libsass/configure.ac create mode 100755 src/libsass/contrib/libsass.spec create mode 100755 src/libsass/contrib/plugin.cpp create mode 100755 src/libsass/docs/README.md create mode 100755 src/libsass/docs/api-context-example.md create mode 100755 src/libsass/docs/api-context-internal.md create mode 100755 src/libsass/docs/api-context.md create mode 100755 src/libsass/docs/api-doc.md create mode 100755 src/libsass/docs/api-function-example.md create mode 100755 src/libsass/docs/api-function-internal.md create mode 100755 src/libsass/docs/api-function.md create mode 100755 src/libsass/docs/api-importer-example.md create mode 100755 src/libsass/docs/api-importer-internal.md create mode 100755 src/libsass/docs/api-importer.md create mode 100755 src/libsass/docs/api-value-example.md create mode 100755 src/libsass/docs/api-value-internal.md create mode 100755 src/libsass/docs/api-value.md create mode 100755 src/libsass/docs/build-on-darwin.md create mode 100755 src/libsass/docs/build-on-gentoo.md create mode 100755 src/libsass/docs/build-on-windows.md create mode 100755 src/libsass/docs/build-shared-library.md create mode 100755 src/libsass/docs/build-with-autotools.md create mode 100755 src/libsass/docs/build-with-makefiles.md create mode 100755 src/libsass/docs/build-with-mingw.md create mode 100755 src/libsass/docs/build-with-visual-studio.md create mode 100755 src/libsass/docs/build.md create mode 100755 src/libsass/docs/compatibility-plan.md create mode 100755 src/libsass/docs/contributing.md create mode 100755 src/libsass/docs/custom-functions-internal.md create mode 100755 src/libsass/docs/dev-ast-memory.md create mode 100755 src/libsass/docs/implementations.md create mode 100755 src/libsass/docs/plugins.md create mode 100755 src/libsass/docs/setup-environment.md create mode 100755 src/libsass/docs/source-map-internals.md create mode 100755 src/libsass/docs/trace.md create mode 100755 src/libsass/docs/triage.md create mode 100755 src/libsass/docs/unicode.md create mode 100755 src/libsass/extconf.rb create mode 100755 src/libsass/include/sass.h create mode 100755 src/libsass/include/sass/base.h create mode 100755 src/libsass/include/sass/context.h create mode 100755 src/libsass/include/sass/functions.h create mode 100755 src/libsass/include/sass/values.h create mode 100755 src/libsass/include/sass/version.h create mode 100755 src/libsass/include/sass/version.h.in create mode 100755 src/libsass/include/sass2scss.h create mode 100755 src/libsass/m4/.gitkeep create mode 100755 src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 create mode 100755 src/libsass/res/resource.rc create mode 100755 src/libsass/script/bootstrap create mode 100755 src/libsass/script/branding create mode 100755 src/libsass/script/ci-build-libsass create mode 100755 src/libsass/script/ci-install-compiler create mode 100755 src/libsass/script/ci-install-deps create mode 100755 src/libsass/script/ci-report-coverage create mode 100755 src/libsass/script/spec create mode 100755 src/libsass/script/tap-driver create mode 100755 src/libsass/script/tap-runner create mode 100755 src/libsass/script/test-leaks.pl create mode 100755 src/libsass/src/GNUmakefile.am create mode 100755 src/libsass/src/ast.cpp create mode 100755 src/libsass/src/ast.hpp create mode 100755 src/libsass/src/ast_def_macros.hpp create mode 100755 src/libsass/src/ast_fwd_decl.hpp create mode 100755 src/libsass/src/b64/cencode.h create mode 100755 src/libsass/src/b64/encode.h create mode 100755 src/libsass/src/backtrace.hpp create mode 100755 src/libsass/src/base64vlq.cpp create mode 100755 src/libsass/src/base64vlq.hpp create mode 100755 src/libsass/src/bind.cpp create mode 100755 src/libsass/src/bind.hpp create mode 100755 src/libsass/src/c99func.c create mode 100755 src/libsass/src/cencode.c create mode 100755 src/libsass/src/check_nesting.cpp create mode 100755 src/libsass/src/check_nesting.hpp create mode 100755 src/libsass/src/color_maps.cpp create mode 100755 src/libsass/src/color_maps.hpp create mode 100755 src/libsass/src/constants.cpp create mode 100755 src/libsass/src/constants.hpp create mode 100755 src/libsass/src/context.cpp create mode 100755 src/libsass/src/context.hpp create mode 100755 src/libsass/src/cssize.cpp create mode 100755 src/libsass/src/cssize.hpp create mode 100755 src/libsass/src/debug.hpp create mode 100755 src/libsass/src/debugger.hpp create mode 100755 src/libsass/src/emitter.cpp create mode 100755 src/libsass/src/emitter.hpp create mode 100755 src/libsass/src/environment.cpp create mode 100755 src/libsass/src/environment.hpp create mode 100755 src/libsass/src/error_handling.cpp create mode 100755 src/libsass/src/error_handling.hpp create mode 100755 src/libsass/src/eval.cpp create mode 100755 src/libsass/src/eval.hpp create mode 100755 src/libsass/src/expand.cpp create mode 100755 src/libsass/src/expand.hpp create mode 100755 src/libsass/src/extend.cpp create mode 100755 src/libsass/src/extend.hpp create mode 100755 src/libsass/src/file.cpp create mode 100755 src/libsass/src/file.hpp create mode 100755 src/libsass/src/functions.cpp create mode 100755 src/libsass/src/functions.hpp create mode 100755 src/libsass/src/inspect.cpp create mode 100755 src/libsass/src/inspect.hpp create mode 100755 src/libsass/src/json.cpp create mode 100755 src/libsass/src/json.hpp create mode 100755 src/libsass/src/kwd_arg_macros.hpp create mode 100755 src/libsass/src/lexer.cpp create mode 100755 src/libsass/src/lexer.hpp create mode 100755 src/libsass/src/listize.cpp create mode 100755 src/libsass/src/listize.hpp create mode 100755 src/libsass/src/mapping.hpp create mode 100755 src/libsass/src/memory/SharedPtr.cpp create mode 100755 src/libsass/src/memory/SharedPtr.hpp create mode 100755 src/libsass/src/node.cpp create mode 100755 src/libsass/src/node.hpp create mode 100755 src/libsass/src/operation.hpp create mode 100755 src/libsass/src/output.cpp create mode 100755 src/libsass/src/output.hpp create mode 100755 src/libsass/src/parser.cpp create mode 100755 src/libsass/src/parser.hpp create mode 100755 src/libsass/src/paths.hpp create mode 100755 src/libsass/src/plugins.cpp create mode 100755 src/libsass/src/plugins.hpp create mode 100755 src/libsass/src/position.cpp create mode 100755 src/libsass/src/position.hpp create mode 100755 src/libsass/src/prelexer.cpp create mode 100755 src/libsass/src/prelexer.hpp create mode 100755 src/libsass/src/remove_placeholders.cpp create mode 100755 src/libsass/src/remove_placeholders.hpp create mode 100755 src/libsass/src/sass.cpp create mode 100755 src/libsass/src/sass.hpp create mode 100755 src/libsass/src/sass2scss.cpp create mode 100755 src/libsass/src/sass_context.cpp create mode 100755 src/libsass/src/sass_context.hpp create mode 100755 src/libsass/src/sass_functions.cpp create mode 100755 src/libsass/src/sass_functions.hpp create mode 100755 src/libsass/src/sass_util.cpp create mode 100755 src/libsass/src/sass_util.hpp create mode 100755 src/libsass/src/sass_values.cpp create mode 100755 src/libsass/src/sass_values.hpp create mode 100755 src/libsass/src/source_map.cpp create mode 100755 src/libsass/src/source_map.hpp create mode 100755 src/libsass/src/subset_map.cpp create mode 100755 src/libsass/src/subset_map.hpp create mode 100755 src/libsass/src/support/libsass.pc.in create mode 100755 src/libsass/src/to_c.cpp create mode 100755 src/libsass/src/to_c.hpp create mode 100755 src/libsass/src/to_value.cpp create mode 100755 src/libsass/src/to_value.hpp create mode 100755 src/libsass/src/units.cpp create mode 100755 src/libsass/src/units.hpp create mode 100755 src/libsass/src/utf8.h create mode 100755 src/libsass/src/utf8/checked.h create mode 100755 src/libsass/src/utf8/core.h create mode 100755 src/libsass/src/utf8/unchecked.h create mode 100755 src/libsass/src/utf8_string.cpp create mode 100755 src/libsass/src/utf8_string.hpp create mode 100755 src/libsass/src/util.cpp create mode 100755 src/libsass/src/util.hpp create mode 100755 src/libsass/src/values.cpp create mode 100755 src/libsass/src/values.hpp create mode 100755 src/libsass/test/test_node.cpp create mode 100755 src/libsass/test/test_paths.cpp create mode 100755 src/libsass/test/test_selector_difference.cpp create mode 100755 src/libsass/test/test_specificity.cpp create mode 100755 src/libsass/test/test_subset_map.cpp create mode 100755 src/libsass/test/test_superselector.cpp create mode 100755 src/libsass/test/test_unification.cpp create mode 100755 src/libsass/version.sh create mode 100755 src/libsass/win/libsass.sln create mode 100755 src/libsass/win/libsass.targets create mode 100755 src/libsass/win/libsass.vcxproj create mode 100755 src/libsass/win/libsass.vcxproj.filters diff --git a/src/libsass/.editorconfig b/src/libsass/.editorconfig new file mode 100755 index 000000000..9a1a67656 --- /dev/null +++ b/src/libsass/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = spaces +indent_size = 2 + +[{Makefile, GNUmakefile.am}] +indent_style = tab +indent_size = 4 diff --git a/src/libsass/.gitattributes b/src/libsass/.gitattributes new file mode 100755 index 000000000..dfe077042 --- /dev/null +++ b/src/libsass/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/src/libsass/.github/CONTRIBUTING.md b/src/libsass/.github/CONTRIBUTING.md new file mode 100755 index 000000000..0e4e1902d --- /dev/null +++ b/src/libsass/.github/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# Contributing to LibSass + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +The following is a set of guidelines for contributing to LibSass, which is hosted in the [Sass Organization](https://github.com/sass) on GitHub. +These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. + +LibSass is a library that implements a [sass language] [8] compiler. As such it does not directly interface with end users (frontend developers). +For direct contributions to the LibSass code base you will need to have at least a rough idea of C++, we will not lie about that. +But there are other ways to contribute to the progress of LibSass. All contributions are done via github pull requests. + +You can also contribute to the LibSass [documentation] [9] or provide additional [spec tests] [10] (and we will gladly point you in the +direction for corners that lack test coverage). Foremost we rely on good and concise bug reports for issues the spec tests do not yet catch. + +## Precheck: My Sass isn't compiling +- [ ] Check if you can reproduce the issue via [SourceMap Inspector] [5] (updated regularly). +- [ ] Validate official ruby sass compiler via [SassMeister] [6] produces your expected result. +- [ ] Search for similar issue in [LibSass] [1] and [node-sass] [2] (include closed tickets) +- [ ] Optionally test your code directly with [sass] [7] or [sassc] [3] ([installer] [4]) + +## Precheck: My build/install fails +- [ ] Problems with building or installing libsass should be directed to implementors first! +- [ ] Except for issues directly verified via sassc or LibSass own build (make/autotools9 + +## Craft a meaningfull error report +- [ ] Include the version of libsass and the implementor (i.e. node-sass or sassc) +- [ ] Include information about your operating system and environment (i.e. io.js) +- [ ] Either create a self contained sample that shows your issue ... +- [ ] ... or provide it as a fetchable (github preferred) archive/repo +- [ ] ... and include a step by step list of command to get all dependencies +- [ ] Make it clear if you use indented or/and scss syntax + +## My error is hiding in a big code base +1. we do not have time to support your code base! +2. to fix occuring issues we need precise bug reports +3. the more precise you are, the faster we can help you +4. lazy reports get overlooked even when exposing serious bugs +5. it's not hard to do, it only takes time +- [ ] Make sure you saved the current state (i.e. commit to git) +- [ ] Start by uncommenting blocks in the initial source file +- [ ] Check if the problem is still there after each edit +- [ ] Repeat until the problem goes away +- [ ] Inline imported files as you go along +- [ ] Finished once you cannot remove more +- [ ] The emphasis is on the word "repeat" ... + +## What makes a code test case + +Important is that someone else can get the test case up and running to reproduce it locally. For this +we urge you to verify that your sample yields the expected result by testing it via [SassMeister] [6] +or directly via ruby sass or node-sass (or any other libsass implementor) before submitting your bug +report. Once you verified all of the above, you may use the template below to file your bug report. + + +[1]: https://github.com/sass/libsass/issues?utf8=%E2%9C%93&q=is%3Aissue +[2]: https://github.com/sass/node-sass/issues?utf8=%E2%9C%93&q=is%3Aissue +[3]: https://github.com/sass/sassc +[4]: http://libsass.ocbnet.ch/installer/ +[5]: http://libsass.ocbnet.ch/srcmap/ +[6]: http://www.sassmeister.com/ +[7]: https://rubygems.org/gems/sass + +[8]: http://sass-lang.com/ +[9]: https://github.com/sass/libsass/tree/master/docs +[10]: https://github.com/sass/sass-spec diff --git a/src/libsass/.github/ISSUE_TEMPLATE.md b/src/libsass/.github/ISSUE_TEMPLATE.md new file mode 100755 index 000000000..723611139 --- /dev/null +++ b/src/libsass/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,29 @@ +### Title: Be as meaningful as possible in 60 chars if possible + +input.scss +```scss +test { + content: bar +} +``` + +[libsass 3.5.5] [1] +```css +test { + content: bar; } +``` + +ruby sass 3.4.21 +```css +test { + content: bar; } +``` + +version info: +```cmd +$ node-sass --version +node-sass 3.3.3 (Wrapper) [JavaScript] +libsass 3.2.5 (Sass Compiler) [C/C++] +``` + +[1]: http://libsass.ocbnet.ch/srcmap/#dGVzdCB7CiAgY29udGVudDogYmFyOyB9Cg== diff --git a/src/libsass/.gitignore b/src/libsass/.gitignore new file mode 100755 index 000000000..af3786418 --- /dev/null +++ b/src/libsass/.gitignore @@ -0,0 +1,82 @@ +# Miscellaneous stuff + +/sassc +/sass-spec + +VERSION +.DS_Store +.sass-cache +*.gem +*.gcno +.svn/* +.cproject +.project +.settings/ + +# Configuration stuff + +GNUmakefile.in +GNUmakefile +/aclocal.m4 +/autom4te.cache/ +/src/config.h +/config.h.in +/config.log +/config.status +/configure +/libtool +/m4/libtool.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 +/script/ar-lib +/script/compile +/script/config.guess +/script/config.sub +/script/depcomp +/script/install-sh +/script/ltmain.sh +/script/missing +/script/test-driver +/src/stamp-h1 +/src/Makefile.in +/src/Makefile +libsass/* + +# Build stuff + +*.o +*.lo +*.so +*.dll +*.a +*.suo +*.sdf +*.opendb +*.opensdf +a.out +libsass.js +tester +tester.exe +build/ +config.h.in* +lib/pkgconfig/ + +bin/* +.deps/ +.libs/ +win/bin +*.user + +# Final results + +sassc++ +libsass.la +src/support/libsass.pc + +# Cloned testing dirs +sassc/ +sass-spec/ + +installer/ diff --git a/src/libsass/.travis.yml b/src/libsass/.travis.yml new file mode 100755 index 000000000..c36f15572 --- /dev/null +++ b/src/libsass/.travis.yml @@ -0,0 +1,45 @@ +language: cpp +sudo: false + +os: + - linux + - osx + +compiler: + - gcc + - clang + +# don't create redundant code coverage reports +# - AUTOTOOLS=yes COVERAGE=yes BUILD=static +# - AUTOTOOLS=no COVERAGE=yes BUILD=shared +# - AUTOTOOLS=no COVERAGE=no BUILD=static + +# further speed up day by day travis-ci builds +# re-enable this if you change the makefiles +# this will still catch all coding errors! +# - AUTOTOOLS=yes COVERAGE=no BUILD=static + +env: + - AUTOTOOLS=no COVERAGE=no BUILD=shared + - AUTOTOOLS=no COVERAGE=yes BUILD=static + - AUTOTOOLS=yes COVERAGE=no BUILD=shared + +# currenty there are various issues when +# built with coverage, clang and autotools +# - AUTOTOOLS=yes COVERAGE=yes BUILD=shared + +matrix: + exclude: + - compiler: clang + env: AUTOTOOLS=yes COVERAGE=yes BUILD=static + - os: linux + env: AUTOTOOLS=no COVERAGE=no BUILD=shared + - os: osx + compiler: gcc + - os: osx + env: AUTOTOOLS=no BUILD=static + +script: ./script/ci-build-libsass +before_install: ./script/ci-install-deps +install: ./script/ci-install-compiler +after_success: ./script/ci-report-coverage diff --git a/src/libsass/COPYING b/src/libsass/COPYING new file mode 100755 index 000000000..8639c1117 --- /dev/null +++ b/src/libsass/COPYING @@ -0,0 +1,25 @@ + +Copyright (C) 2012 by Hampton Catlin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +The following files in the spec were taken from the original Ruby Sass project which +is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under +the same license. diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am new file mode 100755 index 000000000..d2bfdbfec --- /dev/null +++ b/src/libsass/GNUmakefile.am @@ -0,0 +1,88 @@ +ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4 -I script + +AM_COPT = -Wall -O2 +AM_COVLDFLAGS = + +if ENABLE_COVERAGE + AM_COPT = -O0 --coverage + AM_COVLDFLAGS += -lgcov +endif + +AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CFLAGS = $(AM_COPT) +AM_CXXFLAGS = $(AM_COPT) +AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) + +# only needed to support old source tree +# we have moved the files to src folder +AM_CPPFLAGS += -I$(top_srcdir) + +RESOURCES = +if COMPILER_IS_MINGW32 + RESOURCES += res/libsass.rc + AM_CXXFLAGS += -std=gnu++0x +else + AM_CXXFLAGS += -std=c++0x +endif + +if ENABLE_TESTS + +noinst_PROGRAMS = tester + +tester_LDADD = src/libsass.la +tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c +tester_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` +tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" +tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" +tester_LDFLAGS = $(AM_LDFLAGS) + +if ENABLE_COVERAGE +nodist_EXTRA_tester_SOURCES = non-existent-file-to-force-CXX-linking.cxx +endif + +SASS_SASSC_PATH ?= $(top_srcdir)/sassc +SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec + +TESTS = \ + $(SASS_SPEC_PATH)/spec/basic \ + $(SASS_SPEC_PATH)/spec/css \ + $(SASS_SPEC_PATH)/spec/extend-tests \ + $(SASS_SPEC_PATH)/spec/extends \ + $(SASS_SPEC_PATH)/spec/libsass \ + $(SASS_SPEC_PATH)/spec/libsass-closed-issues \ + $(SASS_SPEC_PATH)/spec/maps \ + $(SASS_SPEC_PATH)/spec/misc \ + $(SASS_SPEC_PATH)/spec/regressions \ + $(SASS_SPEC_PATH)/spec/scss \ + $(SASS_SPEC_PATH)/spec/scss-tests \ + $(SASS_SPEC_PATH)/spec/types + +SASS_TEST_FLAGS = -V 3.4 --impl libsass +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) ./script/tap-driver +AM_LOG_FLAGS = -c ./tester $(LOG_FLAGS) +if USE_TAP + AM_LOG_FLAGS += -t + SASS_TEST_FLAGS += -t | tapout + LOG_COMPILER = ./script/tap-runner $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb +else + LOG_COMPILER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb +endif + +SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb +SASS_TESTER += -c $(SASS_LIBSASS_PATH)/tester$(EXEEXT) + +test: + $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + +test_build: + $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + +test_full: + $(SASS_TESTER) --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + +test_probe: + $(SASS_TESTER) --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + +endif + +SUBDIRS = src diff --git a/src/libsass/INSTALL b/src/libsass/INSTALL new file mode 100755 index 000000000..92e0156bf --- /dev/null +++ b/src/libsass/INSTALL @@ -0,0 +1 @@ +// Autotools requires us to have this file. Boo. diff --git a/src/libsass/LICENSE b/src/libsass/LICENSE new file mode 100755 index 000000000..6bc408500 --- /dev/null +++ b/src/libsass/LICENSE @@ -0,0 +1,25 @@ + +Copyright (C) 2012-2016 by the Sass Open Source Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +The following files in the spec were taken from the original Ruby Sass project which +is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under +the same license. diff --git a/src/libsass/Makefile b/src/libsass/Makefile new file mode 100755 index 000000000..abfe7efaf --- /dev/null +++ b/src/libsass/Makefile @@ -0,0 +1,347 @@ +OS ?= $(shell uname -s) +CC ?= gcc +CXX ?= g++ +RM ?= rm -f +CP ?= cp -a +MKDIR ?= mkdir +RMDIR ?= rmdir +WINDRES ?= windres +# Solaris/Illumos flavors +# ginstall from coreutils +ifeq ($(OS),SunOS) +INSTALL ?= ginstall +endif +INSTALL ?= install +CFLAGS ?= -Wall +CXXFLAGS ?= -Wall +LDFLAGS ?= -Wall +ifneq "$(COVERAGE)" "yes" + CFLAGS += -O2 + CXXFLAGS += -O2 + LDFLAGS += -O2 +endif +LDFLAGS += -Wl,-undefined,error +CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) + +ifneq (,$(findstring /cygdrive/,$(PATH))) + UNAME := Cygwin +else + ifneq (,$(findstring Windows_NT,$(OS))) + UNAME := Windows + else + ifneq (,$(findstring mingw32,$(MAKE))) + UNAME := Windows + else + ifneq (,$(findstring MINGW32,$(shell uname -s))) + UNAME = Windows + else + UNAME := $(shell uname -s) + endif + endif + endif +endif + +ifeq ($(SASS_LIBSASS_PATH),) + SASS_LIBSASS_PATH = $(abspath $(CURDIR)) +endif + +ifeq ($(LIBSASS_VERSION),) + ifneq ($(wildcard ./.git/ ),) + LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) + endif +endif + +ifeq ($(LIBSASS_VERSION),) + ifneq ($(wildcard VERSION),) + LIBSASS_VERSION ?= $(shell $(CAT) VERSION) + endif +endif + +ifneq ($(LIBSASS_VERSION),) + CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" + CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" +endif + +# enable mandatory flag +ifeq (Windows,$(UNAME)) + ifneq ($(BUILD),shared) + STATIC_ALL ?= 1 + endif + STATIC_LIBGCC ?= 1 + STATIC_LIBSTDCPP ?= 1 + CXXFLAGS += -std=gnu++0x + LDFLAGS += -std=gnu++0x +else + STATIC_ALL ?= 0 + STATIC_LIBGCC ?= 0 + STATIC_LIBSTDCPP ?= 0 + CXXFLAGS += -std=c++0x + LDFLAGS += -std=c++0x +endif + +ifneq ($(SASS_LIBSASS_PATH),) + CFLAGS += -I $(SASS_LIBSASS_PATH)/include + CXXFLAGS += -I $(SASS_LIBSASS_PATH)/include +else + # this is needed for mingw + CFLAGS += -I include + CXXFLAGS += -I include +endif + +ifneq ($(EXTRA_CFLAGS),) + CFLAGS += $(EXTRA_CFLAGS) +endif +ifneq ($(EXTRA_CXXFLAGS),) + CXXFLAGS += $(EXTRA_CXXFLAGS) +endif +ifneq ($(EXTRA_LDFLAGS),) + LDFLAGS += $(EXTRA_LDFLAGS) +endif + +LDLIBS = -lm + +ifneq ($(BUILD),shared) + LDLIBS += -lstdc++ +endif + +# link statically into lib +# makes it a lot more portable +# increases size by about 50KB +ifeq ($(STATIC_ALL),1) + LDFLAGS += -static +endif +ifeq ($(STATIC_LIBGCC),1) + LDFLAGS += -static-libgcc +endif +ifeq ($(STATIC_LIBSTDCPP),1) + LDFLAGS += -static-libstdc++ +endif + +ifeq ($(UNAME),Darwin) + CFLAGS += -stdlib=libc++ + CXXFLAGS += -stdlib=libc++ + LDFLAGS += -stdlib=libc++ +endif + +ifneq (Windows,$(UNAME)) + ifneq (FreeBSD,$(UNAME)) + ifneq (OpenBSD,$(UNAME)) + LDFLAGS += -ldl + LDLIBS += -ldl + endif + endif +endif + +ifneq ($(BUILD),shared) + BUILD := static +endif +ifeq ($(DEBUG),1) + BUILD := debug-$(BUILD) +endif + +ifeq (,$(TRAVIS_BUILD_DIR)) + ifeq ($(OS),SunOS) + PREFIX ?= /opt/local + else + PREFIX ?= /usr/local + endif +else + PREFIX ?= $(TRAVIS_BUILD_DIR) +endif + + +SASS_SASSC_PATH ?= sassc +SASS_SPEC_PATH ?= sass-spec +SASS_SPEC_SPEC_DIR ?= spec +SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc +RUBY_BIN = ruby + +LIB_STATIC = $(SASS_LIBSASS_PATH)/lib/libsass.a +LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.so + +ifeq (Windows,$(UNAME)) + ifeq (shared,$(BUILD)) + CFLAGS += -D ADD_EXPORTS + CXXFLAGS += -D ADD_EXPORTS + LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.dll + endif +else + ifneq (Cygwin,$(UNAME)) + CFLAGS += -fPIC + CXXFLAGS += -fPIC + LDFLAGS += -fPIC + endif +endif + +ifeq (Windows,$(UNAME)) + SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc.exe +endif + +include Makefile.conf + +RESOURCES = +STATICLIB = lib/libsass.a +SHAREDLIB = lib/libsass.so +ifeq (Windows,$(UNAME)) + RESOURCES += res/resource.rc + SHAREDLIB = lib/libsass.dll + ifeq (shared,$(BUILD)) + CFLAGS += -D ADD_EXPORTS + CXXFLAGS += -D ADD_EXPORTS + endif +else + ifneq (Cygwin,$(UNAME)) + CFLAGS += -fPIC + CXXFLAGS += -fPIC + LDFLAGS += -fPIC + endif +endif + +OBJECTS = $(addprefix src/,$(SOURCES:.cpp=.o)) +COBJECTS = $(addprefix src/,$(CSOURCES:.c=.o)) +RCOBJECTS = $(RESOURCES:.rc=.o) + +DEBUG_LVL ?= NONE + +CLEANUPS ?= +CLEANUPS += $(RCOBJECTS) +CLEANUPS += $(COBJECTS) +CLEANUPS += $(OBJECTS) +CLEANUPS += $(LIBSASS_LIB) + +all: $(BUILD) + +debug: $(BUILD) + +debug-static: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS)) +debug-static: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS)) +debug-static: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS)) +debug-static: static + +debug-shared: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS)) +debug-shared: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS)) +debug-shared: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS)) +debug-shared: shared + +lib: + $(MKDIR) lib + +lib/libsass.a: lib $(COBJECTS) $(OBJECTS) + $(AR) rcvs $@ $(COBJECTS) $(OBJECTS) + +lib/libsass.so: lib $(COBJECTS) $(OBJECTS) + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(LDLIBS) + +lib/libsass.dll: lib $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -Wl,--subsystem,windows,--out-implib,lib/libsass.a + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.rc + $(WINDRES) -i $< -o $@ + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%: %.o static + $(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) $(LDLIBS) + +install: install-$(BUILD) + +static: $(STATICLIB) +shared: $(SHAREDLIB) + +$(DESTDIR)$(PREFIX): + $(MKDIR) $(DESTDIR)$(PREFIX) + +$(DESTDIR)$(PREFIX)/lib: $(DESTDIR)$(PREFIX) + $(MKDIR) $(DESTDIR)$(PREFIX)/lib + +$(DESTDIR)$(PREFIX)/include: $(DESTDIR)$(PREFIX) + $(MKDIR) $(DESTDIR)$(PREFIX)/include + +$(DESTDIR)$(PREFIX)/include/sass: $(DESTDIR)$(PREFIX)/include + $(MKDIR) $(DESTDIR)$(PREFIX)/include/sass + +$(DESTDIR)$(PREFIX)/include/%.h: include/%.h \ + $(DESTDIR)$(PREFIX)/include \ + $(DESTDIR)$(PREFIX)/include/sass + $(INSTALL) -v -m0644 "$<" "$@" + +install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ + $(DESTDIR)$(PREFIX)/include/sass2scss.h \ + $(DESTDIR)$(PREFIX)/include/sass/base.h \ + $(DESTDIR)$(PREFIX)/include/sass/version.h \ + $(DESTDIR)$(PREFIX)/include/sass/values.h \ + $(DESTDIR)$(PREFIX)/include/sass/context.h \ + $(DESTDIR)$(PREFIX)/include/sass/functions.h + +$(DESTDIR)$(PREFIX)/lib/%.a: lib/%.a \ + $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +$(DESTDIR)$(PREFIX)/lib/%.so: lib/%.so \ + $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +$(DESTDIR)$(PREFIX)/lib/%.dll: lib/%.dll \ + $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +install-static: $(DESTDIR)$(PREFIX)/lib/libsass.a + +install-shared: $(DESTDIR)$(PREFIX)/lib/libsass.so \ + install-headers + +$(SASSC_BIN): $(BUILD) + $(MAKE) -C $(SASS_SASSC_PATH) + +sassc: $(SASSC_BIN) + $(SASSC_BIN) -v + +version: $(SASSC_BIN) + $(SASSC_BIN) -h + $(SASSC_BIN) -v + +test: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_build: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_full: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_probe: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +clean-objects: lib + -$(RM) lib/*.a lib/*.so lib/*.dll lib/*.la + -$(RMDIR) lib +clean: clean-objects + $(RM) $(CLEANUPS) + +clean-all: + $(MAKE) -C $(SASS_SASSC_PATH) clean + +lib-file: lib-file-$(BUILD) +lib-opts: lib-opts-$(BUILD) + +lib-file-static: + @echo $(LIB_STATIC) +lib-file-shared: + @echo $(LIB_SHARED) +lib-opts-static: + @echo -L"$(SASS_LIBSASS_PATH)/lib" +lib-opts-shared: + @echo -L"$(SASS_LIBSASS_PATH)/lib -lsass" + +.PHONY: all static shared sassc \ + version install-headers \ + clean clean-all clean-objects \ + debug debug-static debug-shared \ + install install-static install-shared \ + lib-opts lib-opts-shared lib-opts-static \ + lib-file lib-file-shared lib-file-static +.DELETE_ON_ERROR: diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf new file mode 100755 index 000000000..6c15907b1 --- /dev/null +++ b/src/libsass/Makefile.conf @@ -0,0 +1,52 @@ +# this is merely a common Makefile multiple implementers can use +# bigger files (in terms of compile time) tend to go to the top, +# so they don't end up as the last compile unit when compiling +# in parallel. But we also want to mix them a little too avoid +# heavy RAM usage peaks. Other than that the order is arbitrary. + + +SOURCES = \ + ast.cpp \ + node.cpp \ + context.cpp \ + constants.cpp \ + functions.cpp \ + color_maps.cpp \ + environment.cpp \ + bind.cpp \ + file.cpp \ + util.cpp \ + json.cpp \ + units.cpp \ + values.cpp \ + plugins.cpp \ + position.cpp \ + lexer.cpp \ + parser.cpp \ + prelexer.cpp \ + eval.cpp \ + expand.cpp \ + listize.cpp \ + cssize.cpp \ + extend.cpp \ + output.cpp \ + inspect.cpp \ + emitter.cpp \ + check_nesting.cpp \ + remove_placeholders.cpp \ + sass.cpp \ + sass_util.cpp \ + sass_values.cpp \ + sass_context.cpp \ + sass_functions.cpp \ + sass2scss.cpp \ + to_c.cpp \ + to_value.cpp \ + source_map.cpp \ + subset_map.cpp \ + error_handling.cpp \ + memory/SharedPtr.cpp \ + utf8_string.cpp \ + base64vlq.cpp + +CSOURCES = cencode.c diff --git a/src/libsass/Readme.md b/src/libsass/Readme.md new file mode 100755 index 000000000..3eaa63a59 --- /dev/null +++ b/src/libsass/Readme.md @@ -0,0 +1,99 @@ +LibSass +======= + +by Aaron Leung ([@akhleung]), Hampton Catlin ([@hcatlin]), Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) + +[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass) +[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master) +[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE) +[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3) +[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/) + +https://github.com/sass/libsass + +LibSass is just a library, but if you want to RUN LibSass, +then go to https://github.com/sass/sassc or +https://github.com/sass/sassc-ruby or +[find your local implementer](docs/implementations.md). + +LibSass requires GCC 4.6+ or Clang/LLVM. If your OS is older, this version may not compile. + +On Windows, you need MinGW with GCC 4.6+ or VS 2013 Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows. + +About +----- + +LibSass is a C/C++ port of the Sass CSS precompiler. The original version was written in Ruby, but this version is meant for efficiency and portability. + +This library strives to be light, simple, and easy to build and integrate with a variety of platforms and languages. + +Developing +---------- + +As you may have noticed, the LibSass repo itself has +no executables and no tests. Oh noes! How can you develop??? + +Well, luckily, [SassC](http://github.com/sass/sassc) is the official binary wrapper for +LibSass and is *always* kept in sync. SassC is used by continous integration systems in +LibSass repository. When developing LibSass, it is best to actually +checkout SassC and develop in that directory with the SassC spec +and tests there. + +We even run Travis tests for SassC! + +Tests +------- + +Since LibSass is a pure library, tests are run through the [SassSpec](https://github.com/sass/sass-spec) project using the [SassC](http://github.com/sass/sassc) driver. + +To run tests against LibSass while developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. + +### DEBUG builds + +Set the environment variable `DEBUG` to `1` to enable debug builds that can be debugged +with `gdb`, `lldb` and others. E.g.: use `$ DEBUG=1 ./script/spec` to run the tests with +a debug build. + +Library Usage +------------- + +While LibSass is a library primarily written in C++, it provides a simple +C interface which should be used by most implementers. LibSass does not do +much on its own without an implementer. This can be a command line tool, like +[sassc](https://github.com/sass/sassc) or a [binding](docs/implementations.md) +to your favorite programing language. + +If you want to build or interface with LibSass, we recommend to check out the +documentation pages about [building LibSass](docs/build.md) and +the [C-API documentation](docs/api-doc.md). + +About Sass +---------- + +Sass is a CSS pre-processor language to add on exciting, new, +awesome features to CSS. Sass was the first language of its kind +and by far the most mature and up to date codebase. + +Sass was originally concieved of by the co-creator of this library, +Hampton Catlin ([@hcatlin]). Most of the language has been the result of years +of work by Natalie Weizenbaum ([@nex3]) and Chris Eppstein ([@chriseppstein]). + +For more information about Sass itself, please visit http://sass-lang.com + +Initial development of libsass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). + +Licensing +--------- + +Our MIT license is designed to be as simple, and liberal as possible. + +sass2scss was originally written by [Marcel Greter][@mgreter] +and he happily agreed to have it merged into the project. + + +[@hcatlin]: https://github.com/hcatlin +[@akhleung]: https://github.com/akhleung +[@chriseppstein]: https://github.com/chriseppstein +[@nex3]: https://github.com/nex3 +[@mgreter]: https://github.com/mgreter +[@xzyfer]: https://github.com/xzyfer diff --git a/src/libsass/SECURITY.md b/src/libsass/SECURITY.md new file mode 100755 index 000000000..90c837c60 --- /dev/null +++ b/src/libsass/SECURITY.md @@ -0,0 +1,10 @@ +Serious about security +====================== + +The LibSass team recognizes the important contributions the security research +community can make. We therefore encourage reporting security issues with the +code contained in this repository. + +If you believe you have discovered a security vulnerability, please report it at +https://hackerone.com/libsass instead of GitHub. + diff --git a/src/libsass/appveyor.yml b/src/libsass/appveyor.yml new file mode 100755 index 000000000..e88cfcb81 --- /dev/null +++ b/src/libsass/appveyor.yml @@ -0,0 +1,87 @@ +os: Visual Studio 2013 + +environment: + CTEST_OUTPUT_ON_FAILURE: 1 + ruby_version: 22-x64 + TargetPath: sassc/bin/sassc.exe + matrix: + - Compiler: msvc + Config: Release + - Compiler: msvc + Config: Debug + - Compiler: mingw + Build: static + - Compiler: mingw + Build: shared + +cache: + - C:\Ruby%ruby_version%\lib\ruby\gems + - C:\mingw64 + +install: + - git clone https://github.com/sass/sassc.git + - git clone https://github.com/sass/sass-spec.git + - set PATH=C:\Ruby%ruby_version%\bin;%PATH% + - ps: | + if(!(gem which minitest 2>$nul)) { gem install minitest --no-ri --no-rdoc } + if ($env:Compiler -eq "mingw" -AND -Not (Test-Path "C:\mingw64")) { + # Install MinGW. + $file = "x86_64-4.9.2-release-win32-seh-rt_v4-rev3.7z" + wget https://bintray.com/artifact/download/drewwells/generic/$file -OutFile $file + &7z x -oC:\ $file > $null + } + - set PATH=C:\mingw64\bin;%PATH% + - set CC=gcc + +build_script: + - ps: | + if ($env:Compiler -eq "mingw") { + mingw32-make -j4 sassc + } else { + msbuild /m:4 /p:Configuration=$env:Config sassc\win\sassc.sln + } + + # print the branding art + mv script/branding script/branding.ps1 + script/branding.ps1 + + # print the version info + &$env:TargetPath -v + ruby -v + +test_script: + - ps: | + $PRNR = $env:APPVEYOR_PULL_REQUEST_NUMBER + if ($PRNR) { + echo "Fetching info for PR $PRNR" + wget https://api.github.com/repos/sass/libsass/pulls/$PRNR -OutFile pr.json + $json = cat pr.json -Raw + $SPEC_PR = [regex]::match($json,'sass\/sass-spec(#|\/pull\/)([0-9]+)').Groups[2].Value + if ($SPEC_PR) { + echo "Checkout sass spec PR $SPEC_PR" + git -C sass-spec fetch -q -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR + git -C sass-spec checkout -q --force ci-spec-pr-$SPEC_PR + } + } + $env:TargetPath = Join-Path $pwd.Path $env:TargetPath + If (Test-Path "$env:TargetPath") { + ruby sass-spec/sass-spec.rb -V 3.4 --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec + if(-not($?)) { + echo "sass-spec tests failed" + exit 1 + } + } else { + echo "spec runner not found (compile error?)" + exit 1 + } + Write-Host "Explicitly testing the case when cwd has Cyrillic characters: " -nonewline + # See comments in gh-1774 for details. + cd sass-spec/spec/libsass/Sáss-UŢF8/ + &$env:TargetPath ./input.scss 2>&1>$null + if(-not($?)) { + echo "Failed!" + exit 1 + } else { + echo "Success!" + } + diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac new file mode 100755 index 000000000..3a6ccc940 --- /dev/null +++ b/src/libsass/configure.ac @@ -0,0 +1,138 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.61]) + +AC_INIT([libsass], m4_esyscmd_s([./version.sh]), [support@moovweb.com]) +AC_CONFIG_SRCDIR([src/ast.hpp]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([src/config.h]) +AC_CONFIG_FILES([include/sass/version.h]) +AC_CONFIG_AUX_DIR([script]) +# These are flags passed to automake +# Though they look like gcc flags! +AM_INIT_AUTOMAKE([foreign parallel-tests -Wall]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([no])]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX +AC_LANG_PUSH([C]) +AC_LANG_PUSH([C++]) +AC_GNU_SOURCE +# Check fails on Travis, but it works fine +# AX_CXX_COMPILE_STDCXX_11([ext],[optional]) +AC_CHECK_TOOL([AR], [ar], [false]) +AC_CHECK_TOOL([DLLTOOL], [dlltool], [false]) +AC_CHECK_TOOL([DLLWRAP], [dllwrap], [false]) +AC_CHECK_TOOL([WINDRES], [windres], [false]) +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +LT_INIT([dlopen]) + +# Checks for header files. +AC_CHECK_HEADERS([unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_CHECK_FUNCS([floor getcwd strtol]) + +# Checks for testing. +AC_ARG_ENABLE(tests, AS_HELP_STRING([--enable-tests], [enable testing the build]), + [enable_tests="$enableval"], [enable_tests=no]) + +AS_CASE([$host], [*-*-mingw*], [is_mingw32=yes], [is_mingw32=no]) +AM_CONDITIONAL(COMPILER_IS_MINGW32, test "x$is_mingw32" = "xyes") + +dnl The dlopen() function is in the C library for *BSD and in +dnl libdl on GLIBC-based systems +if test "x$is_mingw32" != "xyes"; then + AC_SEARCH_LIBS([dlopen], [dl dld], [], [ + AC_MSG_ERROR([unable to find the dlopen() function]) + ]) +fi + +if test "x$enable_tests" = "xyes"; then + AC_PROG_CC + AC_PROG_AWK + # test need minitest gem + AC_PATH_PROG(RUBY, [ruby]) + AC_PATH_PROG(TAPOUT, [tapout]) + AC_REQUIRE_AUX_FILE([tap-driver]) + AC_REQUIRE_AUX_FILE([tap-runner]) + AC_ARG_WITH(sassc-dir, + AS_HELP_STRING([--with-sassc-dir=], [specify directory of sassc sources for testing (default: sassc)]), + [sassc_dir="$withval"], [sassc_dir="sassc"]) + AC_CHECK_FILE([$sassc_dir/sassc.c], [], [ + AC_MSG_ERROR([Unable to find sassc directory. +You must clone the sassc repository in this directory or specify +the --with-sassc-dir= argument. +]) + ]) + SASS_SASSC_PATH=$sassc_dir + AC_SUBST(SASS_SASSC_PATH) + + AC_ARG_WITH(sass-spec-dir, + AS_HELP_STRING([--with-sass-spec-dir=], [specify directory of sass-spec for testing (default: sass-spec)]), + [sass_spec_dir="$withval"], [sass_spec_dir="sass-spec"]) + AC_CHECK_FILE([$sass_spec_dir/sass-spec.rb], [], [ + AC_MSG_ERROR([Unable to find sass-spec directory. +You must clone the sass-spec repository in this directory or specify +the --with-sass-spec-dir= argument. +]) + ]) + # Automake doesn't like its tests in an absolute path, so we make it relative. + case $sass_spec_dir in + /*) + SASS_SPEC_PATH=`$RUBY -e "require 'pathname'; puts Pathname.new('$sass_spec_dir').relative_path_from(Pathname.new('$PWD')).to_s"` + ;; + *) + SASS_SPEC_PATH="$sass_spec_dir" + ;; + esac + AC_SUBST(SASS_SPEC_PATH) + + # TODO: Remove this when automake requirements are 1.12+ + AC_MSG_CHECKING([whether we can use TAP mode]) + tmp=`$AWK '/TEST_LOG_DRIVER/' $srcdir/GNUmakefile.in` + if test "x$tmp" != "x"; then + use_tap=yes + else + use_tap=no + fi + AC_MSG_RESULT([$use_tap]) + +fi + +AM_CONDITIONAL(ENABLE_TESTS, test "x$enable_tests" = "xyes") +AM_CONDITIONAL(USE_TAP, test "x$use_tap" = "xyes") + +AC_ARG_ENABLE([coverage], + [AS_HELP_STRING([--enable-coverage], + [enable coverage report for test suite])], + [enable_cov=$enableval], + [enable_cov=no]) + +if test "x$enable_cov" = "xyes"; then + + AC_CHECK_PROG(GCOV, gcov, gcov) + + # Remove all optimization flags from C[XX]FLAGS + changequote({,}) + CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'` + CXXFLAGS=`echo "$CXXFLAGS" | $SED -e 's/-O[0-9]*//g'` + changequote([,]) + + AC_SUBST(GCOV) +fi + +AM_CONDITIONAL(ENABLE_COVERAGE, test "x$enable_cov" = "xyes") + +AC_SUBST(PACKAGE_VERSION) + +AC_MSG_NOTICE([Building libsass ($VERSION)]) + +AC_CONFIG_FILES([GNUmakefile src/GNUmakefile src/support/libsass.pc]) +AC_OUTPUT diff --git a/src/libsass/contrib/libsass.spec b/src/libsass/contrib/libsass.spec new file mode 100755 index 000000000..a83d5f0cb --- /dev/null +++ b/src/libsass/contrib/libsass.spec @@ -0,0 +1,66 @@ +Name: libsass +Version: %{version} +Release: 1%{?dist} +Summary: A C/C++ implementation of a Sass compiler + +License: MIT +URL: http://libsass.org +Source0: %{name}-%{version}.tar.gz + +BuildRequires: gcc-c++ >= 4.7 +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool + + +%description +LibSass is a C/C++ port of the Sass engine. The point is to be simple, fast, and easy to integrate. + +%package devel +Summary: Development files for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} + + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + + +%prep +%setup -q +autoreconf --force --install + + +%build +%configure --disable-static \ + --disable-tests \ + --enable-shared + +make %{?_smp_mflags} + + +%install +%make_install +find $RPM_BUILD_ROOT -name '*.la' -exec rm -f {} ';' + + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + + +%files +%doc Readme.md LICENSE +%{_libdir}/*.so.* + +%files devel +%doc +%{_includedir}/* +%{_libdir}/*.so +%{_libdir}/pkgconfig/*.pc + + +%changelog +* Tue Feb 10 2015 Gawain Lynch - 3.1.0-1 +- Initial SPEC file + diff --git a/src/libsass/contrib/plugin.cpp b/src/libsass/contrib/plugin.cpp new file mode 100755 index 000000000..2f67bb371 --- /dev/null +++ b/src/libsass/contrib/plugin.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +// gcc: g++ -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +// mingw: g++ -shared plugin.cpp -o plugin.dll -Llib -lsass + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +union Sass_Value* custom_function(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) +{ + // get context/option struct associated with this compiler + struct Sass_Context* ctx = sass_compiler_get_context(comp); + struct Sass_Options* opts = sass_compiler_get_options(comp); + // get the cookie from function descriptor + void* cookie = sass_function_get_cookie(cb); + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" Sass_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_Function_Entry c_func = + sass_make_function("foo()", custom_function, (void*)42); + // create list of all custom functions + Sass_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, c_func); + // return the list + return fn_list; +} + +Sass_Import_List custom_importer(const char* cur_path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) +{ + // get the cookie from importer descriptor + void* cookie = sass_importer_get_cookie(cb); + // create a list to hold our import entries + Sass_Import_List incs = sass_make_import_list(1); + // create our only import entry (route path back) + incs[0] = sass_make_import_entry(cur_path, 0, 0); + // return imports + return incs; +} + +extern "C" Sass_Importer_List ADDCALL libsass_load_importers() +{ + // allocate a custom function caller + Sass_Importer_Entry c_imp = + sass_make_importer(custom_importer, - 99, (void*)42); + // create list of all custom functions + Sass_Importer_List imp_list = sass_make_importer_list(1); + // put the only function in this plugin to the list + sass_importer_set_list_entry(imp_list, 0, c_imp); + // return the list + return imp_list; +} diff --git a/src/libsass/docs/README.md b/src/libsass/docs/README.md new file mode 100755 index 000000000..a233fae48 --- /dev/null +++ b/src/libsass/docs/README.md @@ -0,0 +1,20 @@ +Welcome to the LibSass documentation! + +## First Off +LibSass is just a library. To run the code locally (i.e. to compile your stylesheets), you need an implementer. SassC (get it?) is an implementer written in C. There are a number of other implementations of LibSass - for example Node. We encourage you to write your own port - the whole point of LibSass is that we want to bring Sass to many other languages, not just Ruby! + +We're working hard on moving to full parity with Ruby Sass... learn more at the [The-LibSass-Compatibility-Plan](compatibility-plan.md)! + +### Implementing LibSass + +If you're interested in implementing LibSass in your own project see the [API Documentation](api-doc.md) which now includes implementing +your own [Sass functions](api-function.md). You may wish to [look at other implementations](implementations.md) for your language of choice. +Or make your own! + +### Contributing to LibSass + +| Issue Tracker | Issue Triage | Community Guidelines | +|-------------------|----------------------------------|-----------------------------| +| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [[Sass Community Guidelines|http://sass-lang.com/community-guidelines]]. Be nice and everyone else will be nice too! | + +Please refer to the steps on [Building LibSass](build.md) diff --git a/src/libsass/docs/api-context-example.md b/src/libsass/docs/api-context-example.md new file mode 100755 index 000000000..4f2a2a0ce --- /dev/null +++ b/src/libsass/docs/api-context-example.md @@ -0,0 +1,45 @@ +## Example main.c + +```C +#include +#include "sass/context.h" + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // configure some options ... + sass_option_set_precision(ctx_opt, 10); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +### Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: 21px * 2; }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + diff --git a/src/libsass/docs/api-context-internal.md b/src/libsass/docs/api-context-internal.md new file mode 100755 index 000000000..94fd62804 --- /dev/null +++ b/src/libsass/docs/api-context-internal.md @@ -0,0 +1,155 @@ +```C +// Input behaviours +enum Sass_Input_Style { + SASS_CONTEXT_NULL, + SASS_CONTEXT_FILE, + SASS_CONTEXT_DATA, + SASS_CONTEXT_FOLDER +}; + +// simple linked list +struct string_list { + string_list* next; + char* string; +}; + +// sass config options structure +struct Sass_Options { + + // Precision for fractional numbers + int precision; + + // Output style for the generated css code + // A value from above SASS_STYLE_* constants + enum Sass_Output_Style output_style; + + // Emit comments in the generated CSS indicating + // the corresponding source line. + bool source_comments; + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. LibSass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Note: It may be better to use + // array interface instead + char* include_path; + char* plugin_path; + + // Include paths (linked string list) + struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; + + // Path to source map file + // Enables source map generation + // Used to create sourceMappingUrl + char* source_map_file; + + // Directly inserted in source maps + char* source_map_root; + + // Custom functions that can be called from sccs code + Sass_C_Function_List c_functions; + + // Callback to overload imports + Sass_C_Import_Callback importer; + +}; + +// base for all contexts +struct Sass_Context : Sass_Options +{ + + // store context type info + enum Sass_Input_Style type; + + // generated output data + char* output_string; + + // generated source map json + char* source_map_string; + + // error status + int error_status; + char* error_json; + char* error_text; + char* error_message; + // error position + char* error_file; + size_t error_line; + size_t error_column; + + // report imported files + char** included_files; + +}; + +// struct for file compilation +struct Sass_File_Context : Sass_Context { + + // no additional fields required + // input_path is already on options + +}; + +// struct for data compilation +struct Sass_Data_Context : Sass_Context { + + // provided source string + char* source_string; + +}; + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + void* cpp_ctx; + // Sass::Block + void* root; +}; +``` + diff --git a/src/libsass/docs/api-context.md b/src/libsass/docs/api-context.md new file mode 100755 index 000000000..b023d4e00 --- /dev/null +++ b/src/libsass/docs/api-context.md @@ -0,0 +1,275 @@ +Sass Contexts come in two flavors: + +- `Sass_File_Context` +- `Sass_Data_Context` + +### Basic Usage + +```C +#include "sass/context.h" +``` + +***Sass_Options*** + +```C +// Precision for fractional numbers +int precision; +``` +```C +// Output style for the generated css code +// A value from above SASS_STYLE_* constants +int output_style; +``` +```C +// Emit comments in the generated CSS indicating +// the corresponding source line. +bool source_comments; +``` +```C +// embed sourceMappingUrl as data uri +bool source_map_embed; +``` +```C +// embed include contents in maps +bool source_map_contents; +``` +```C +// create file urls for sources +bool source_map_file_urls; +``` +```C +// Disable sourceMappingUrl in css output +bool omit_source_map_url; +``` +```C +// Treat source_string as sass (as opposed to scss) +bool is_indented_syntax_src; +``` +```C +// The input path is used for source map +// generating. It can be used to define +// something with string compilation or to +// overload the input file path. It is +// set to "stdin" for data contexts and +// to the input file on file contexts. +char* input_path; +``` +```C +// The output path is used for source map +// generating. LibSass will not write to +// this file, it is just used to create +// information in source-maps etc. +char* output_path; +``` +```C +// String to be used for indentation +const char* indent; +``` +```C +// String to be used to for line feeds +const char* linefeed; +``` +```C +// Colon-separated list of paths +// Semicolon-separated on Windows +char* include_path; +char* plugin_path; +``` +```C +// Additional include paths +// Must be null delimited +char** include_paths; +char** plugin_paths; +``` +```C +// Path to source map file +// Enables the source map generating +// Used to create sourceMappingUrl +char* source_map_file; +``` +```C +// Directly inserted in source maps +char* source_map_root; +``` +```C +// Custom functions that can be called from Sass code +Sass_C_Function_List c_functions; +``` +```C +// Callback to overload imports +Sass_C_Import_Callback importer; +``` + +***Sass_Context*** + +```C +// store context type info +enum Sass_Input_Style type; +```` +```C +// generated output data +char* output_string; +``` +```C +// generated source map json +char* source_map_string; +``` +```C +// error status +int error_status; +char* error_json; +char* error_text; +char* error_message; +// error position +char* error_file; +size_t error_line; +size_t error_column; +``` +```C +// report imported files +char** included_files; +``` + +***Sass_File_Context*** + +```C +// no additional fields required +// input_path is already on options +``` + +***Sass_Data_Context*** + +```C +// provided source string +char* source_string; +``` + +### Sass Context API + +```C +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Create and initialize an option struct +struct Sass_Options* sass_make_options (void); +// Create and initialize a specific context +struct Sass_File_Context* sass_make_file_context (const char* input_path); +struct Sass_Data_Context* sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +int sass_compile_file_context (struct Sass_File_Context* ctx); +int sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ctx); +struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// Usefull if you only want to query the included files +int sass_compiler_parse (struct Sass_Compiler* compiler); +int sass_compiler_execute (struct Sass_Compiler* compiler); + +// Release all memory allocated with the compiler +// This does _not_ include any contexts or options +void sass_delete_compiler (struct Sass_Compiler* compiler); +void sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +void sass_delete_file_context (struct Sass_File_Context* ctx); +void sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for Context from specific implementation +struct Sass_Context* sass_file_context_get_context (struct Sass_File_Context* file_ctx); +struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +struct Sass_Options* sass_context_get_options (struct Sass_Context* ctx); +struct Sass_Options* sass_file_context_get_options (struct Sass_File_Context* file_ctx); +struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +void sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +void sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + +// Getters for Sass_Context values +const char* sass_context_get_output_string (struct Sass_Context* ctx); +int sass_context_get_error_status (struct Sass_Context* ctx); +const char* sass_context_get_error_json (struct Sass_Context* ctx); +const char* sass_context_get_error_text (struct Sass_Context* ctx); +const char* sass_context_get_error_message (struct Sass_Context* ctx); +const char* sass_context_get_error_file (struct Sass_Context* ctx); +size_t sass_context_get_error_line (struct Sass_Context* ctx); +size_t sass_context_get_error_column (struct Sass_Context* ctx); +const char* sass_context_get_source_map_string (struct Sass_Context* ctx); +char** sass_context_get_included_files (struct Sass_Context* ctx); + +// Take ownership of memory (value on context is set to 0) +char* sass_context_take_error_json (struct Sass_Context* ctx); +char* sass_context_take_error_text (struct Sass_Context* ctx); +char* sass_context_take_error_message (struct Sass_Context* ctx); +char* sass_context_take_error_file (struct Sass_Context* ctx); +char* sass_context_take_output_string (struct Sass_Context* ctx); +char* sass_context_take_source_map_string (struct Sass_Context* ctx); + +// Push function for plugin/include paths (no manipulation support for now) +void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +void sass_option_push_include_path (struct Sass_Options* options, const char* path); +``` + +### Sass Options API + +```C +// Getters for Context_Option values +int sass_option_get_precision (struct Sass_Options* options); +enum Sass_Output_Style sass_option_get_output_style (struct Sass_Options* options); +bool sass_option_get_source_comments (struct Sass_Options* options); +bool sass_option_get_source_map_embed (struct Sass_Options* options); +bool sass_option_get_source_map_contents (struct Sass_Options* options); +bool sass_option_get_source_map_file_urls (struct Sass_Options* options); +bool sass_option_get_omit_source_map_url (struct Sass_Options* options); +bool sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +const char* sass_option_get_indent (struct Sass_Options* options); +const char* sass_option_get_linefeed (struct Sass_Options* options); +const char* sass_option_get_input_path (struct Sass_Options* options); +const char* sass_option_get_output_path (struct Sass_Options* options); +const char* sass_option_get_plugin_path (struct Sass_Options* options); +const char* sass_option_get_include_path (struct Sass_Options* options); +const char* sass_option_get_source_map_file (struct Sass_Options* options); +const char* sass_option_get_source_map_root (struct Sass_Options* options); +Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); +Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); + +// Setters for Context_Option values +void sass_option_set_precision (struct Sass_Options* options, int precision); +void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +void sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +void sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +void sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +void sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +void sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +void sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +void sass_option_set_indent (struct Sass_Options* options, const char* indent); +void sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +void sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +void sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +void sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +void sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +void sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +void sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +void sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); +void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); + +// Push function for paths (no manipulation support for now) +void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +void sass_option_push_include_path (struct Sass_Options* options, const char* path); +``` + +### More links + +- [Sass Context Example](api-context-example.md) +- [Sass Context Internal](api-context-internal.md) + diff --git a/src/libsass/docs/api-doc.md b/src/libsass/docs/api-doc.md new file mode 100755 index 000000000..b1393e07f --- /dev/null +++ b/src/libsass/docs/api-doc.md @@ -0,0 +1,182 @@ +## Introduction + +LibSass wouldn't be much good without a way to interface with it. These interface documentations describe the various functions and data structures available to implementers. They are split up over three major components, which have all their own source files (plus some common functionality). + +- [Sass Context](api-context.md) - Trigger and handle the main Sass compilation +- [Sass Value](api-value.md) - Exchange values and its format with LibSass +- [Sass Function](api-function.md) - Get invoked by LibSass for function statments +- [Sass Importer](api-importer.md) - Get invoked by LibSass for @import statments + +### Basic usage + +First you will need to include the header file! +This will automatically load all other headers too! + +```C +#include "sass/context.h" +``` + +## Basic C Example + +```C +#include +#include "sass/context.h" + +int main() { + puts(libsass_version()); + return 0; +} +``` + +```bash +gcc -Wall version.c -lsass -o version && ./version +``` + +## More C Examples + +- [Sample code for Sass Context](api-context-example.md) +- [Sample code for Sass Value](api-value-example.md) +- [Sample code for Sass Function](api-function-example.md) +- [Sample code for Sass Importer](api-importer-example.md) + +## Compiling your code + +The most important is your sass file (or string of sass code). With this, you will want to start a LibSass compiler. Here is some pseudocode describing the process. The compiler has two different modes: direct input as a string with `Sass_Data_Context` or LibSass will do file reading for you by using `Sass_File_Context`. See the code for a list of options available [Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) + +**Building a file compiler** + + context = sass_make_file_context("file.scss") + options = sass_file_context_get_options(context) + sass_option_set_precision(options, 1) + sass_option_set_source_comments(options, true) + + sass_file_context_set_options(context, options) + + compiler = sass_make_file_compiler(sass_context) + sass_compiler_parse(compiler) + sass_compiler_execute(compiler) + + output = sass_context_get_output_string(context) + // Retrieve errors during compilation + error_status = sass_context_get_error_status(context) + json_error = sass_context_get_error_json(context) + // Release memory dedicated to the C compiler + sass_delete_compiler(compiler) + +**Building a data compiler** + + context = sass_make_data_context("div { a { color: blue; } }") + options = sass_data_context_get_options(context) + sass_option_set_precision(options, 1) + sass_option_set_source_comments(options, true) + + sass_data_context_set_options(context, options) + + compiler = sass_make_data_compiler(context) + sass_compiler_parse(compiler) + sass_compiler_execute(compiler) + + output = sass_context_get_output_string(context) + // div a { color: blue; } + // Retrieve errors during compilation + error_status = sass_context_get_error_status(context) + json_error = sass_context_get_error_json(context) + // Release memory dedicated to the C compiler + sass_delete_compiler(compiler) + +## Sass Context Internals + +Everything is stored in structs: + +```C +struct Sass_Options; +struct Sass_Context : Sass_Options; +struct Sass_File_context : Sass_Context; +struct Sass_Data_context : Sass_Context; +``` + +This mirrors very well how `libsass` uses these structures. + +- `Sass_Options` holds everything you feed in before the compilation. It also hosts `input_path` and `output_path` options, because they are used to generate/calculate relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. +- `Sass_Context` holds all the data returned by the compilation step. +- `Sass_File_Context` is a specific implementation that requires no additional fields +- `Sass_Data_Context` is a specific implementation that adds the `input_source` field + +Structs can be down-casted to access `context` or `options`! + +## Memory handling and life-cycles + +We keep memory around for as long as the main [context](api-context.md) object is not destroyed (`sass_delete_context`). LibSass will create copies of most inputs/options beside the main sass code. +You need to allocate and fill that buffer before passing it to LibSass. You may also overtake memory management from libsass for certain return values (i.e. `sass_context_take_output_string`). + +```C +// to allocate buffer to be filled +void* sass_alloc_memory(size_t size); +// to allocate a buffer from existing string +char* sass_copy_c_string(const char* str); +// to free overtaken memory when done +void sass_free_memory(void* ptr); +``` + +## Miscellaneous API functions + +```C +// Some convenient string helper function +char* sass_string_unquote (const char* str); +char* sass_string_quote (const char* str, const char quote_mark); + +// Resolve a file via the given include paths in the include char* array +char* sass_resolve_file (const char* path, const char* incs[]); + +// Get compiled libsass version +const char* libsass_version(void); + +// Implemented sass language version +// Hardcoded version 3.4 for time being +const char* libsass_language_version(void); +``` + +## Common Pitfalls + +**input_path** + +The `input_path` is part of `Sass_Options`, but it also is the main option for `Sass_File_Context`. It is also used to generate relative file links in source-maps. Therefore it is pretty usefull to pass this information if you have a `Sass_Data_Context` and know the original path. + +**output_path** + +Be aware that `libsass` does not write the output file itself. This option merely exists to give `libsass` the proper information to generate links in source-maps. The file has to be written to the disk by the binding/implementation. If the `output_path` is omitted, `libsass` tries to extrapolate one from the `input_path` by replacing (or adding) the file ending with `.css`. + +## Error Codes + +The `error_code` is integer value which indicates the type of error that occurred inside the LibSass process. Following is the list of error codes along with the short description: + +* 1: normal errors like parsing or `eval` errors +* 2: bad allocation error (memory error) +* 3: "untranslated" C++ exception (`throw std::exception`) +* 4: legacy string exceptions ( `throw const char*` or `std::string` ) +* 5: Some other unknown exception + +Although for the API consumer, error codes do not offer much value except indicating whether *any* error occurred during the compilation, it helps debugging the LibSass internal code paths. + +## Real-World Implementations + +The proof is in the pudding, so we have highlighted a few implementations that should be on par with the latest LibSass interface version. Some of them may not have all features implemented! + +1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) +2. [Go Example](http://godoc.org/github.com/wellington/go-libsass#example-Context-Compile) +3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) + +## ABI forward compatibility + +We use a functional API to make dynamic linking more robust and future compatible. The API is not yet 100% stable, so we do not yet guarantee [ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward compatibility. We will do so, once we increase the shared library version above 1.0. + +## Plugins (experimental) + +LibSass can load plugins from directories. Just define `plugin_path` on context options to load all plugins from the given directories. To implement plugins, please consult the [[Wiki-Page for plugins|API-Plugins]]. + +## Internal Structs + +- [Sass Context Internals](api-context-internal.md) +- [Sass Value Internals](api-value-internal.md) +- [Sass Function Internals](api-function-internal.md) +- [Sass Importer Internals](api-importer-internal.md) diff --git a/src/libsass/docs/api-function-example.md b/src/libsass/docs/api-function-example.md new file mode 100755 index 000000000..0e41940ce --- /dev/null +++ b/src/libsass/docs/api-function-example.md @@ -0,0 +1,67 @@ +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) +{ + // get context/option struct associated with this compiler + struct Sass_Context* ctx = sass_compiler_get_context(comp); + struct Sass_Options* opts = sass_compiler_get_options(comp); + // get information about previous importer entry from the stack + struct Sass_Import* import = sass_compiler_get_last_import(comp); + const char* prev_abs_path = sass_import_get_abs_path(import); + const char* prev_imp_path = sass_import_get_imp_path(import); + // get the cookie from function descriptor + void* cookie = sass_function_get_cookie(cb); + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_Function_Entry fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +### Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + diff --git a/src/libsass/docs/api-function-internal.md b/src/libsass/docs/api-function-internal.md new file mode 100755 index 000000000..69d81d04d --- /dev/null +++ b/src/libsass/docs/api-function-internal.md @@ -0,0 +1,8 @@ +```C +// Struct to hold custom function callback +struct Sass_Function { + const char* signature; + Sass_Function_Fn function; + void* cookie; +}; +``` diff --git a/src/libsass/docs/api-function.md b/src/libsass/docs/api-function.md new file mode 100755 index 000000000..eeaa61a1d --- /dev/null +++ b/src/libsass/docs/api-function.md @@ -0,0 +1,50 @@ +Sass functions are used to define new custom functions callable by Sass code. They are also used to overload debug or error statements. You can also define a fallback function, which is called for every unknown function found in the Sass code. Functions get passed zero or more `Sass_Values` (a `Sass_List` value) and they must also return a `Sass_Value`. Return a `Sass_Error` if you want to signal an error. + +## Special signatures + +- `*` - Fallback implementation +- `@warn` - Overload warn statements +- `@error` - Overload error statements +- `@debug` - Overload debug statements + +Note: The fallback implementation will be given the name of the called function as the first argument, before all the original function arguments. These features are pretty new and should be considered experimental. + +### Basic Usage + +```C +#include "sass/functions.h" +``` + +## Sass Function API + +```C +// Forward declaration +struct Sass_Compiler; +struct Sass_Function; + +// Typedef helpers for custom functions lists +typedef struct Sass_Function (*Sass_Function_Entry); +typedef struct Sass_Function* (*Sass_Function_List); +// Typedef defining function signature and return type +typedef union Sass_Value* (*Sass_Function_Fn) + (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); + +// Creators for sass function list and function descriptors +ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); +ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); + +// Setters and getters for callbacks on function lists +ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); +ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); + +// Getters for custom function descriptors +ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); +ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); +ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); +``` + +### More links + +- [Sass Function Example](api-function-example.md) +- [Sass Function Internal](api-function-internal.md) + diff --git a/src/libsass/docs/api-importer-example.md b/src/libsass/docs/api-importer-example.md new file mode 100755 index 000000000..d83bf2609 --- /dev/null +++ b/src/libsass/docs/api-importer-example.md @@ -0,0 +1,112 @@ +## Example importer.c + +```C +#include +#include +#include "sass/context.h" + +Sass_Import_List sass_importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) +{ + // get the cookie from importer descriptor + void* cookie = sass_importer_get_cookie(cb); + Sass_Import_List list = sass_make_import_list(2); + char* local = sass_copy_c_string("local { color: green; }"); + char* remote = sass_copy_c_string("remote { color: red; }"); + list[0] = sass_make_import_entry("/tmp/styles.scss", local, 0); + list[1] = sass_make_import_entry("http://www.example.com", remote, 0); + return list; +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate custom importer + Sass_Importer_Entry c_imp = + sass_make_importer(sass_importer, 0, 0); + // create list for all custom importers + Sass_Importer_List imp_list = sass_make_importer_list(1); + // put only the importer on to the list + sass_importer_set_list_entry(imp_list, 0, c_imp); + // register list on to the context options + sass_option_set_c_importers(ctx_opt, imp_list); + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +Compile importer.c + +```bash +gcc -c importer.c -o importer.o +gcc -o importer importer.o -lsass +echo "@import 'foobar';" > importer.scss +./importer importer.scss +``` + +## Importer Behavior Examples + +```C +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass handle the import request + return NULL; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass handle the request + // swallows »@import "http://…"« pass-through + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry(path, 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // return an error to halt execution + Sass_Import_List list = sass_make_import_list(1); + const char* message = "some error message"; + list[0] = sass_make_import_entry(path, 0, 0); + sass_import_set_error(list[0], sass_copy_c_string(message), 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass load the file identifed by the importer + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry("/tmp/file.scss", 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // completely hide the import + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // completely hide the import + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry(0, 0, 0); + return list; +} +``` diff --git a/src/libsass/docs/api-importer-internal.md b/src/libsass/docs/api-importer-internal.md new file mode 100755 index 000000000..63d70fe75 --- /dev/null +++ b/src/libsass/docs/api-importer-internal.md @@ -0,0 +1,20 @@ +```C +// External import entry +struct Sass_Import { + char* imp_path; // path as found in the import statement + char *abs_path; // path after importer has resolved it + char* source; + char* srcmap; + // error handling + char* error; + size_t line; + size_t column; +}; + +// Struct to hold importer callback +struct Sass_Importer { + Sass_Importer_Fn importer; + double priority; + void* cookie; +}; +``` diff --git a/src/libsass/docs/api-importer.md b/src/libsass/docs/api-importer.md new file mode 100755 index 000000000..08ef6cd2e --- /dev/null +++ b/src/libsass/docs/api-importer.md @@ -0,0 +1,86 @@ +By using custom importers, Sass stylesheets can be implemented in any possible way, such as by being loaded via a remote server. Please note: this feature is experimental and is implemented differently than importers in Ruby Sass. Imports must be relative to the parent import context and therefore we need to pass this information to the importer callback. This is currently done by passing the complete import string/path of the previous import context. + +## Return Imports + +You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). + +```C +struct Sass_Import** rv = sass_make_import_list(1); +rv[0] = sass_make_import(rel, abs, source, srcmap); +``` + +Every import will then be included in LibSass. You are allowed to only return a file path without any loaded source. This way you can ie. implement rewrite rules for import paths and leave the loading part for LibSass. + +Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not deprecate the C-API once support has been implemented. It will be used to re-map the actual sourcemap with the provided ones. + +### Basic Usage + +```C +#include "sass/functions.h" +``` + +## Sass Importer API + +```C +// Forward declaration +struct Sass_Import; + +// Forward declaration +struct Sass_C_Import_Descriptor; + +// Typedef defining the custom importer callback +typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); +// Typedef defining the importer c function prototype +typedef struct Sass_Import** (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); + +// Creators for custom importer callback (with some additional pointer) +// The pointer is mostly used to store the callback into the actual function +Sass_C_Import_Callback sass_make_importer (Sass_C_Import_Fn, void* cookie); + +// Getters for import function descriptors +Sass_C_Import_Fn sass_import_get_function (Sass_C_Import_Callback fn); +void* sass_import_get_cookie (Sass_C_Import_Callback fn); + +// Deallocator for associated memory +void sass_delete_importer (Sass_C_Import_Callback fn); + +// Creator for sass custom importer return argument list +struct Sass_Import** sass_make_import_list (size_t length); +// Creator for a single import entry returned by the custom importer inside the list +struct Sass_Import* sass_make_import_entry (const char* path, char* source, char* srcmap); +struct Sass_Import* sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); + +// set error message to abort import and to print out a message (path from existing object is used in output) +struct Sass_Import* sass_import_set_error(struct Sass_Import* import, const char* message, size_t line, size_t col); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +void sass_import_set_list_entry (struct Sass_Import** list, size_t idx, struct Sass_Import* entry); +struct Sass_Import* sass_import_get_list_entry (struct Sass_Import** list, size_t idx); + +// Getters for import entry +const char* sass_import_get_rel_path (struct Sass_Import*); +const char* sass_import_get_abs_path (struct Sass_Import*); +const char* sass_import_get_source (struct Sass_Import*); +const char* sass_import_get_srcmap (struct Sass_Import*); +// Explicit functions to take ownership of these items +// The property on our struct will be reset to NULL +char* sass_import_take_source (struct Sass_Import*); +char* sass_import_take_srcmap (struct Sass_Import*); + +// Getters for import error entries +size_t sass_import_get_error_line (struct Sass_Import*); +size_t sass_import_get_error_column (struct Sass_Import*); +const char* sass_import_get_error_message (struct Sass_Import*); + +// Deallocator for associated memory (incl. entries) +void sass_delete_import_list (struct Sass_Import**); +// Just in case we have some stray import structs +void sass_delete_import (struct Sass_Import*); +``` + +### More links + +- [Sass Importer Example](api-importer-example.md) +- [Sass Importer Internal](api-importer-internal.md) + diff --git a/src/libsass/docs/api-value-example.md b/src/libsass/docs/api-value-example.md new file mode 100755 index 000000000..690654eaf --- /dev/null +++ b/src/libsass/docs/api-value-example.md @@ -0,0 +1,55 @@ +## Example operation.c + +```C +#include +#include +#include "sass/values.h" + +int main( int argc, const char* argv[] ) +{ + + // create two new sass values to be added + union Sass_Value* string = sass_make_string("String"); + union Sass_Value* number = sass_make_number(42, "nits"); + + // invoke the add operation which returns a new sass value + union Sass_Value* total = sass_value_op(ADD, string, number); + + // no further use for the two operands + sass_delete_value(string); + sass_delete_value(number); + + // this works since libsass will always return a + // string for add operations with a string as the + // left hand side. But you should never rely on it! + puts(sass_string_get_value(total)); + + // invoke stringification (uncompressed with precision of 5) + union Sass_Value* result = sass_value_stringify(total, false, 5); + + // no further use for the sum + sass_delete_value(total); + + // print the result - you may want to make + // sure result is indeed a string, altough + // stringify guarantees to return a string + // if (sass_value_is_string(result)) {} + // really depends on your level of paranoia + puts(sass_string_get_value(result)); + + // finally free result + sass_delete_value(result); + + // exit status + return 0; + +} +``` + +## Compile operation.c + +```bash +gcc -c operation.c -o operation.o +gcc -o operation operation.o -lsass +./operation # => String42nits +``` diff --git a/src/libsass/docs/api-value-internal.md b/src/libsass/docs/api-value-internal.md new file mode 100755 index 000000000..fed402256 --- /dev/null +++ b/src/libsass/docs/api-value-internal.md @@ -0,0 +1,76 @@ +```C +struct Sass_Unknown { + enum Sass_Tag tag; +}; + +struct Sass_Boolean { + enum Sass_Tag tag; + bool value; +}; + +struct Sass_Number { + enum Sass_Tag tag; + double value; + char* unit; +}; + +struct Sass_Color { + enum Sass_Tag tag; + double r; + double g; + double b; + double a; +}; + +struct Sass_String { + enum Sass_Tag tag; + char* value; +}; + +struct Sass_List { + enum Sass_Tag tag; + enum Sass_Separator separator; + size_t length; + // null terminated "array" + union Sass_Value** values; +}; + +struct Sass_Map { + enum Sass_Tag tag; + size_t length; + struct Sass_MapPair* pairs; +}; + +struct Sass_Null { + enum Sass_Tag tag; +}; + +struct Sass_Error { + enum Sass_Tag tag; + char* message; +}; + +struct Sass_Warning { + enum Sass_Tag tag; + char* message; +}; + +union Sass_Value { + struct Sass_Unknown unknown; + struct Sass_Boolean boolean; + struct Sass_Number number; + struct Sass_Color color; + struct Sass_String string; + struct Sass_List list; + struct Sass_Map map; + struct Sass_Null null; + struct Sass_Error error; + struct Sass_Warning warning; +}; + +struct Sass_MapPair { + union Sass_Value* key; + union Sass_Value* value; +}; +``` + diff --git a/src/libsass/docs/api-value.md b/src/libsass/docs/api-value.md new file mode 100755 index 000000000..ddf016c17 --- /dev/null +++ b/src/libsass/docs/api-value.md @@ -0,0 +1,152 @@ +`Sass_Values` are used to pass values and their types between the implementer +and LibSass. Sass knows various different value types (including nested arrays +and hash-maps). If you implement a binding to another programming language, you +have to find a way to [marshal] [1] (convert) `Sass_Values` between the target +language and C. `Sass_Values` are currently only used by custom functions, but +it should also be possible to use them without a compiler context. + +[1]: https://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 + +### Basic Usage + +```C +#include "sass/values.h" +``` + +```C +// Type for Sass values +enum Sass_Tag { + SASS_BOOLEAN, + SASS_NUMBER, + SASS_COLOR, + SASS_STRING, + SASS_LIST, + SASS_MAP, + SASS_NULL, + SASS_ERROR, + SASS_WARNING +}; + +// Tags for denoting Sass list separators +enum Sass_Separator { + SASS_COMMA, + SASS_SPACE, + // only used internally to represent a hash map before evaluation + // otherwise we would be too early to check for duplicate keys + SASS_HASH +}; + +// Value Operators +enum Sass_OP { + AND, OR, // logical connectives + EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations + ADD, SUB, MUL, DIV, MOD, // arithmetic functions + NUM_OPS // so we know how big to make the op table +}; +``` + +### Sass Value API + +```C +// Forward declaration +union Sass_Value; + +// Creator functions for all value types +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); + +// Generic destructor function for all types +// Will release memory of all associated Sass_Values +// Means we will delete recursively for lists and maps +void sass_delete_value (union Sass_Value* val); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) +union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); + +// Execute an operation for two Sass_Values and return the result as a Sass_Value too +union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); + +// Return the sass tag for a generic sass value +// Check is needed before accessing specific values! +enum Sass_Tag sass_value_get_tag (const union Sass_Value* v); + +// Check value to be of a specific type +// Can also be used before accessing properties! +bool sass_value_is_null (const union Sass_Value* v); +bool sass_value_is_number (const union Sass_Value* v); +bool sass_value_is_string (const union Sass_Value* v); +bool sass_value_is_boolean (const union Sass_Value* v); +bool sass_value_is_color (const union Sass_Value* v); +bool sass_value_is_list (const union Sass_Value* v); +bool sass_value_is_map (const union Sass_Value* v); +bool sass_value_is_error (const union Sass_Value* v); +bool sass_value_is_warning (const union Sass_Value* v); + +// Getters and setters for Sass_Number +double sass_number_get_value (const union Sass_Value* v); +void sass_number_set_value (union Sass_Value* v, double value); +const char* sass_number_get_unit (const union Sass_Value* v); +void sass_number_set_unit (union Sass_Value* v, char* unit); + +// Getters and setters for Sass_String +const char* sass_string_get_value (const union Sass_Value* v); +void sass_string_set_value (union Sass_Value* v, char* value); +bool sass_string_is_quoted(const union Sass_Value* v); +void sass_string_set_quoted(union Sass_Value* v, bool quoted); + +// Getters and setters for Sass_Boolean +bool sass_boolean_get_value (const union Sass_Value* v); +void sass_boolean_set_value (union Sass_Value* v, bool value); + +// Getters and setters for Sass_Color +double sass_color_get_r (const union Sass_Value* v); +void sass_color_set_r (union Sass_Value* v, double r); +double sass_color_get_g (const union Sass_Value* v); +void sass_color_set_g (union Sass_Value* v, double g); +double sass_color_get_b (const union Sass_Value* v); +void sass_color_set_b (union Sass_Value* v, double b); +double sass_color_get_a (const union Sass_Value* v); +void sass_color_set_a (union Sass_Value* v, double a); + +// Getter for the number of items in list +size_t sass_list_get_length (const union Sass_Value* v); +// Getters and setters for Sass_List +enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); +void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +// Getters and setters for Sass_List values +union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); +void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); + +// Getter for the number of items in map +size_t sass_map_get_length (const union Sass_Value* v); +// Getters and setters for Sass_Map keys and values +union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); +void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); +union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); +void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); + +// Getters and setters for Sass_Error +char* sass_error_get_message (const union Sass_Value* v); +void sass_error_set_message (union Sass_Value* v, char* msg); + +// Getters and setters for Sass_Warning +char* sass_warning_get_message (const union Sass_Value* v); +void sass_warning_set_message (union Sass_Value* v, char* msg); +``` + +### More links + +- [Sass Value Example](api-value-example.md) +- [Sass Value Internal](api-value-internal.md) + diff --git a/src/libsass/docs/build-on-darwin.md b/src/libsass/docs/build-on-darwin.md new file mode 100755 index 000000000..119a5350e --- /dev/null +++ b/src/libsass/docs/build-on-darwin.md @@ -0,0 +1,27 @@ +To install LibSass, make sure the OS X build tools are installed: + + xcode-select --install + +## Homebrew + +To install homebrew, see [http://brew.sh](http://brew.sh) + + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +You can install the latest version of LibSass quite easily with brew. + + brew install --HEAD libsass + +To update this, do: + + brew reinstall --HEAD libsass + +Brew will build static and shared libraries, and a `libsass.pc` file in `/usr/local/lib/pkgconfig`. + +To use `libsass.pc`, make sure this path is in your `PKG_CONFIG_PATH` + + export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig + +## Manually + +See the linux instructions [Building-with-autotools](build-with-autotools.md) or [Building-with-makefiles](build-with-makefiles.md) diff --git a/src/libsass/docs/build-on-gentoo.md b/src/libsass/docs/build-on-gentoo.md new file mode 100755 index 000000000..601b1fe5e --- /dev/null +++ b/src/libsass/docs/build-on-gentoo.md @@ -0,0 +1,55 @@ +Here are two ebuilds to compile LibSass and sassc on gentoo linux. If you do not know how to use these ebuilds, you should probably read the gentoo wiki page about [portage overlays](http://wiki.gentoo.org/wiki/Overlay). + +## www-misc/libsass/libsass-9999.ebuild +```ebuild +EAPI=4 + +inherit eutils git-2 autotools + +DESCRIPTION="A C/C++ implementation of a Sass compiler." +HOMEPAGE="http://libsass.org/" +EGIT_PROJECT='libsass' +EGIT_REPO_URI="https://github.com/sass/libsass.git" +LICENSE="MIT" +SLOT="0" +KEYWORDS="" +IUSE="" +DEPEND="" +RDEPEND="${DEPEND}" +DEPEND="${DEPEND}" + +pkg_pretend() { + # older gcc is not supported + local major=$(gcc-major-version) + local minor=$(gcc-minor-version) + [[ "${MERGE_TYPE}" != "binary" && ( $major > 4 || ( $major == 4 && $minor < 5 ) ) ]] && \ + die "Sorry, but gcc earlier than 4.5 will not work for LibSass." +} + +src_prepare() { + eautoreconf +} +``` + +## www-misc/sassc/sassc-9999.ebuild +```ebuild +EAPI=4 + +inherit eutils git-2 autotools + +DESCRIPTION="Command Line Tool for LibSass." +HOMEPAGE="http://libsass.org/" +EGIT_PROJECT='sassc' +EGIT_REPO_URI="https://github.com/sass/sassc.git" +LICENSE="MIT" +SLOT="0" +KEYWORDS="" +IUSE="" +DEPEND="www-misc/libsass" +RDEPEND="${DEPEND}" +DEPEND="${DEPEND}" + +src_prepare() { + eautoreconf +} +``` diff --git a/src/libsass/docs/build-on-windows.md b/src/libsass/docs/build-on-windows.md new file mode 100755 index 000000000..7c095fad0 --- /dev/null +++ b/src/libsass/docs/build-on-windows.md @@ -0,0 +1,139 @@ +We support builds via MingGW and via Visual Studio Community 2013. +Both should be considered experimental (MinGW was better tested)! + +## Building via MingGW (makefiles) + +First grab the latest [MinGW for windows] [1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. + +You need to have the following components installed: +![Visualization of components installed in the interface](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) + +Next we need to install [git for windows] [2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. + +If you want to run the spec test-suite you also need [ruby] [3] and a few gems available. Grab the [latest installer] [3] and make sure to add it the global path. Then install the missing gems: + +```bash +gem install minitest +``` + +### Mount the mingw root directory + +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: + +``` +C:\MinGW /mingw +``` + +### Starting a "MingGW" console + +Create a batch file with this content: +```bat +@echo off +set PATH=C:\MinGW\bin;%PATH% +REM only needed if not already available +set PATH=%PROGRAMFILES%\git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! + +### Get the sources + +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bat +set BUILD="shared" +``` + +### Compile the library +```bash +mingw32-make -C libsass +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.dll libsass.so +``` + +### Run the spec test-suite +```bash +mingw32-make -C libsass test_build +``` + +## Building via MingGW 64bit (makefiles) +Building libass to dll on window 64bit. + ++ downloads [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) , and unzip to "C:\mingw64". + ++ Create a batch file with this content: + +```bat +@echo off +set PATH=C:\mingw64\bin;%PATH% +set CC=gcc +REM only needed if not already available +set PATH=%PROGRAMFILES%\Git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + ++ By default , mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​" , we can modify Makefile to fix this:(add "-static") + +``` bash +lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(MKDIR) lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a +``` + ++ Compile the library + +```bash +mingw32-make -C libsass +``` + +By the way , if you are using java jna , [JNAerator](http://jnaerator.googlecode.com/) is a good tool. + +## Building via Visual Studio Community 2013 + +Open a Visual Studio 2013 command prompt: +- `VS2013 x86 Native Tools Command Prompt` + +Note: When I installed the community edition, I only got the 2012 command prompts. I copied them from the Startmenu to the Desktop and adjusted the paths from `Visual Studio 11.0` to `Visual Studio 12.0`. Since `libsass` uses some `C++11` features, you need at least a MSVC 2013 compiler (v120). + +### Get the source +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +git clone https://github.com/sass/sassc.git libsass/sassc +# only needed if you want to run the testsuite +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Compile sassc + +Sometimes `msbuild` seems not available from the command prompt. Just search for it and add it to the global path. It seems to be included in the .net folders too. + +```bat +cd libsass +REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin +msbuild /m:4 /p:Configuration=Release win\libsass.sln +REM running the spec test-suite manually (needs ruby and minitest gem) +ruby sass-spec\sass-spec.rb -V 3.4 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec +cd .. +``` + +[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files +[2]: https://msysgit.github.io/ +[3]: http://rubyinstaller.org/ diff --git a/src/libsass/docs/build-shared-library.md b/src/libsass/docs/build-shared-library.md new file mode 100755 index 000000000..3c143b46a --- /dev/null +++ b/src/libsass/docs/build-shared-library.md @@ -0,0 +1,35 @@ +This page is mostly intended for people that want to build a system library that gets distributed via RPMs or other means. This is currently in a experimental phase, as we currently do not really guarantee any ABI forward compatibility. The C API was rewritten to make this possible in the future, but we want to wait some more time till we can call this final and stable. + +Building via autotools +-- + +You want to build a system library only via autotools, since it will create the proper `libtool` files to make it loadable on multiple systems. We hope this works correctly, but nobody of the `libsass` core team has much knowledge in this area. Therefore we are open for comments or improvements by people that have more experience in that matter (like package maintainers from various linux distributions). + +```bash +apt-get install autoconf libtool +git clone https://github.com/sass/libsass.git +cd libsass +autoreconf --force --install +./configure \ + --disable-tests \ + --disable-static \ + --enable-shared \ + --prefix=/usr +make -j5 install +cd .. +``` + +This should install these files +```bash +# $ ls -la /usr/lib/libsass.* +/usr/lib/libsass.la +/usr/lib/libsass.so -> libsass.so.0.0.9 +/usr/lib/libsass.so.0 -> libsass.so.0.0.9 +/usr/lib/libsass.so.0.0.9 +# $ ls -la /usr/include/sass* +/usr/include/sass.h +/usr/include/sass2scss.h +/usr/include/sass/context.h +/usr/include/sass/functions.h +/usr/include/sass/values.h +``` diff --git a/src/libsass/docs/build-with-autotools.md b/src/libsass/docs/build-with-autotools.md new file mode 100755 index 000000000..a48ed18aa --- /dev/null +++ b/src/libsass/docs/build-with-autotools.md @@ -0,0 +1,78 @@ +### Get the sources +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Prerequisites + +In order to run autotools you need a few tools installed on your system. +```bash +yum install automake libtool # RedHat Linux +emerge -a automake libtool # Gentoo Linux +pkgin install automake libtool # SmartOS +``` + + +### Create configure script +```bash +cd libsass +autoreconf --force --install +cd .. +``` + +### Create custom makefiles +```bash +cd libsass +./configure \ + --disable-tests \ + --disable-shared \ + --prefix=/usr +cd .. +``` + +### Build the library +```bash +make -C libsass -j5 +``` + +### Install the library +The library will be installed to the location given as `prefix` to `configure`. This is standard behavior for autotools and not `libsass` specific. +```bash +make -C libsass -j5 install +``` + +### Configure options +The `configure` script is created by autotools. To get an overview of available options you can call `./configure --help`. When you execute this script, it will create specific makefiles, which you then use via the regular make command. + +There are some `libsass` specific options: + +``` +Optional Features: + --enable-tests enable testing the build + --enable-coverage enable coverage report for test suite + --enable-shared build shared libraries [default=yes] + --enable-static build static libraries [default=yes] + +Optional Packages: + --with-sassc-dir= specify directory of sassc sources for + testing (default: sassc) + --with-sass-spec-dir= specify directory of sass-spec for testing + (default: sass-spec) +``` + +### Build sassc and run spec test-suite + +```bash +cd libsass +autoreconf --force --install +./configure \ + --enable-tests \ + --enable-shared \ + --prefix=/usr +make -j5 test_build +cd .. +``` diff --git a/src/libsass/docs/build-with-makefiles.md b/src/libsass/docs/build-with-makefiles.md new file mode 100755 index 000000000..7ae2e33d6 --- /dev/null +++ b/src/libsass/docs/build-with-makefiles.md @@ -0,0 +1,68 @@ +### Get the sources +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bash +export BUILD="shared" +``` + +Alternatively you can also define it directly when calling make: + +```bash +BUILD="shared" make ... +``` + +### Compile the library +```bash +make -C libsass -j5 +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.so +``` + +### Install onto the system + +We recommend to use [autotools to install](build-with-autotools.md) libsass onto the +system, since that brings all the benefits of using libtools as the main install method. +If you still want to install libsass via the makefile, you need to make sure that gnu +`install` utility (or compatible) is installed on your system. +```bash +yum install coreutils # RedHat Linux +emerge -a coreutils # Gentoo Linux +pkgin install coreutils # SmartOS +``` + +You can set the install location by setting `PREFIX` +```bash +PREFIX="/opt/local" make install +``` + + +### Compling sassc + +```bash +# Let build know library location +export SASS_LIBSASS_PATH="`pwd`/libsass" +# Invokes the sassc makefile +make -C libsass -j5 sassc +``` + +### Run the spec test-suite + +```bash +# needs ruby available +# also gem install minitest +make -C libsass -j5 test_build +``` diff --git a/src/libsass/docs/build-with-mingw.md b/src/libsass/docs/build-with-mingw.md new file mode 100755 index 000000000..6d5f4b2fd --- /dev/null +++ b/src/libsass/docs/build-with-mingw.md @@ -0,0 +1,107 @@ +## Building LibSass with MingGW (makefiles) + +First grab the latest [MinGW for windows] [1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. + +You need to have the following components installed: +![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) + +Next we need to install [git for windows] [2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. + +If you want to run the spec test-suite you also need [ruby] [3] and a few gems available. Grab the [latest installer] [3] and make sure to add it the global path. Then install the missing gems: + +```bash +gem install minitest +``` + +### Mount the mingw root directory + +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: + +``` +C:\MinGW /mingw +``` + +### Starting a "MingGW" console + +Create a batch file with this content: +```bat +@echo off +set PATH=C:\MinGW\bin;%PATH% +REM only needed if not already available +set PATH=%PROGRAMFILES%\git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! + +### Get the sources + +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bat +set BUILD="shared" +``` + +### Compile the library +```bash +mingw32-make -C libsass +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.dll libsass.so +``` + +### Run the spec test-suite +```bash +mingw32-make -C libsass test_build +``` + +## Building via MingGW 64bit (makefiles) +Building libass to dll on window 64bit. + +Download [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) and unzip to "C:\mingw64". + +Create a batch file with this content: + +```bat +@echo off +set PATH=C:\mingw64\bin;%PATH% +set CC=gcc +REM only needed if not already available +set PATH=%PROGRAMFILES%\Git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +By default, mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​", we can modify Makefile to fix this:(add "-static") + +``` bash +lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(MKDIR) lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a +``` + +Compile the library + +```bash +mingw32-make -C libsass +``` + +By the way, if you are using java jna, [JNAerator](http://jnaerator.googlecode.com/) is a good tool. + +[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files +[2]: https://msysgit.github.io/ +[3]: http://rubyinstaller.org/ diff --git a/src/libsass/docs/build-with-visual-studio.md b/src/libsass/docs/build-with-visual-studio.md new file mode 100755 index 000000000..275b917b8 --- /dev/null +++ b/src/libsass/docs/build-with-visual-studio.md @@ -0,0 +1,90 @@ +## Building LibSass with Visual Studio + +### Requirements: + +The minimum requirement to build LibSass with Visual Studio is "Visual Studio 2013 Express for Desktop". + +Additionally, it is recommended to have `git` installed and available in `PATH`, so to deduce the `libsass` version information. For instance, if GitHub for Windows (https://windows.github.com/) is installed, the `PATH` will have an entry resembling: `X:\Users\\AppData\Local\GitHub\PortableGit_\cmd\` (where `X` is the drive letter of system drive). If `git` is not available, inquiring the LibSass version will result in `[NA]`. + +### Build Steps: + +#### From Visual Studio: + +On opening the `win\libsass.sln` solution and build (Ctrl+Shift+B) to build `libsass.dll`. + +To Build LibSass as a static Library, it is recommended to set an environment variable `LIBSASS_STATIC_LIB` before launching the project: + +```cmd +cd path\to\libsass +SET LIBSASS_STATIC_LIB=1 +:: +:: or in PowerShell: +:: $env:LIBSASS_STATIC_LIB=1 +:: +win\libsass.sln +``` + +Visual Studio will form the filtered source tree as shown below: + +![image](https://cloud.githubusercontent.com/assets/3840695/9298985/aae9e072-44bf-11e5-89eb-e7995c098085.png) + +`Header Files` contains the .h and .hpp files, while `Source Files` covers `.c` and `.cpp`. The other used headers/sources will appear under `External Dependencies`. + +If there is a LibSass code file appearing under External Dependencies, it can be changed by altering the `win\libsass.vcxproj.filters` file or dragging in Solution Explorer. + +#### From Command Prompt: + +Notice that in the following commands: + +* If the platform is 32-bit Windows, replace `ProgramFiles(x86)` with `ProgramFiles`. +* To build with Visual Studio 2015, replace `12.0` with `14.0` in the aforementioned command. + +Open a command prompt: + +To build dynamic/shared library (`libsass.dll`): + +```cmd +:: debug build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln + +:: release build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:Configuration=Release +``` + +To build static library (`libsass.lib`): + +```cmd +:: debug build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:LIBSASS_STATIC_LIB=1 + +:: release build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +``` + +#### From PowerShell: + +To build dynamic/shared library (`libsass.dll`): + +```powershell +# debug build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln + +# release build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:Configuration=Release +``` + +To build static library (`libsass.lib`): + +```powershell +# build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:LIBSASS_STATIC_LIB=1 + +# release build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +``` diff --git a/src/libsass/docs/build.md b/src/libsass/docs/build.md new file mode 100755 index 000000000..b8c7c8d41 --- /dev/null +++ b/src/libsass/docs/build.md @@ -0,0 +1,97 @@ +`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line] [6]. Or some [[bindings|Implementations]] to use it within your favorite programming language. You should be able to get [`sassc`] [6] running by following the instructions in this guide. + +Before starting, see [setup dev environment](setup-environment.md). + +Building on different Operating Systems +-- + +We try to keep the code as OS independent and standard compliant as possible. Reading files from the file-system has some OS depending code, but will ultimately fall back to a posix compatible implementation. We do use some `C++11` features, but are so far only committed to use `unordered_map`. This means you will need a pretty recent compiler on most systems (gcc 4.5 seems to be the minimum). + +### Building on Linux (and other *nix flavors) + +Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). + +- [Building with makefiles] [1] +- [Building with autotools] [2] + +### Building on Windows (experimental) + +Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! + +- [Building with MinGW] [3] +- [Building with Visual Studio] [11] + +### Building on Max OS X (untested) + +Works the same as on linux, but you can also install LibSass via `homebrew`. + +- [Building on Mac OS X] [10] + +### Building a system library (experimental) + +Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. + +- [Building shared system library] [4] + +Compiling with clang instead of gcc +-- + +To use clang you just need to set the appropriate environment variables: + +```bash +export CC=/usr/bin/clang +export CXX=/usr/bin/clang++ +``` + +Running the spec test-suite +-- + +We constantly and automatically test `libsass` against the official [spec test-suite] [5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`] [6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: + +```bash +ruby -v +gem install minitest +# should be optional +gem install minitap +``` + +Including the LibSass version +-- + +There is a function in `libsass` to query the current version. This has to be defined at compile time. We use a C macro for this, which can be defined by calling `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not need to do anything if you use the makefiles or autotools. They will try to fetch the version via git directly. If you only have the sources without the git repo, you can pass the version as an environment variable to `make` or `configure`: + +``` +export LIBSASS_VERSION="x.y.z." +``` + +Continuous Integration +-- + +We use two CI services to automatically test all commits against the latest [spec test-suite] [5]. + +- [LibSass on Travis-CI (linux)][7] +[![Build Status](https://travis-ci.org/sass/libsass.png?branch=master)](https://travis-ci.org/sass/libsass) +- [LibSass on AppVeyor (windows)][8] +[![Build status](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/mgreter/libsass-513/branch/master) + +Why not using CMake? +-- + +There were some efforts to get `libsass` to compile with CMake, which should make it easier to create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! + +Miscellaneous +-- + +- [Ebuilds for Gentoo Linux](build-on-gentoo.md) + +[1]: build-with-makefiles.md +[2]: build-with-autotools.md +[3]: build-with-mingw.md +[4]: build-shared-library.md +[5]: https://github.com/sass/sass-spec +[6]: https://github.com/sass/sassc +[7]: https://github.com/sass/libsass/blob/master/.travis.yml +[8]: https://github.com/sass/libsass/blob/master/appveyor.yml +[9]: implementations.md +[10]: build-on-darwin.md +[11]: build-with-visual-studio.md diff --git a/src/libsass/docs/compatibility-plan.md b/src/libsass/docs/compatibility-plan.md new file mode 100755 index 000000000..d8e538fa4 --- /dev/null +++ b/src/libsass/docs/compatibility-plan.md @@ -0,0 +1,48 @@ +This document is to serve as a living, changing plan for getting LibSass caught up with Ruby Sass. + +_Note: an "s" preceeding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ + +# Goal +**Our goal is to reach full s3.4 compatibility as soon as possible. LibSass version 3.4 will behave just like Ruby Sass 3.4** + +I highlight the goal, because there are some things that are *not* currently priorities. To be clear, they WILL be priorities, but they are not at the moment: + +* Performance Improvements +* Extensibility + +The overriding goal is correctness. + +## Verifying Correctness +LibSass uses the spec for its testing. The spec was originally based off s3.2 tests. Many things have changed in Ruby Sass since then and some of the tests need to be updated and changed in order to get them to match both LibSass and Ruby Sass. + +Until this project is complete, the spec will be primarily a place to test LibSass. By the time LibSass reaches 3.4, it is our goal that sass-spec will be fully usable as an official testing source for ALL implementations of Sass. + +## Version Naming +Until LibSass reaches parity with Ruby Sass, we will be aggressively bumping versions, and LibSass 3.4 will be the peer to Ruby Sass 3.4 in every way. + +# Release Plan + +## 3.0 +The goal of 3.0 is to introduce some of the most demanded features for LibSass. That is, we are focusing on issues and features that have kept adoption down. This is a mongrel release wrt which version of Sass it's targeting. It's often a mixture of 3.2 / 3.3 / 3.4 behaviours. This is not ideal, but it's favourable to not existing. Targeting 3.4 strictly during this release would mean we never actually release. + +# 3.1 +The goal of 3.1 is to update all the passing specs to agree with 3.4. This will not be a complete representation of s3.4 (aka, there will me missing features), but the goal is to change existing features and implemented features to match 3.4 behaviour. + +By the end of this, the sass-spec must pass against 3.4. + +Major issues: +* Variable Scoping +* Color Handling +* Precision + +# 3.2 +This version will focus on edge case fixes. There are a LOT of edge cases in the _todo_ tests and this is the release where we hunt those down like dogs (not that we want to hurt dogs, it's just a figure of speech in English). + +# 3.3 +Dress rehearsal. When we are 99% sure that we've fixed the main issues keeping us from saying we are compliant in s3.4 behaviour. + +# 3.4 +Compass Compatibility. We need to be able to work with Compass and all the other libraries out there. At this point, we are calling LibSass "mature" + +# Beyond 3.4 +Obviously, there is matching Sass 3.5 behaviour. But, beyond that, we'll want to focus on performance, stability, and error handling. These can always be improved upon and are the life's work of an open source project. We'll have to work closely with Sass in the future. diff --git a/src/libsass/docs/contributing.md b/src/libsass/docs/contributing.md new file mode 100755 index 000000000..4a2d470ef --- /dev/null +++ b/src/libsass/docs/contributing.md @@ -0,0 +1,17 @@ +First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! + +## I'm a programmer + +Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! + +To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). + +## I'm not a backend programmer + +COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. + +Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. + +## I don't know what a computer is? + +Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/src/libsass/docs/custom-functions-internal.md b/src/libsass/docs/custom-functions-internal.md new file mode 100755 index 000000000..1c19965c6 --- /dev/null +++ b/src/libsass/docs/custom-functions-internal.md @@ -0,0 +1,120 @@ +# Developer Documentation + +Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. + +## Sass_C_Function_Descriptor + +```C +struct Sass_C_Function_Descriptor { + const char* signature; + Sass_C_Function function; + void* cookie; +}; +``` + +- `signature`: The function declaration, like `foo($bar, $baz:1)` +- `function`: Reference to the C function callback +- `cookie`: any pointer you want to attach + +### signature + +The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. + + foo($bar, $baz: 2) + +In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. + +### function + +The callback function needs to be of the following form: + +```C +union Sass_Value* call_sass_function( + const union Sass_Value* s_args, + void* cookie +) { + return sass_clone_value(s_args); +} +``` + +### cookie + +The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! + +## Sass_Values + +```C +// allocate memory (copies passed strings) +union Sass_Value* make_sass_boolean (int val); +union Sass_Value* make_sass_number (double val, const char* unit); +union Sass_Value* make_sass_color (double r, double g, double b, double a); +union Sass_Value* make_sass_string (const char* val); +union Sass_Value* make_sass_list (size_t len, enum Sass_Separator sep); +union Sass_Value* make_sass_map (size_t len); +union Sass_Value* make_sass_null (); +union Sass_Value* make_sass_error (const char* msg); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// deallocate memory (incl. all copied memory) +void sass_delete_value (const union Sass_Value* val); +``` + +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((size_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +## Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` diff --git a/src/libsass/docs/dev-ast-memory.md b/src/libsass/docs/dev-ast-memory.md new file mode 100755 index 000000000..31004bcf2 --- /dev/null +++ b/src/libsass/docs/dev-ast-memory.md @@ -0,0 +1,223 @@ +# LibSass smart pointer implementation + +LibSass uses smart pointers very similar to `shared_ptr` known +by Boost or C++11. Implementation is a bit less modular since +it was not needed. Various compile time debug options are +available if you need to debug memory life-cycles. + + +## Memory Classes + +### SharedObj + +Base class for the actual node implementations. This ensures +that every object has a reference counter and other values. + +```c++ +class AST_Node : public SharedObj { ... }; +``` + +### SharedPtr (base class for SharedImpl) + +Base class that holds on to the pointer. The reference counter +is stored inside the pointer object directly (`SharedObj`). + +### SharedImpl (inherits from SharedPtr) + +This is the main base class for objects you use in your code. It +will make sure that the memory it points at will be deleted once +all copies to the same object/memory go out of scope. + +```c++ +Class* pointer = new Class(...); +SharedImpl obj(pointer); +``` + +To spare the developer of typing the templated class every time, +we created typedefs for each available AST Node specialization. + +```c++ +typedef SharedImpl Number_Obj; +Number_Obj number = SASS_MEMORY_NEW(...); +``` + + +## Memory life-cycles + +### Pointer pickups + +I often use the terminology of "pickup". This means the moment when +a raw pointer not under any control is assigned to a reference counted +object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be +automatically released once the object goes out of scope (but only +if the reference counter reaches zero). Main point beeing, you don't +have to worry about memory management yourself. + +### Object detach + +Sometimes we can't return reference counted objects directly (see +invalid covariant return types problems below). But we often still +need to use reference objects inside a function to avoid leaks when +something throws. For this you can use `detach`, which basically +detaches the pointer memory from the reference counted object. So +when the reference counted object goes out of scope, it will not +free the attached memory. You are now again in charge of freeing +the memory (just assign it to a reference counted object again). + + +## Circular references + +Reference counted memory implementations are prone to circular references. +This can be addressed by using a multi generation garbage collector. But +for our use-case that seems overkill. There is no way so far for users +(sass code) to create circular references. Therefore we can code around +this possible issue. But developers should be aware of this limitation. + +There are AFAIR two places where circular references could happen. One is +the `sources` member on every `Selector`. The other one can happen in the +extend code (Node handling). The easy way to avoid this is to only assign +complete object clones to these members. If you know the objects lifetime +is longer than the reference you create, you can also just store the raw +pointer. Once needed this could be solved with weak pointers. + + +## Addressing the invalid covariant return types problems + +If you are not familiar with the mentioned problem, you may want +to read up on covariant return types and virtual functions, i.e. + +- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers +- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers +- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr + +We hit this issue at least with the CRTP visitor pattern (eval, expand, +listize and so forth). This means we cannot return reference counted +objects directly. We are forced to return raw pointers or we would need +to have a lot of explicit and expensive upcasts by callers/consumers. + +### Simple functions that allocate new AST Nodes + +In the parser step we often create new objects and can just return a +unique pointer (meaning ownership clearly shifts back to the caller). +The caller/consumer is responsible that the memory is freed. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + return 42; +} +Number_Ptr parse_number() { + Number_Ptr p_nr = SASS_MEMORY_NEW(...); + p_nr->value(parse_integer()); + return p_nr; +} +Number_Obj nr = parse_number(); +``` + +The above would be the encouraged pattern for such simple cases. + +### Allocate new AST Nodes in functions that can throw + +There is a major caveat with the previous example, considering this +more real-life implementation that throws an error. The throw may +happen deep down in another function. Holding raw pointers that +we need to free would leak in this case. + +```c++ +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +``` + +With this `parse_integer` function the previous example would leak memory. +I guess it is pretty obvious, as the allocated memory will not be freed, +as it was never assigned to a SharedObj value. Therefore the above code +would better be written as: + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +// this leaks due to pointer return +// should return Number_Obj instead +// though not possible for virtuals! +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return &nr; // Ptr from Obj +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + +The example above unfortunately will not work as is, since we return a +`Number_Ptr` from that function. Therefore the object allocated inside +the function is already gone when it is picked up again by the caller. +The easy fix for the given simplified use case would be to change the +return type of `parse_number` to `Number_Obj`. Indeed we do it exactly +this way in the parser. But as stated above, this will not work for +virtual functions due to invalid covariant return types! + +### Return managed objects from virtual functions + +The easy fix would be to just create a new copy on the heap and return +that. But this seems like a very inelegant solution to this problem. I +mean why can't we just tell the object to treat it like a newly allocated +object? And indeed we can. I've added a `detach` method that will tell +the object to survive deallocation until the next pickup. This means +that it will leak if it is not picked up by consumer. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return nr.detach(); +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + + +## Compile time debug options + +To enable memory debugging you need to define `DEBUG_SHARED_PTR`. +This can i.e. be done in `include/sass/base.h` + +```c++ +define DEBUG_SHARED_PTR +``` + +This will print lost memory on exit to stderr. You can also use +`setDbg(true)` on sepecific variables to emit reference counter +increase, decrease and other events. + + +## Why reinvent the wheel when there is `shared_ptr` from C++11 + +First, implementing a smart pointer class is not really that hard. It +was indeed also a learning experience for myself. But there are more +profound advantages: + +- Better GCC 4.4 compatibility (which most code still has OOTB) +- Not thread safe (give us some free performance on some compiler) +- Beeing able to track memory allocations for debugging purposes +- Adding additional features if needed (as seen in `detach`) +- Optional: optimized weak pointer implementation possible + +### Thread Safety + +As said above, this is not thread safe currently. But we don't need +this ATM anyway. And I guess we probably never will share AST Nodes +across different threads. \ No newline at end of file diff --git a/src/libsass/docs/implementations.md b/src/libsass/docs/implementations.md new file mode 100755 index 000000000..4321558c3 --- /dev/null +++ b/src/libsass/docs/implementations.md @@ -0,0 +1,53 @@ +There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. + +### C +* [sassc](https://github.com/hcatlin/sassc) + +### Elixir +* [sass.ex](https://github.com/scottdavis/sass.ex) + +### Go +* [go-libsass](https://github.com/wellington/go-libsass) +* [go_sass](https://github.com/suapapa/go_sass) +* [go-sass](https://github.com/SamWhited/go-sass) + +### Lua +* [lua-sass](https://github.com/craigbarnes/lua-sass) + +### .NET +* [libsass-net](https://github.com/darrenkopp/libsass-net) +* [NSass](https://github.com/TBAPI-0KA/NSass) +* [Sass.Net](https://github.com/andyalm/Sass.Net) + +### node.js +* [node-sass](https://github.com/andrew/node-sass) + +### Java +* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) +* [jsass](https://github.com/bit3/jsass) + +### JavaScript +* [sass.js](https://github.com/medialize/sass.js) + +### Perl +* [CSS::Sass](https://github.com/caldwell/CSS-Sass) +* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) + +### PHP +* [sassphp](https://github.com/sensational/sassphp) +* [php-sass](https://github.com/lesstif/php-sass) + +### Python +* [libsass-python](https://github.com/dahlia/libsass-python) +* [SassPython](https://github.com/marianoguerra/SassPython) +* [pylibsass](https://github.com/rsenk330/pylibsass) +* [python-scss](https://github.com/pistolero/python-scss) + +### Ruby +* [sassruby](https://github.com/hcatlin/sassruby) + +### Scala +* [Sass-Scala](https://github.com/kkung/Sass-Scala) + +### Tcl +* [tclsass](https://github.com/flightaware/tclsass) diff --git a/src/libsass/docs/plugins.md b/src/libsass/docs/plugins.md new file mode 100755 index 000000000..a9711e3e1 --- /dev/null +++ b/src/libsass/docs/plugins.md @@ -0,0 +1,47 @@ +Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). + +## plugin.cpp + +```C++ +#include +#include +#include +#include "sass_values.h" + +union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, fn_foo); + // return the list + return fn_list; +} +``` + +To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). + +## Compile with gcc on linux + +```bash +g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +``` + +## Compile with mingw on windows + +```bash +g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass +``` diff --git a/src/libsass/docs/setup-environment.md b/src/libsass/docs/setup-environment.md new file mode 100755 index 000000000..805613656 --- /dev/null +++ b/src/libsass/docs/setup-environment.md @@ -0,0 +1,68 @@ +## Requirements +In order to install and setup your local development environment, there are some prerequisites: + +* git +* gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) +* ruby w/ bundler + +OS X: +First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: + +```` +xcode-select --install +```` + +## Cloning the Projects + +First, clone the project and then add a line to your `~/.bash_profile` that will let other programs know where the LibSass dev files are. + +```` +git clone git@github.com:sass/libsass.git +cd libsass +echo "export SASS_LIBSASS_PATH=$(pwd)" >> ~/.bash_profile + +```` + +Then, if you run the "bootstrap" script, it should clone all the other required projects. + +```` +./script/bootstrap +```` + +You should now have a `sass-spec` and `sassc` folder within the libsass folder. Both of these are clones of their respective git projects. If you want to do a pull request, remember to work in those folders. For instance, if you want to add a test (see other documentation for how to do that), make sure to commit it to your *fork* of the sass-spec github project. Also, whenever you are running tests, make sure to `pull` from the origin! We want to make sure we are testing against the newest libsass, sassc, and sass-spec! + +Now, try and see if you can build the project. We do that with the `make` command. + +```` +make +```` + +At this point, if you get an error, something is most likely wrong with your compiler installation. Yikes. It's hard to cover how to fix this in an article. Feel free to open an issue and we'll try and help! But, remember, before you do that, googling the error message is your friend! Many problems are solved quickly that way. + +## Running The Spec Against LibSass + +Then, to run the spec against LibSass, just run: + +```` +./script/spec +```` + +If you get an error about `SASS_LIBSASS_PATH`, you may still need to set a variable pointing to the libsass folder, like this: + +```` +export SASS_LIBSASS_PATH=/Users/you/path/libsass +```` + +...where the latter part is to the `libsass` directory you've cloned. You can get this path by typing `pwd` in the Terminal + +## Running the Spec Against Ruby Sass + +Go into the sass-spec folder that should have been cloned earlier with the "bootstrap" command. Run the following. + +```` +bundle install +./sass-spec.rb +```` + +Voila! Now you are testing against Sass too! + diff --git a/src/libsass/docs/source-map-internals.md b/src/libsass/docs/source-map-internals.md new file mode 100755 index 000000000..50f83b54f --- /dev/null +++ b/src/libsass/docs/source-map-internals.md @@ -0,0 +1,51 @@ +This document is mainly intended for developers! + +# Documenting some of the source map internals + +Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. + +The main storage for SourceMap mappings is the `mappings` vector: + +``` +# in source_map.hpp +vector mappings +# in mappings.hpp +struct Mapping ... + Position original_position; + Position generated_position; +``` + +## Every parsed token has its source associated + +LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: + +``` +if (lex< custom_property_name >()) { + Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); + return new (ctx.mem) Declaration(path, prop->position(), prop, ...); +} +``` + +## How is the `source_position` calculated + +This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! + +``` +lex< exactly < empty_str > >(); +end = new (ctx.mem) String_Constant(path, source_position, lexed); +``` + +## How are mappings for the output created + +So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: + +``` +# in source_map.hpp +void add_mapping(AST_Node* node); +``` + +This method is called in two places: +- `Inspect::append_to_buffer` +- `Output_[Nested|Compressed]::append_to_buffer` + +Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/src/libsass/docs/trace.md b/src/libsass/docs/trace.md new file mode 100755 index 000000000..4a57c901f --- /dev/null +++ b/src/libsass/docs/trace.md @@ -0,0 +1,26 @@ +## This is proposed interface in https://github.com/sass/libsass/pull/1288 + +Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. + +Both macros simulate a string stream, so they can be used like this: + + TRACE() << "Reached."; + +produces: + + [LibSass] parse_value parser.cpp:1384 Reached. + +`TRACE()` + logs function name, source filename, source file name to the standard error and the attached + stream to the standard error. + +`TRACEINST(obj)` + logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: + + TRACEINST(this) << "String_Constant created " << this; + +produces: + + [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") + +The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/src/libsass/docs/triage.md b/src/libsass/docs/triage.md new file mode 100755 index 000000000..0fc11784c --- /dev/null +++ b/src/libsass/docs/triage.md @@ -0,0 +1,17 @@ +This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: + +1. Issue is reported by a user. +2. If the issue seems like a bug, then the "bug" tag is added. +3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. +4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. +5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. +6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! +7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. +8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". +9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". +10. A C++ developer will then work on the issue and issue a pull request to fix the issue. +11. A core member verifies that the fix does actually fix the spec tests. +12. The fix is merged into the project. +13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues +14. The issue is closed +15. Have a soda pop or enjoyable beverage of your choice diff --git a/src/libsass/docs/unicode.md b/src/libsass/docs/unicode.md new file mode 100755 index 000000000..3897dcd6c --- /dev/null +++ b/src/libsass/docs/unicode.md @@ -0,0 +1,39 @@ +LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. + +### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) + +This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I solved that by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. + +Since my tool is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). + +### Current status on LibSass unicode support + +Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv). + +### Current encoding auto detection + +LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). + +### What is currently not supported + +- Using non ASCII compatible encodings (like UTF-16) +- Using non ASCII characters in different encodings in different includes + +### What is missing to support the above cases + +- A way to convert between encodings (like libiconv) +- Sniffing the charset inside the file (source is available) +- Handling the conversion on import (and export) +- Optional: Make output encoding configurable +- Optional: Add optional/mandatory BOM (configurable) + +### Low priority feature + +I guess the current implementation should handle more than 99% of all real world use cases. +A) Unicode characters are still seldomly seen (as they can be written escaped) +B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. +Although I'm not sure how this applies to asian and other "exotic" codepages! + +I guess the biggest Problem is to have libiconv (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). + +I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/src/libsass/extconf.rb b/src/libsass/extconf.rb new file mode 100755 index 000000000..3e6d00bc9 --- /dev/null +++ b/src/libsass/extconf.rb @@ -0,0 +1,6 @@ +require 'mkmf' +# .. more stuff +#$LIBPATH.push(Config::CONFIG['libdir']) +$CFLAGS << " #{ENV["CFLAGS"]}" +$LIBS << " #{ENV["LIBS"]}" +create_makefile("libsass") diff --git a/src/libsass/include/sass.h b/src/libsass/include/sass.h new file mode 100755 index 000000000..1dd8b06dc --- /dev/null +++ b/src/libsass/include/sass.h @@ -0,0 +1,15 @@ +#ifndef SASS_H +#define SASS_H + +// #define DEBUG 1 + +// include API headers +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/src/libsass/include/sass/base.h b/src/libsass/include/sass/base.h new file mode 100755 index 000000000..d88af927f --- /dev/null +++ b/src/libsass/include/sass/base.h @@ -0,0 +1,92 @@ +#ifndef SASS_BASE_H +#define SASS_BASE_H + +// #define DEBUG_SHARED_PTR + +#ifdef _MSC_VER + #pragma warning(disable : 4503) + #ifndef _SCL_SECURE_NO_WARNINGS + #define _SCL_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#include +#include + +#ifdef __GNUC__ + #define DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) + #define DEPRECATED(func) __declspec(deprecated) func +#else + #pragma message("WARNING: You need to implement DEPRECATED for this compiler") + #define DEPRECATED(func) func +#endif + +#ifdef _WIN32 + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #define ADDCALL __cdecl + #else + #define ADDAPI + #define ADDCALL + #endif + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +/* Make sure functions are exported with C linkage under C++ compilers. */ +#ifdef __cplusplus +extern "C" { +#endif + + +// Different render styles +enum Sass_Output_Style { + SASS_STYLE_NESTED, + SASS_STYLE_EXPANDED, + SASS_STYLE_COMPACT, + SASS_STYLE_COMPRESSED, + // only used internaly + SASS_STYLE_INSPECT, + SASS_STYLE_TO_SASS +}; + +// to allocate buffer to be filled +ADDAPI void* ADDCALL sass_alloc_memory(size_t size); +// to allocate a buffer from existing string +ADDAPI char* ADDCALL sass_copy_c_string(const char* str); +// to free overtaken memory when done +ADDAPI void ADDCALL sass_free_memory(void* ptr); + +// Some convenient string helper function +ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); +ADDAPI char* ADDCALL sass_string_unquote (const char* str); + +// Resolve a file via the given include paths in the include char* array +ADDAPI char* ADDCALL sass_resolve_file (const char* path, const char* incs[]); + +// Implemented sass language version +// Hardcoded version 3.4 for time being +ADDAPI const char* ADDCALL libsass_version(void); + +// Get compiled libsass language +ADDAPI const char* ADDCALL libsass_language_version(void); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/src/libsass/include/sass/context.h b/src/libsass/include/sass/context.h new file mode 100755 index 000000000..0a913c02d --- /dev/null +++ b/src/libsass/include/sass/context.h @@ -0,0 +1,155 @@ +#ifndef SASS_C_CONTEXT_H +#define SASS_C_CONTEXT_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; // base struct +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// Create and initialize an option struct +ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); +// Create and initialize a specific context +ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); +ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); +ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// Usefull if you only want to query the included files +ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); +ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); + +// Release all memory allocated with the compiler +// This does _not_ include any contexts or options +ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); +ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); +ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for context from specific implementation +ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); +ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + + +// Getters for Context_Option values +ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); +ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_plugin_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_include_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); +ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); + +// Setters for Context_Option values +ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); +ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); +ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); +ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); +ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); + + +// Getters for Sass_Context values +ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); +ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); + +// Calculate the size of the stored null terminated array +ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); + +// Take ownership of memory (value on context is set to 0) +ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); + +// Getters for Sass_Compiler options +ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); +ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); +ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); +ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); + +// Push function for paths (no manipulation support for now) +ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/src/libsass/include/sass/functions.h b/src/libsass/include/sass/functions.h new file mode 100755 index 000000000..2eeb39c4c --- /dev/null +++ b/src/libsass/include/sass/functions.h @@ -0,0 +1,108 @@ +#ifndef SASS_C_FUNCTIONS_H +#define SASS_C_FUNCTIONS_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +struct Sass_Import; +struct Sass_Options; +struct Sass_Compiler; +struct Sass_Importer; +struct Sass_Function; + +// Typedef helpers for import lists +typedef struct Sass_Import (*Sass_Import_Entry); +typedef struct Sass_Import* (*Sass_Import_List); +// Typedef helpers for custom importer lists +typedef struct Sass_Importer (*Sass_Importer_Entry); +typedef struct Sass_Importer* (*Sass_Importer_List); +// Typedef defining importer signature and return type +typedef Sass_Import_List (*Sass_Importer_Fn) + (const char* url, Sass_Importer_Entry cb, struct Sass_Compiler* compiler); + +// Typedef helpers for custom functions lists +typedef struct Sass_Function (*Sass_Function_Entry); +typedef struct Sass_Function* (*Sass_Function_List); +// Typedef defining function signature and return type +typedef union Sass_Value* (*Sass_Function_Fn) + (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); + + +// Creator for sass custom importer return argument list +ADDAPI Sass_Importer_List ADDCALL sass_make_importer_list (size_t length); +ADDAPI Sass_Importer_Entry ADDCALL sass_importer_get_list_entry (Sass_Importer_List list, size_t idx); +ADDAPI void ADDCALL sass_importer_set_list_entry (Sass_Importer_List list, size_t idx, Sass_Importer_Entry entry); + + +// Creators for custom importer callback (with some additional pointer) +// The pointer is mostly used to store the callback into the actual binding +ADDAPI Sass_Importer_Entry ADDCALL sass_make_importer (Sass_Importer_Fn importer, double priority, void* cookie); + +// Getters for import function descriptors +ADDAPI Sass_Importer_Fn ADDCALL sass_importer_get_function (Sass_Importer_Entry cb); +ADDAPI double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb); +ADDAPI void* ADDCALL sass_importer_get_cookie (Sass_Importer_Entry cb); + +// Deallocator for associated memory +ADDAPI void ADDCALL sass_delete_importer (Sass_Importer_Entry cb); + +// Creator for sass custom importer return argument list +ADDAPI Sass_Import_List ADDCALL sass_make_import_list (size_t length); +// Creator for a single import entry returned by the custom importer inside the list +ADDAPI Sass_Import_Entry ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); +ADDAPI Sass_Import_Entry ADDCALL sass_make_import (const char* imp_path, const char* abs_base, char* source, char* srcmap); +// set error message to abort import and to print out a message (path from existing object is used in output) +ADDAPI Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); +ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx); + +// Getters for import entry +ADDAPI const char* ADDCALL sass_import_get_imp_path (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_abs_path (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_source (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_srcmap (Sass_Import_Entry); +// Explicit functions to take ownership of these items +// The property on our struct will be reset to NULL +ADDAPI char* ADDCALL sass_import_take_source (Sass_Import_Entry); +ADDAPI char* ADDCALL sass_import_take_srcmap (Sass_Import_Entry); +// Getters from import error entry +ADDAPI size_t ADDCALL sass_import_get_error_line (Sass_Import_Entry); +ADDAPI size_t ADDCALL sass_import_get_error_column (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_error_message (Sass_Import_Entry); + +// Deallocator for associated memory (incl. entries) +ADDAPI void ADDCALL sass_delete_import_list (Sass_Import_List); +// Just in case we have some stray import structs +ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry); + + + +// Creators for sass function list and function descriptors +ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); +ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); + +// Setters and getters for callbacks on function lists +ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); +ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); + +// Getters for custom function descriptors +ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); +ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); +ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); + + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/src/libsass/include/sass/values.h b/src/libsass/include/sass/values.h new file mode 100755 index 000000000..c00d091ec --- /dev/null +++ b/src/libsass/include/sass/values.h @@ -0,0 +1,143 @@ +#ifndef SASS_C_VALUES_H +#define SASS_C_VALUES_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +union Sass_Value; + +// Type for Sass values +enum Sass_Tag { + SASS_BOOLEAN, + SASS_NUMBER, + SASS_COLOR, + SASS_STRING, + SASS_LIST, + SASS_MAP, + SASS_NULL, + SASS_ERROR, + SASS_WARNING +}; + +// Tags for denoting Sass list separators +enum Sass_Separator { + SASS_COMMA, + SASS_SPACE, + // only used internally to represent a hash map before evaluation + // otherwise we would be too early to check for duplicate keys + SASS_HASH +}; + +// Value Operators +enum Sass_OP { + AND, OR, // logical connectives + EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations + ADD, SUB, MUL, DIV, MOD, // arithmetic functions + NUM_OPS // so we know how big to make the op table +}; + +// Creator functions for all value types +ADDAPI union Sass_Value* ADDCALL sass_make_null (void); +ADDAPI union Sass_Value* ADDCALL sass_make_boolean (bool val); +ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); +ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); +ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); +ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); +ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep); +ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); +ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); +ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg); + +// Generic destructor function for all types +// Will release memory of all associated Sass_Values +// Means we will delete recursively for lists and maps +ADDAPI void ADDCALL sass_delete_value (union Sass_Value* val); + +// Make a deep cloned copy of the given sass value +ADDAPI union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val); + +// Execute an operation for two Sass_Values and return the result as a Sass_Value too +ADDAPI union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); + +// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) +ADDAPI union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); + +// Return the sass tag for a generic sass value +// Check is needed before accessing specific values! +ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v); + +// Check value to be of a specific type +// Can also be used before accessing properties! +ADDAPI bool ADDCALL sass_value_is_null (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_number (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_string (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_boolean (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_color (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_list (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_map (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_error (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_warning (const union Sass_Value* v); + +// Getters and setters for Sass_Number +ADDAPI double ADDCALL sass_number_get_value (const union Sass_Value* v); +ADDAPI void ADDCALL sass_number_set_value (union Sass_Value* v, double value); +ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v); +ADDAPI void ADDCALL sass_number_set_unit (union Sass_Value* v, char* unit); + +// Getters and setters for Sass_String +ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v); +ADDAPI void ADDCALL sass_string_set_value (union Sass_Value* v, char* value); +ADDAPI bool ADDCALL sass_string_is_quoted(const union Sass_Value* v); +ADDAPI void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted); + +// Getters and setters for Sass_Boolean +ADDAPI bool ADDCALL sass_boolean_get_value (const union Sass_Value* v); +ADDAPI void ADDCALL sass_boolean_set_value (union Sass_Value* v, bool value); + +// Getters and setters for Sass_Color +ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r); +ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g); +ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b); +ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a); + +// Getter for the number of items in list +ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); +// Getters and setters for Sass_List +ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v); +ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +// Getters and setters for Sass_List values +ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); +ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); + +// Getter for the number of items in map +ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v); +// Getters and setters for Sass_Map keys and values +ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i); +ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); +ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i); +ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); + +// Getters and setters for Sass_Error +ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v); +ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg); + +// Getters and setters for Sass_Warning +ADDAPI char* ADDCALL sass_warning_get_message (const union Sass_Value* v); +ADDAPI void ADDCALL sass_warning_set_message (union Sass_Value* v, char* msg); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/src/libsass/include/sass/version.h b/src/libsass/include/sass/version.h new file mode 100755 index 000000000..3047c3953 --- /dev/null +++ b/src/libsass/include/sass/version.h @@ -0,0 +1,12 @@ +#ifndef SASS_VERSION_H +#define SASS_VERSION_H + +#ifndef LIBSASS_VERSION +#define LIBSASS_VERSION "[NA]" +#endif + +#ifndef LIBSASS_LANGUAGE_VERSION +#define LIBSASS_LANGUAGE_VERSION "3.4" +#endif + +#endif diff --git a/src/libsass/include/sass/version.h.in b/src/libsass/include/sass/version.h.in new file mode 100755 index 000000000..197468b6f --- /dev/null +++ b/src/libsass/include/sass/version.h.in @@ -0,0 +1,12 @@ +#ifndef SASS_VERSION_H +#define SASS_VERSION_H + +#ifndef LIBSASS_VERSION +#define LIBSASS_VERSION "@PACKAGE_VERSION@" +#endif + +#ifndef LIBSASS_LANGUAGE_VERSION +#define LIBSASS_LANGUAGE_VERSION "3.4" +#endif + +#endif diff --git a/src/libsass/include/sass2scss.h b/src/libsass/include/sass2scss.h new file mode 100755 index 000000000..5ddef1006 --- /dev/null +++ b/src/libsass/include/sass2scss.h @@ -0,0 +1,120 @@ +/** + * sass2scss + * Licensed under the MIT License + * Copyright (c) Marcel Greter + */ + +#ifndef SASS2SCSS_H +#define SASS2SCSS_H + +#ifdef _WIN32 + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #define ADDCALL __cdecl + #else + #define ADDAPI + #define ADDCALL + #endif + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +#ifndef SASS2SCSS_VERSION +// Hardcode once the file is copied from +// https://github.com/mgreter/sass2scss +#define SASS2SCSS_VERSION "1.1.0" +#endif + +// add namespace for c++ +namespace Sass +{ + + // pretty print options + const int SASS2SCSS_PRETTIFY_0 = 0; + const int SASS2SCSS_PRETTIFY_1 = 1; + const int SASS2SCSS_PRETTIFY_2 = 2; + const int SASS2SCSS_PRETTIFY_3 = 3; + + // remove one-line comment + const int SASS2SCSS_KEEP_COMMENT = 32; + // remove multi-line comments + const int SASS2SCSS_STRIP_COMMENT = 64; + // convert one-line to multi-line + const int SASS2SCSS_CONVERT_COMMENT = 128; + + // String for finding something interesting + const std::string SASS2SCSS_FIND_WHITESPACE = " \t\n\v\f\r"; + + // converter struct + // holding all states + struct converter + { + // bit options + int options; + // is selector + bool selector; + // concat lists + bool comma; + // has property + bool property; + // has semicolon + bool semicolon; + // comment context + std::string comment; + // flag end of file + bool end_of_file; + // whitespace buffer + std::string whitespace; + // context/block stack + std::stack indents; + }; + + // function only available in c++ code + char* sass2scss (const std::string& sass, const int options); + +} +// EO namespace + +// declare for c +extern "C" { +#endif + + // prettyfy print options + #define SASS2SCSS_PRETTIFY_0 0 + #define SASS2SCSS_PRETTIFY_1 1 + #define SASS2SCSS_PRETTIFY_2 2 + #define SASS2SCSS_PRETTIFY_3 3 + + // keep one-line comments + #define SASS2SCSS_KEEP_COMMENT 32 + // remove multi-line comments + #define SASS2SCSS_STRIP_COMMENT 64 + // convert one-line to multi-line + #define SASS2SCSS_CONVERT_COMMENT 128 + + // available to c and c++ code + ADDAPI char* ADDCALL sass2scss (const char* sass, const int options); + + // Get compiled sass2scss version + ADDAPI const char* ADDCALL sass2scss_version(void); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif \ No newline at end of file diff --git a/src/libsass/m4/.gitkeep b/src/libsass/m4/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 b/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 new file mode 100755 index 000000000..395b13d2a --- /dev/null +++ b/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,167 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++11 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX11 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { + func(0); + } + } +]]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], + [$2], [optional], [ax_cxx_compile_cxx11_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + for switch in -std=c++11 -std=c++0x +std=c++11; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + AC_MSG_NOTICE([No compiler with C++11 support was found]) + else + HAVE_CXX11=1 + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++11 syntax]) + fi + + AC_SUBST(HAVE_CXX11) + fi +]) diff --git a/src/libsass/res/resource.rc b/src/libsass/res/resource.rc new file mode 100755 index 000000000..1262b182c --- /dev/null +++ b/src/libsass/res/resource.rc @@ -0,0 +1,35 @@ +#include + +// DLL version information. +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE +#else + FILEFLAGS 0 +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "CompanyName", "Libsass Organization" + VALUE "FileDescription", "A C/C++ implementation of a Sass compiler" + VALUE "FileVersion", "0.9.0.0" + VALUE "InternalName", "libsass" + VALUE "LegalCopyright", "©2014 libsass.org" + VALUE "OriginalFilename", "libsass.dll" + VALUE "ProductName", "Libsass Library" + VALUE "ProductVersion", "0.9.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END \ No newline at end of file diff --git a/src/libsass/script/bootstrap b/src/libsass/script/bootstrap new file mode 100755 index 000000000..ab82fac94 --- /dev/null +++ b/src/libsass/script/bootstrap @@ -0,0 +1,13 @@ +#!/bin/bash + +script/branding + +: ${SASS_SPEC_PATH:="sass-spec"} +: ${SASS_SASSC_PATH:="sassc" } + +if [ ! -d $SASS_SPEC_PATH ]; then + git clone https://github.com/sass/sass-spec.git $SASS_SPEC_PATH +fi +if [ ! -d $SASS_SASSC_PATH ]; then + git clone https://github.com/sass/sassc.git $SASS_SASSC_PATH +fi diff --git a/src/libsass/script/branding b/src/libsass/script/branding new file mode 100755 index 000000000..cd8cb2a52 --- /dev/null +++ b/src/libsass/script/branding @@ -0,0 +1,10 @@ +#! /bin/bash + +echo " " +echo " _ ___ ____ ____ _ ____ ____ " +echo "| | |_ _| __ ) ___| / \ / ___/ ___| " +echo "| | | || _ \___ \ / _ \ \___ \___ \ " +echo "| |___ | || |_) |__) / ___ \ ___) |__) |" +echo "|_____|___|____/____/_/ \_\____/____/ " +echo " " + diff --git a/src/libsass/script/ci-build-libsass b/src/libsass/script/ci-build-libsass new file mode 100755 index 000000000..e2e5b2d2c --- /dev/null +++ b/src/libsass/script/ci-build-libsass @@ -0,0 +1,129 @@ +#!/bin/bash + +set -e + +script/bootstrap + +# export this path right here (was in script/spec before) +export SASS_LIBSASS_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../ && pwd )" + +# use some defaults if not running under travis ci +if [ "x$CONTINUOUS_INTEGRATION" == "x" ]; then export CONTINUOUS_INTEGRATION=true; fi +if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then export TRAVIS_BUILD_DIR=$(pwd); fi +if [ "x$SASS_SASSC_PATH" == "x" ]; then export SASS_SASSC_PATH=$(pwd)/sassc; fi +if [ "x$SASS_SPEC_PATH" == "x" ]; then export SASS_SPEC_PATH=$(pwd)/sass-spec; fi + +# try to get the os name from uname (and filter via perl - probably not the most portable way?) +if [ "x$TRAVIS_OS_NAME" == "x" ]; then export TRAVIS_OS_NAME=`uname -s | perl -ne 'print lc \$1 if\(/^([a-zA-Z]+)/'\)`; fi + +if [ "x$COVERAGE" == "xyes" ]; then + COVERAGE_OPT="--enable-coverage" + export EXTRA_CFLAGS="--coverage" + export EXTRA_CXXFLAGS="--coverage" + export EXTRA_LDFLAGS="--coverage" +else + COVERAGE_OPT="--disable-coverage" +fi + +if [ "x$BUILD" == "xstatic" ]; then + SHARED_OPT="--disable-shared --enable-static" + MAKE_TARGET="static" +else + # Makefile of sassc wants to link to static + SHARED_OPT="--enable-shared --enable-static" + MAKE_TARGET="shared" +fi + +if [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then + MAKE_OPTS="$MAKE_OPTS -j1 V=1" +else + MAKE_OPTS="$MAKE_OPTS -j3 V=1" +fi + +if [ "x$PREFIX" == "x" ]; then + if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then + PREFIX=$SASS_LIBSASS_PATH/build + else + PREFIX=$TRAVIS_BUILD_DIR/build + fi +fi + +# enable address sanitation +# https://en.wikipedia.org/wiki/AddressSanitizer +if [ "x$CC" == "xclang" ]; then + if [ "x$COVERAGE" != "xyes" ]; then + if [ "$TRAVIS_OS_NAME" == "linux" ]; then + export EXTRA_CFLAGS="$EXTRA_CFLAGS -fsanitize=address" + export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -fsanitize=address" + export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fsanitize=address" + fi + fi +fi + +echo SASS_LIBSASS_PATH: $SASS_LIBSASS_PATH +echo TRAVIS_BUILD_DIR: $TRAVIS_BUILD_DIR +echo SASS_SASSC_PATH: $SASS_SASSC_PATH +echo SASS_SPEC_PATH: $SASS_SPEC_PATH +echo INSTALL_LOCATION: $PREFIX + +if [ "x$AUTOTOOLS" == "xyes" ]; then + + echo -en 'travis_fold:start:configure\r' + autoreconf --force --install + ./configure --enable-tests $COVERAGE_OPT \ + --disable-silent-rules \ + --with-sassc-dir=$SASS_SASSC_PATH \ + --with-sass-spec-dir=$SASS_SPEC_PATH \ + --prefix=$PREFIX \ + ${SHARED_OPT} + echo -en 'travis_fold:end:configure\r' + + make $MAKE_OPTS clean + + # install to prefix directory + PREFIX="$PREFIX" make $MAKE_OPTS install + +else + + make $MAKE_OPTS clean + +fi + +# install to prefix directory +PREFIX="$PREFIX" make $MAKE_OPTS install + +ls -la $PREFIX/* + +echo successfully compiled libsass +echo AUTOTOOLS=$AUTOTOOLS COVERAGE=$COVERAGE BUILD=$BUILD + +if [ "$CONTINUOUS_INTEGRATION" == "true" ] && [ "$TRAVIS_PULL_REQUEST" != "false" ] && [ "x$TRAVIS_PULL_REQUEST" != "x" ] && + ([ "$TRAVIS_OS_NAME" == "linux" ] || [ "$TRAVIS_OS_NAME" == "osx" ] || [ "$TRAVIS_OS_NAME" == "cygwin" ]); +then + + echo "Fetching PR $TRAVIS_PULL_REQUEST" + + JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + + if [[ $JSON =~ "API rate limit exceeded" ]]; + then + echo "Travis rate limit on github exceeded" + echo "Retrying via 'special purpose proxy'" + JSON=$(curl -L -sS http://libsass.ocbnet.ch/libsass-spec-pr.psgi/$TRAVIS_PULL_REQUEST) + fi + + RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + + if [[ $JSON =~ $RE_SPEC_PR ]]; + then + SPEC_PR="${BASH_REMATCH[2]}" + echo "Fetching Sass Spec PR $SPEC_PR" + git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR + git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR + LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + else + LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + fi +else + LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe +fi diff --git a/src/libsass/script/ci-install-compiler b/src/libsass/script/ci-install-compiler new file mode 100755 index 000000000..c36211a31 --- /dev/null +++ b/src/libsass/script/ci-install-compiler @@ -0,0 +1,6 @@ +#!/bin/bash + +gem install minitest +gem install minitap + +pip install --user 'requests[security]' diff --git a/src/libsass/script/ci-install-deps b/src/libsass/script/ci-install-deps new file mode 100755 index 000000000..b560f7441 --- /dev/null +++ b/src/libsass/script/ci-install-deps @@ -0,0 +1,23 @@ +#!/bin/bash +if [ "x$COVERAGE" == "xyes" ]; then + pip install --user gcovr + pip install --user cpp-coveralls +else + echo "no dependencies to install" +fi + +if [ "x$AUTOTOOLS" == "xyes" ]; then + AUTOTOOLS=yes + + if [ "$TRAVIS_OS_NAME" == "linux" ]; then + sudo add-apt-repository -y ppa:rbose-debianizer/automake &> /dev/null + sudo apt-get -qq update + sudo apt-get -qq install automake + fi + + # https://github.com/sass/libsass/pull/2183 + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + brew uninstall libtool + brew install libtool + fi +fi diff --git a/src/libsass/script/ci-report-coverage b/src/libsass/script/ci-report-coverage new file mode 100755 index 000000000..4e4c99f03 --- /dev/null +++ b/src/libsass/script/ci-report-coverage @@ -0,0 +1,32 @@ +#!/bin/bash + +if [ "x$COVERAGE" = "xyes" ]; then + + # exclude some directories from profiling (.libs is from autotools) + export EXCLUDE_COVERAGE="--exclude plugins + --exclude sassc/sassc.c + --exclude src/sass-spec + --exclude src/.libs + --exclude src/debug.hpp + --exclude src/json.cpp + --exclude src/json.hpp + --exclude src/cencode.c + --exclude src/b64 + --exclude src/utf8 + --exclude src/utf8_string.hpp + --exclude src/utf8.h + --exclude src/utf8_string.cpp + --exclude src/sass2scss.h + --exclude src/sass2scss.cpp + --exclude src/test + --exclude src/posix + --exclude src/debugger.hpp" + # debug via gcovr + gcov -v + gcovr -r . + # generate and submit report to coveralls.io + coveralls $EXCLUDE_COVERAGE --gcov-options '\-lp' + +else + echo "skip coverage reporting" +fi diff --git a/src/libsass/script/spec b/src/libsass/script/spec new file mode 100755 index 000000000..d0b864a13 --- /dev/null +++ b/src/libsass/script/spec @@ -0,0 +1,5 @@ +#!/bin/bash + +script/bootstrap + +make $MAKE_OPTS test_build diff --git a/src/libsass/script/tap-driver b/src/libsass/script/tap-driver new file mode 100755 index 000000000..19aa531de --- /dev/null +++ b/src/libsass/script/tap-driver @@ -0,0 +1,652 @@ +#! /bin/sh +# Copyright (C) 2011-2013 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +scriptversion=2011-12-27.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat < + # + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# FIXME: the usages of "cat >&3" below could be optimized when using +# FIXME: GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +# FIXME: this can certainly be improved ... +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assing the next progresive number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/src/libsass/script/tap-runner b/src/libsass/script/tap-runner new file mode 100755 index 000000000..4adecafb9 --- /dev/null +++ b/src/libsass/script/tap-runner @@ -0,0 +1 @@ +$@ | tapout tap \ No newline at end of file diff --git a/src/libsass/script/test-leaks.pl b/src/libsass/script/test-leaks.pl new file mode 100755 index 000000000..bfb8653f4 --- /dev/null +++ b/src/libsass/script/test-leaks.pl @@ -0,0 +1,103 @@ +#!/usr/bin/perl +############################################################ +# this perl script is meant for developers only! +# it will run all spec-tests (without verifying the +# results) via valgrind to detect possible leaks. +# expect that it takes 1h or more to finish! +############################################################ +# Prerequisite install: `cpan Parallel::Runner` +# You may also need to install `cpan File::Find` +# You may also need to install `cpan IPC::Run3` +############################################################ +# usage: `perl test-leaks.pl [threads]` +# example: `time perl test-leaks.pl 4` +############################################################ +# leaks will be reported in "mem-leaks.log" +############################################################ + +use strict; +use warnings; + +############################################################ +# configurations (you may adjust) +############################################################ + +# number of threads to use +my $threads = $ARGV[0] || 8; + +# the github repositories to checkout +# if you need other branch, clone manually! +my $sassc = "https://www.github.com/sass/sassc"; +my $specs = "https://www.github.com/sass/sass-spec"; + +############################################################ +# load modules +############################################################ + +use IPC::Run3; +use IO::Handle; +use Fcntl qw(:flock); +use File::Find::Rule; +use Parallel::Runner; +use List::Util qw(shuffle); + +############################################################ +# check prerequisites +############################################################ + +unless (-d "../sassc") { + warn "sassc folder not found\n"; + warn "trying to checkout via git\n"; + system("git", "clone", $sassc, "../sassc"); + die "git command did not exit gracefully" if $?; +} + +unless (-d "../sass-spec") { + warn "sass-spec folder not found\n"; + warn "trying to checkout via git\n"; + system("git", "clone", $specs, "../sass-spec"); + die "git command did not exit gracefully" if $?; +} + +unless (-f "../sassc/bin/sassc") { + warn "sassc executable not found\n"; + warn "trying to compile via make\n"; + system("make", "-C", "../sassc", "-j", $threads); + die "make command did not exit gracefully" if $?; +} + +############################################################ +# main runner code +############################################################ + +my $root = "../sass-spec/spec"; +my @files = File::Find::Rule->file() + ->name('input.scss')->in($root); + +open(my $leaks, ">", "mem-leaks.log"); +die "Cannot open log" unless $leaks; +my $runner = Parallel::Runner->new($threads); +die "Cannot start runner" unless $runner; + +print "##########################\n"; +print "Testing $#files spec files\n"; +print "##########################\n"; + +foreach my $file (shuffle @files) { + $runner->run(sub { + $| = 1; select STDOUT; + my $cmd = sprintf('../sassc/bin/sassc %s', $file); + my $check = sprintf('valgrind --leak-check=yes %s', $cmd); + run3($check, undef, \ my $out, \ my $err); + if ($err =~ m/in use at exit: 0 bytes in 0 blocks/) { + print "."; # print success indicator + } else { + print "F"; # print error indicator + flock($leaks, LOCK_EX) or die "Cannot lock log"; + $leaks->printflush("#" x 80, "\n", $err, "\n"); + flock($leaks, LOCK_UN) or die "Cannot unlock log"; + } + }); +} + +$runner->finish; diff --git a/src/libsass/src/GNUmakefile.am b/src/libsass/src/GNUmakefile.am new file mode 100755 index 000000000..c4c5e08cb --- /dev/null +++ b/src/libsass/src/GNUmakefile.am @@ -0,0 +1,54 @@ +ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4 -I script + +AM_COPT = -Wall -O2 +AM_COVLDFLAGS = + +if ENABLE_COVERAGE + AM_COPT = -O0 --coverage + AM_COVLDFLAGS += -lgcov +endif + +AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CFLAGS = $(AM_COPT) +AM_CXXFLAGS = $(AM_COPT) +AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) + +if COMPILER_IS_MINGW32 + AM_CXXFLAGS += -std=gnu++0x +else + AM_CXXFLAGS += -std=c++0x +endif + +EXTRA_DIST = \ + COPYING \ + INSTALL \ + LICENSE \ + Readme.md + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = support/libsass.pc + +lib_LTLIBRARIES = libsass.la + +include $(top_srcdir)/Makefile.conf + +libsass_la_SOURCES = ${CSOURCES} ${SOURCES} + +libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 0:9:0 + +if ENABLE_TESTS +if ENABLE_COVERAGE +nodist_EXTRA_libsass_la_SOURCES = non-existent-file-to-force-CXX-linking.cxx +endif +endif + +include_HEADERS = $(top_srcdir)/include/sass.h \ + $(top_srcdir)/include/sass2scss.h + +sass_includedir = $(includedir)/sass + +sass_include_HEADERS = $(top_srcdir)/include/sass/base.h \ + $(top_srcdir)/include/sass/values.h \ + $(top_srcdir)/include/sass/version.h \ + $(top_srcdir)/include/sass/context.h \ + $(top_srcdir)/include/sass/functions.h diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp new file mode 100755 index 000000000..726f0103c --- /dev/null +++ b/src/libsass/src/ast.cpp @@ -0,0 +1,2451 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "node.hpp" +#include "extend.hpp" +#include "emitter.hpp" +#include "color_maps.hpp" +#include "ast_fwd_decl.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace Sass { + + static Null sass_null(ParserState("null")); + + bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { + if (Supports_Operator_Obj op = SASS_MEMORY_CAST(Supports_Operator, cond)) { + return op->operand() != operand(); + } + return SASS_MEMORY_CAST(Supports_Negation, cond) != NULL; + } + + bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const { + return SASS_MEMORY_CAST(Supports_Negation, cond) || + SASS_MEMORY_CAST(Supports_Operator, cond); + } + + size_t HashExpression::operator() (Expression_Obj ex) const { + return ex ? ex->hash() : 0; + } + + size_t HashSimpleSelector::operator() (Simple_Selector_Obj ex) const { + return ex ? ex->hash() : 0; + } + + + bool CompareExpression::operator()(const Expression_Obj& lhs, const Expression_Obj& rhs) const { + return lhs && rhs && lhs->eq(*rhs); + } + + bool CompareSimpleSelector::operator()(Simple_Selector_Obj lhs, Simple_Selector_Obj rhs) const { + return &lhs && &rhs && *lhs == *rhs; + } + + std::string & str_ltrim(std::string & str) + { + auto it2 = std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace(ch , std::locale::classic() ) ; } ); + str.erase( str.begin() , it2); + return str; + } + + std::string & str_rtrim(std::string & str) + { + auto it1 = std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace(ch , std::locale::classic() ) ; } ); + str.erase( it1.base() , str.end() ); + return str; + } + + void String_Constant::rtrim() + { + value_ = str_rtrim(value_); + } + void String_Constant::ltrim() + { + value_ = str_ltrim(value_); + } + void String_Constant::trim() + { + rtrim(); + ltrim(); + } + + void String_Schema::rtrim() + { + if (!empty()) { + if (String_Ptr str = SASS_MEMORY_CAST(String, last())) str->rtrim(); + } + } + void String_Schema::ltrim() + { + if (!empty()) { + if (String_Ptr str = SASS_MEMORY_CAST(String, first())) str->ltrim(); + } + } + void String_Schema::trim() + { + rtrim(); + ltrim(); + } + + void Argument::set_delayed(bool delayed) + { + if (value_) value_->set_delayed(delayed); + is_delayed(delayed); + } + + void Arguments::set_delayed(bool delayed) + { + for (Argument_Obj arg : elements()) { + if (arg) arg->set_delayed(delayed); + } + is_delayed(delayed); + } + + + bool At_Root_Query::exclude(std::string str) + { + bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; + List_Ptr l = static_cast(&value()); + std::string v; + + if (with) + { + if (!l || l->length() == 0) return str.compare("rule") != 0; + for (size_t i = 0, L = l->length(); i < L; ++i) + { + v = unquote((*l)[i]->to_string()); + if (v.compare("all") == 0 || v == str) return false; + } + return true; + } + else + { + if (!l || !l->length()) return str.compare("rule") == 0; + for (size_t i = 0, L = l->length(); i < L; ++i) + { + v = unquote((*l)[i]->to_string()); + if (v.compare("all") == 0 || v == str) return true; + } + return false; + } + } + + void AST_Node::update_pstate(const ParserState& pstate) + { + pstate_.offset += pstate - pstate_ + pstate.offset; + } + + void AST_Node::set_pstate_offset(const Offset& offset) + { + pstate_.offset = offset; + } + + inline bool is_ns_eq(const std::string& l, const std::string& r) + { + if (l.empty() && r.empty()) return true; + else if (l.empty() && r == "*") return true; + else if (r.empty() && l == "*") return true; + else return l == r; + } + + + + bool Compound_Selector::operator< (const Compound_Selector& rhs) const + { + size_t L = std::min(length(), rhs.length()); + for (size_t i = 0; i < L; ++i) + { + Simple_Selector_Obj l = (*this)[i]; + Simple_Selector_Obj r = rhs[i]; + if (!l && !r) return false; + else if (!r) return false; + else if (!l) return true; + else if (*l != *r) + { return *l < *r; } + } + // just compare the length now + return length() < rhs.length(); + } + + bool Compound_Selector::has_parent_ref() + { + for (Simple_Selector_Obj s : *this) { + if (s && s->has_parent_ref()) return true; + } + return false; + } + + bool Compound_Selector::has_real_parent_ref() + { + for (Simple_Selector_Obj s : *this) { + if (s && s->has_real_parent_ref()) return true; + } + return false; + } + + bool Complex_Selector::has_parent_ref() + { + return (head() && head()->has_parent_ref()) || + (tail() && tail()->has_parent_ref()); + } + + bool Complex_Selector::has_real_parent_ref() + { + return (head() && head()->has_real_parent_ref()) || + (tail() && tail()->has_real_parent_ref()); + } + + bool Complex_Selector::operator< (const Complex_Selector& rhs) const + { + // const iterators for tails + Complex_Selector_Ptr_Const l = this; + Complex_Selector_Ptr_Const r = &rhs; + Compound_Selector_Ptr l_h = l ? &l->head() : 0; + Compound_Selector_Ptr r_h = r ? &r->head() : 0; + // process all tails + while (true) + { + // skip empty ancestor first + if (l && l->is_empty_ancestor()) + { + l = &l->tail(); + l_h = l ? &l->head() : 0; + continue; + } + // skip empty ancestor first + if (r && r->is_empty_ancestor()) + { + r = &r->tail(); + r_h = r ? &r->head() : 0; + continue; + } + // check for valid selectors + if (!l) return !!r; + if (!r) return false; + // both are null + else if (!l_h && !r_h) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() < r->combinator(); } + // advance to next tails + l = &l->tail(); + r = &r->tail(); + // fetch the next headers + l_h = l ? &l->head() : 0; + r_h = r ? &r->head() : 0; + } + // one side is null + else if (!r_h) return true; + else if (!l_h) return false; + // heads ok and equal + else if (*l_h == *r_h) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() < r->combinator(); } + // advance to next tails + l = &l->tail(); + r = &r->tail(); + // fetch the next headers + l_h = l ? &l->head() : 0; + r_h = r ? &r->head() : 0; + } + // heads are not equal + else return *l_h < *r_h; + } + return true; + } + + bool Complex_Selector::operator== (const Complex_Selector& rhs) const + { + // const iterators for tails + Complex_Selector_Ptr_Const l = this; + Complex_Selector_Ptr_Const r = &rhs; + Compound_Selector_Ptr l_h = l ? &l->head() : 0; + Compound_Selector_Ptr r_h = r ? &r->head() : 0; + // process all tails + while (true) + { + // skip empty ancestor first + if (l && l->is_empty_ancestor()) + { + l = &l->tail(); + l_h = l ? &l->head() : 0; + continue; + } + // skip empty ancestor first + if (r && r->is_empty_ancestor()) + { + r = &r->tail(); + r_h = r ? &r->head() : 0; + continue; + } + // check the pointers + if (!r) return !l; + if (!l) return !r; + // both are null + if (!l_h && !r_h) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() < r->combinator(); } + // advance to next tails + l = &l->tail(); + r = &r->tail(); + // fetch the next heads + l_h = l ? &l->head() : 0; + r_h = r ? &r->head() : 0; + } + // equals if other head is empty + else if ((!l_h && !r_h) || + (!l_h && r_h->empty()) || + (!r_h && l_h->empty()) || + (*l_h == *r_h)) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() == r->combinator(); } + // advance to next tails + l = &l->tail(); + r = &r->tail(); + // fetch the next heads + l_h = l ? &l->head() : 0; + r_h = r ? &r->head() : 0; + } + // abort + else break; + } + // unreachable + return false; + } + + Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + { + if (empty()) return rhs; + Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); + for (size_t i = 0, L = length(); i < L; ++i) + { + if (unified.isNull()) break; + unified = at(i)->unify_with(&unified, ctx); + } + return unified.detach(); + } + + bool Selector::operator== (const Selector& rhs) const + { + if (Selector_List_Ptr_Const sl = dynamic_cast(this)) return *sl == rhs; + if (Simple_Selector_Ptr_Const sp = dynamic_cast(this)) return *sp == rhs; + throw std::runtime_error("invalid selector base classes to compare"); + return false; + } + + bool Selector::operator< (const Selector& rhs) const + { + if (Selector_List_Ptr_Const sl = dynamic_cast(this)) return *sl < rhs; + if (Simple_Selector_Ptr_Const sp = dynamic_cast(this)) return *sp < rhs; + throw std::runtime_error("invalid selector base classes to compare"); + return false; + } + + bool Simple_Selector::operator== (const Selector& rhs) const + { + if (Simple_Selector_Ptr_Const sp = dynamic_cast(&rhs)) return *this == *sp; + return false; + } + + bool Simple_Selector::operator< (const Selector& rhs) const + { + if (Simple_Selector_Ptr_Const sp = dynamic_cast(&rhs)) return *this < *sp; + return false; + } + + bool Simple_Selector::operator== (const Simple_Selector& rhs) const + { + Simple_Type type = simple_type(); + // dynamic cast is a bottleneck - use concrete type as types are final + if (type == PSEUDO_SEL /* Pseudo_Selector_Ptr_Const lp = dynamic_cast(this) */) { + return *static_cast(this) == rhs; + } + else if (type == WRAPPED_SEL /* Wrapped_Selector_Ptr_Const lw = dynamic_cast(this) */) { + return *static_cast(this) == rhs; + } + else if (type == ATTR_SEL /* Attribute_Selector_Ptr_Const la = dynamic_cast(this) */) { + return *static_cast(this) == rhs; + } + else if (name_ == rhs.name_) + { return is_ns_eq(ns_, rhs.ns_); } + else return false; + } + + bool Simple_Selector::operator< (const Simple_Selector& rhs) const + { + Simple_Type type = simple_type(); + // dynamic cast is a bottleneck - use concrete type as types are final + if (type == PSEUDO_SEL /* Pseudo_Selector_Ptr_Const lp = dynamic_cast(this) */) { + return *static_cast(this) < rhs; + } + else if (type == WRAPPED_SEL /* Wrapped_Selector_Ptr_Const lw = dynamic_cast(this) */) { + return *static_cast(this) < rhs; + } + else if (type == ATTR_SEL /* Attribute_Selector_Ptr_Const la = dynamic_cast(this) */) { + return *static_cast(this) < rhs; + } + if (is_ns_eq(ns_, rhs.ns_)) + { return name_ < rhs.name_; } + return ns_ < rhs.ns_; + } + + bool Selector_List::operator== (const Selector& rhs) const + { + // solve the double dispatch problem by using RTTI information via dynamic cast + if (Selector_List_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + else if (Complex_Selector_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + else if (Compound_Selector_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + // no compare method + return this == &rhs; + } + + // Selector lists can be compared to comma lists + bool Selector_List::operator==(const Expression& rhs) const + { + // solve the double dispatch problem by using RTTI information via dynamic cast + if (List_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + if (Selector_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + // compare invalid (maybe we should error?) + return false; + } + + bool Selector_List::operator== (const Selector_List& rhs) const + { + // for array access + size_t i = 0, n = 0; + size_t iL = length(); + size_t nL = rhs.length(); + // create temporary vectors and sort them + std::vector l_lst = this->elements(); + std::vector r_lst = rhs.elements(); + std::sort(l_lst.begin(), l_lst.end(), cmp_complex_selector()); + std::sort(r_lst.begin(), r_lst.end(), cmp_complex_selector()); + // process loop + while (true) + { + // first check for valid index + if (i == iL) return iL == nL; + else if (n == nL) return iL == nL; + // the access the vector items + Complex_Selector_Obj l = l_lst[i]; + Complex_Selector_Obj r = r_lst[n]; + // skip nulls + if (!l) ++i; + else if (!r) ++n; + // do the check + else if (*l != *r) + { return false; } + // advance + ++i; ++n; + } + // no mismatch + return true; + } + + bool Selector_List::operator< (const Selector& rhs) const + { + if (Selector_List_Ptr_Const sp = dynamic_cast(&rhs)) return *this < *sp; + return false; + } + + bool Selector_List::operator< (const Selector_List& rhs) const + { + if (this->length() != rhs.length()) return false; + for (size_t i = 0; i < rhs.length(); i ++) { + if (!(*at(i) < *rhs.at(i))) return false; + } + return true; + } + + Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + { + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { if (to_string(ctx.c_options) == rhs->at(i)->to_string(ctx.c_options)) return rhs; } + + // check for pseudo elements because they are always last + size_t i, L; + bool found = false; + if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector)) + { + for (i = 0, L = rhs->length(); i < L; ++i) + { + if ((SASS_MEMORY_CAST(Pseudo_Selector, (*rhs)[i]) || SASS_MEMORY_CAST(Wrapped_Selector, (*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) + { found = true; break; } + } + } + else + { + for (i = 0, L = rhs->length(); i < L; ++i) + { + if (SASS_MEMORY_CAST(Pseudo_Selector, (*rhs)[i]) || SASS_MEMORY_CAST(Wrapped_Selector, (*rhs)[i])) + { found = true; break; } + } + } + if (!found) + { + rhs->append(this); + return rhs; + } + rhs->elements().insert(rhs->elements().begin() + i, this); + return rhs; + } + + Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs, Context& ctx) + { + // check if ns can be extended + // true for no ns or universal + if (has_universal_ns()) + { + // but dont extend with universal + // true for valid ns and universal + if (!rhs->is_universal_ns()) + { + // overwrite the name if star is given as name + if (this->name() == "*") { this->name(rhs->name()); } + // now overwrite the namespace name and flag + this->ns(rhs->ns()); this->has_ns(rhs->has_ns()); + // return copy + return this; + } + } + // namespace may changed, check the name now + // overwrite star (but not with another star) + if (name() == "*" && rhs->name() != "*") + { + // simply set the new name + this->name(rhs->name()); + // return copy + return this; + } + // return original + return this; + } + + Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + { + // TODO: handle namespaces + + // if the rhs is empty, just return a copy of this + if (rhs->length() == 0) { + rhs->append(this); + return rhs; + } + + Simple_Selector_Ptr rhs_0 = &rhs->at(0); + // otherwise, this is a tag name + if (name() == "*") + { + if (typeid(*rhs_0) == typeid(Element_Selector)) + { + // if rhs is universal, just return this tagname + rhs's qualifiers + Element_Selector_Ptr ts = SASS_MEMORY_CAST_PTR(Element_Selector, rhs_0); + rhs->at(0) = this->unify_with(ts, ctx); + return rhs; + } + else if (SASS_MEMORY_CAST_PTR(Class_Selector, rhs_0) || SASS_MEMORY_CAST_PTR(Id_Selector, rhs_0)) { + // qualifier is `.class`, so we can prefix with `ns|*.class` + if (has_ns() && !rhs_0->has_ns()) { + if (ns() != "*") rhs->elements().insert(rhs->begin(), this); + } + return rhs; + } + + + return rhs; + } + + if (typeid(*rhs_0) == typeid(Element_Selector)) + { + // if rhs is universal, just return this tagname + rhs's qualifiers + if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; + // otherwise create new compound and unify first simple selector + rhs->at(0) = this->unify_with(rhs_0, ctx); + return rhs; + + } + // else it's a tag name and a bunch of qualifiers -- just append them + if (name() != "*") rhs->elements().insert(rhs->begin(), this); + return rhs; + } + + Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + { + rhs->has_line_break(has_line_break()); + return Simple_Selector::unify_with(rhs, ctx); + } + + Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + { + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { + if (Id_Selector_Ptr sel = SASS_MEMORY_CAST(Id_Selector, rhs->at(i))) { + if (sel->name() != name()) return 0; + } + } + rhs->has_line_break(has_line_break()); + return Simple_Selector::unify_with(rhs, ctx); + } + + Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + { + if (is_pseudo_element()) + { + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { + if (Pseudo_Selector_Ptr sel = SASS_MEMORY_CAST(Pseudo_Selector, rhs->at(i))) { + if (sel->is_pseudo_element() && sel->name() != name()) return 0; + } + } + } + return Simple_Selector::unify_with(rhs, ctx); + } + + bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const + { + if (is_ns_eq(ns(), rhs.ns())) { + if (name() == rhs.name()) { + if (matcher() == rhs.matcher()) { + bool no_lhs_val = value().isNull(); + bool no_rhs_val = rhs.value().isNull(); + if (no_lhs_val && no_rhs_val) { + return true; + } + if (!no_lhs_val && !no_rhs_val) { + return *value() < *rhs.value(); + } + } else { return matcher() < rhs.matcher(); } + } else { return name() < rhs.name(); } + } + return false; + } + + bool Attribute_Selector::operator< (const Simple_Selector& rhs) const + { + if (Attribute_Selector_Ptr_Const w = dynamic_cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const + { + // get optional value state + bool no_lhs_val = value().isNull(); + bool no_rhs_val = rhs.value().isNull(); + // both are null, therefore equal + if (no_lhs_val && no_rhs_val) { + return (name() == rhs.name()) + && (matcher() == rhs.matcher()) + && (is_ns_eq(ns(), rhs.ns())); + } + // both are defined, evaluate + if (no_lhs_val == no_rhs_val) { + return (name() == rhs.name()) + && (matcher() == rhs.matcher()) + && (is_ns_eq(ns(), rhs.ns())) + && (*value() == *rhs.value()); + } + // not equal + return false; + + } + + bool Attribute_Selector::operator== (const Simple_Selector& rhs) const + { + if (Attribute_Selector_Ptr_Const w = dynamic_cast(&rhs)) + { + return *this == *w; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() == rhs.name(); } + return ns() == rhs.ns(); + } + + bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const + { + if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + { + String_Obj lhs_ex = expression(); + String_Obj rhs_ex = rhs.expression(); + if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; + else return lhs_ex == rhs_ex; + } + else return false; + } + + bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const + { + if (Pseudo_Selector_Ptr_Const w = dynamic_cast(&rhs)) + { + return *this == *w; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() == rhs.name(); } + return ns() == rhs.ns(); + } + + bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const + { + if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + { + String_Obj lhs_ex = expression(); + String_Obj rhs_ex = rhs.expression(); + if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; + else return lhs_ex < rhs_ex; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const + { + if (Pseudo_Selector_Ptr_Const w = dynamic_cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const + { + if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + { return *(selector()) == *(rhs.selector()); } + else return false; + } + + bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const + { + if (Wrapped_Selector_Ptr_Const w = dynamic_cast(&rhs)) + { + return *this == *w; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() == rhs.name(); } + return ns() == rhs.ns(); + } + + bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const + { + if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + { return *(selector()) < *(rhs.selector()); } + if (is_ns_eq(ns(), rhs.ns())) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const + { + if (Wrapped_Selector_Ptr_Const w = dynamic_cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(ns(), rhs.ns())) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub) + { + if (this->name() != sub->name()) return false; + if (this->name() == ":current") return false; + if (Selector_List_Obj rhs_list = SASS_MEMORY_CAST(Selector_List, sub->selector())) { + if (Selector_List_Obj lhs_list = SASS_MEMORY_CAST(Selector_List, selector())) { + return lhs_list->is_superselector_of(rhs_list); + } + error("is_superselector expected a Selector_List", sub->pstate()); + } else { + error("is_superselector expected a Selector_List", sub->pstate()); + } + return false; + } + + bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped) + { + for (Complex_Selector_Obj item : rhs->elements()) { + if (is_superselector_of(&item, wrapped)) return true; + } + return false; + } + + bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped) + { + if (rhs->head()) return is_superselector_of(&rhs->head(), wrapped); + return false; + } + + bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) + { + Compound_Selector_Ptr lhs = this; + Simple_Selector_Ptr lbase = lhs->base(); + Simple_Selector_Ptr rbase = rhs->base(); + + // Check if pseudo-elements are the same between the selectors + + std::set lpsuedoset, rpsuedoset; + for (size_t i = 0, L = length(); i < L; ++i) + { + if ((*this)[i]->is_pseudo_element()) { + std::string pseudo((*this)[i]->to_string()); + pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving + lpsuedoset.insert(pseudo); + } + } + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { + if ((*rhs)[i]->is_pseudo_element()) { + std::string pseudo((*rhs)[i]->to_string()); + pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving + rpsuedoset.insert(pseudo); + } + } + if (lpsuedoset != rpsuedoset) { + return false; + } + + std::set lset, rset; + + if (lbase && rbase) + { + if (lbase->to_string() == rbase->to_string()) { + for (size_t i = 1, L = length(); i < L; ++i) + { lset.insert((*this)[i]->to_string()); } + for (size_t i = 1, L = rhs->length(); i < L; ++i) + { rset.insert((*rhs)[i]->to_string()); } + return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); + } + return false; + } + + for (size_t i = 0, iL = length(); i < iL; ++i) + { + Selector_Obj lhs = &(*this)[i]; + // very special case for wrapped matches selector + if (Wrapped_Selector_Obj wrapped = SASS_MEMORY_CAST(Wrapped_Selector, lhs)) { + if (wrapped->name() == ":not") { + if (Selector_List_Obj not_list = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + if (not_list->is_superselector_of(rhs, wrapped->name())) return false; + } else { + throw std::runtime_error("wrapped not selector is not a list"); + } + } + if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { + lhs = wrapped->selector(); + if (Selector_List_Obj list = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + if (Compound_Selector_Obj comp = SASS_MEMORY_CAST(Compound_Selector, rhs)) { + if (!wrapping.empty() && wrapping != wrapped->name()) return false; + if (wrapping.empty() || wrapping != wrapped->name()) {; + if (list->is_superselector_of(comp, wrapped->name())) return true; + } + } + } + } + Simple_Selector_Ptr rhs_sel = rhs->elements().size() > i ? &(*rhs)[i] : 0; + if (Wrapped_Selector_Ptr wrapped_r = dynamic_cast(rhs_sel)) { + if (wrapped->name() == wrapped_r->name()) { + if (wrapped->is_superselector_of(wrapped_r)) { + continue; + rset.insert(lhs->to_string()); + + }} + } + } + // match from here on as strings + lset.insert(lhs->to_string()); + } + + for (size_t n = 0, nL = rhs->length(); n < nL; ++n) + { + Selector_Obj r = &(*rhs)[n]; + if (Wrapped_Selector_Obj wrapped = SASS_MEMORY_CAST(Wrapped_Selector, r)) { + if (wrapped->name() == ":not") { + if (Selector_List_Obj ls = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + ls->remove_parent_selectors(); + if (is_superselector_of(ls, wrapped->name())) return false; + } + } + if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { + if (!wrapping.empty()) { + if (wrapping != wrapped->name()) return false; + } + if (Selector_List_Obj ls = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + ls->remove_parent_selectors(); + return (is_superselector_of(ls, wrapped->name())); + } + } + } + rset.insert(r->to_string()); + } + + //for (auto l : lset) { cerr << "l: " << l << endl; } + //for (auto r : rset) { cerr << "r: " << r << endl; } + + if (lset.empty()) return true; + // return true if rset contains all the elements of lset + return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); + + } + + // create complex selector (ancestor of) from compound selector + Complex_Selector_Obj Compound_Selector::to_complex() + { + // create an intermediate complex selector + return SASS_MEMORY_NEW(Complex_Selector, + pstate(), + Complex_Selector::ANCESTOR_OF, + this, + 0); + } + + Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other, Context& ctx) + { + + // get last tails (on the right side) + Complex_Selector_Obj l_last = this->last(); + Complex_Selector_Obj r_last = other->last(); + + // check valid pointers (assertion) + SASS_ASSERT(l_last, "lhs is null"); + SASS_ASSERT(r_last, "rhs is null"); + + // Not sure about this check, but closest way I could check + // was to see if this is a ruby 'SimpleSequence' equivalent. + // It seems to do the job correctly as some specs react to this + if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0; + if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0; + + // get the headers for the last tails + Compound_Selector_Obj l_last_head = l_last->head(); + Compound_Selector_Obj r_last_head = r_last->head(); + + // check valid head pointers (assertion) + SASS_ASSERT(l_last_head, "lhs head is null"); + SASS_ASSERT(r_last_head, "rhs head is null"); + + // get the unification of the last compound selectors + Compound_Selector_Obj unified = r_last_head->unify_with(&l_last_head, ctx); + + // abort if we could not unify heads + if (unified == 0) return 0; + + // check for universal (star: `*`) selector + bool is_universal = l_last_head->is_universal() || + r_last_head->is_universal(); + + if (is_universal) + { + // move the head + l_last->head(0); + r_last->head(unified); + } + + // create nodes from both selectors + Node lhsNode = complexSelectorToNode(this, ctx); + Node rhsNode = complexSelectorToNode(other, ctx); + + // overwrite universal base + if (!is_universal) + { + // create some temporaries to convert to node + Complex_Selector_Obj fake = unified->to_complex(); + Node unified_node = complexSelectorToNode(&fake, ctx); + // add to permutate the list? + rhsNode.plus(unified_node); + } + + // do some magic we inherit from node and extend + Node node = Extend::subweave(lhsNode, rhsNode, ctx); + Selector_List_Ptr result = SASS_MEMORY_NEW(Selector_List, pstate()); + NodeDequePtr col = node.collection(); // move from collection to list + for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) + { result->append(nodeToComplexSelector(Node::naiveTrim(*it, ctx), ctx)); } + + // only return if list has some entries + return result->length() ? result : 0; + + } + + bool Compound_Selector::operator== (const Compound_Selector& rhs) const + { + // for array access + size_t i = 0, n = 0; + size_t iL = length(); + size_t nL = rhs.length(); + // create temporary vectors and sort them + std::vector l_lst = this->elements(); + std::vector r_lst = rhs.elements(); + std::sort(l_lst.begin(), l_lst.end(), cmp_simple_selector()); + std::sort(r_lst.begin(), r_lst.end(), cmp_simple_selector()); + // process loop + while (true) + { + // first check for valid index + if (i == iL) return iL == nL; + else if (n == nL) return iL == nL; + // the access the vector items + Simple_Selector_Obj l = l_lst[i]; + Simple_Selector_Obj r = r_lst[n]; + // skip nulls + if (!l) ++i; + if (!r) ++n; + // do the check now + else if (*l != *r) + { return false; } + // advance now + ++i; ++n; + } + // no mismatch + return true; + } + + bool Complex_Selector_Pointer_Compare::operator() (const Complex_Selector_Obj& pLeft, const Complex_Selector_Obj& pRight) const { + return *pLeft < *pRight; + } + + bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) + { + return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); + } + + bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping) + { + Complex_Selector_Ptr lhs = this; + // check for selectors with leading or trailing combinators + if (!lhs->head() || !rhs->head()) + { return false; } + Complex_Selector_Obj l_innermost = lhs->innermost(); + if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF) + { return false; } + Complex_Selector_Obj r_innermost = rhs->innermost(); + if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF) + { return false; } + // more complex (i.e., longer) selectors are always more specific + size_t l_len = lhs->length(), r_len = rhs->length(); + if (l_len > r_len) + { return false; } + + if (l_len == 1) + { return lhs->head()->is_superselector_of(&rhs->last()->head(), wrapping); } + + // we have to look one tail deeper, since we cary the + // combinator around for it (which is important here) + if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { + Complex_Selector_Obj lhs_tail = lhs->tail(); + Complex_Selector_Obj rhs_tail = rhs->tail(); + if (lhs_tail->combinator() != rhs_tail->combinator()) return false; + if (lhs_tail->head() && !rhs_tail->head()) return false; + if (!lhs_tail->head() && rhs_tail->head()) return false; + if (lhs_tail->head() && rhs_tail->head()) { + if (!lhs_tail->head()->is_superselector_of(&rhs_tail->head())) return false; + } + } + + bool found = false; + Complex_Selector_Obj marker = rhs; + for (size_t i = 0, L = rhs->length(); i < L; ++i) { + if (i == L-1) + { return false; } + if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(&marker->head(), wrapping)) + { found = true; break; } + marker = marker->tail(); + } + if (!found) + { return false; } + + /* + Hmm, I hope I have the logic right: + + if lhs has a combinator: + if !(marker has a combinator) return false + if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false + return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost) + else if marker has a combinator: + if !(marker.combinator == ">") return false + return lhs.tail.is_superselector_of(marker.tail) + else + return lhs.tail.is_superselector_of(marker.tail) + */ + if (lhs->combinator() != Complex_Selector::ANCESTOR_OF) + { + if (marker->combinator() == Complex_Selector::ANCESTOR_OF) + { return false; } + if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) + { return false; } + return lhs->tail()->is_superselector_of(&marker->tail()); + } + else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) + { + if (marker->combinator() != Complex_Selector::PARENT_OF) + { return false; } + return lhs->tail()->is_superselector_of(&marker->tail()); + } + else + { + return lhs->tail()->is_superselector_of(&marker->tail()); + } + // catch-all + return false; + } + + size_t Complex_Selector::length() const + { + // TODO: make this iterative + if (!tail()) return 1; + return 1 + tail()->length(); + } + + Complex_Selector_Obj Complex_Selector::context(Context& ctx) + { + if (!tail()) return 0; + if (!head()) return tail()->context(ctx); + Complex_Selector_Obj cpy = SASS_MEMORY_NEW(Complex_Selector, pstate(), combinator(), head(), tail()->context(ctx)); + cpy->media_block(media_block()); + return cpy; + } + + // append another complex selector at the end + // check if we need to append some headers + // then we need to check for the combinator + // only then we can safely set the new tail + void Complex_Selector::append(Context& ctx, Complex_Selector_Obj ss) + { + + Complex_Selector_Obj t = ss->tail(); + Combinator c = ss->combinator(); + String_Obj r = ss->reference(); + Compound_Selector_Obj h = ss->head(); + + if (ss->has_line_feed()) has_line_feed(true); + if (ss->has_line_break()) has_line_break(true); + + // append old headers + if (h && h->length()) { + if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { + error("Invalid parent selector", pstate_); + } else if (last()->head_ && last()->head_->length()) { + Compound_Selector_Obj rh = last()->head(); + size_t i = 0, L = h->length(); + if (SASS_MEMORY_CAST(Element_Selector, h->first())) { + if (Class_Selector_Ptr sq = SASS_MEMORY_CAST(Class_Selector, rh->last())) { + Class_Selector_Ptr sqs = SASS_MEMORY_COPY(sq); + sqs->name(sqs->name() + (*h)[0]->name()); + sqs->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = sqs; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append(&(*h)[i]); + } else if (Id_Selector_Ptr sq = SASS_MEMORY_CAST(Id_Selector, rh->last())) { + Id_Selector_Ptr sqs = SASS_MEMORY_COPY(sq); + sqs->name(sqs->name() + (*h)[0]->name()); + sqs->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = sqs; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append(&(*h)[i]); + } else if (Element_Selector_Ptr ts = SASS_MEMORY_CAST(Element_Selector, rh->last())) { + Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts); + tss->name(tss->name() + (*h)[0]->name()); + tss->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = tss; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append(&(*h)[i]); + } else if (Placeholder_Selector_Ptr ps = SASS_MEMORY_CAST(Placeholder_Selector, rh->last())) { + Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps); + pss->name(pss->name() + (*h)[0]->name()); + pss->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = pss; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append(&(*h)[i]); + } else { + last()->head_->concat(&h); + } + } else { + last()->head_->concat(&h); + } + } else { + last()->head_->concat(&h); + } + } else { + // std::cerr << "has no or empty head\n"; + } + + if (last()) { + if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { + Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate()); + inter->reference(r); + inter->combinator(c); + inter->tail(t); + last()->tail(inter); + } else { + if (last()->combinator() == ANCESTOR_OF) { + last()->combinator(c); + last()->reference(r); + } + last()->tail(t); + } + } + + + } + + Selector_List_Ptr Selector_List::resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent) + { + if (!this->has_parent_ref()) return this; + Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); + Selector_List_Ptr ps = &pstack.back(); + for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { + for (size_t si = 0, sL = this->length(); si < sL; ++si) { + Selector_List_Obj rv = at(si)->resolve_parent_refs(ctx, pstack, implicit_parent); + ss->concat(&rv); + } + } + return ss; + } + + Selector_List_Ptr Complex_Selector::resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent) + { + Complex_Selector_Obj tail = this->tail(); + Compound_Selector_Obj head = this->head(); + Selector_List_Ptr parents = &pstack.back(); + + if (!this->has_real_parent_ref() && !implicit_parent) { + Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate()); + retval->append(this); + return retval; + } + + // first resolve_parent_refs the tail (which may return an expanded list) + Selector_List_Obj tails = tail ? tail->resolve_parent_refs(ctx, pstack, implicit_parent) : 0; + + if (head && head->length() > 0) { + + Selector_List_Obj retval; + // we have a parent selector in a simple compound list + // mix parent complex selector into the compound list + if (SASS_MEMORY_CAST(Parent_Selector, (*head)[0])) { + retval = SASS_MEMORY_NEW(Selector_List, pstate()); + + // it turns out that real parent references reach + // across @at-root rules, which comes unexpected + if (parents == NULL && head->has_real_parent_ref()) { + int i = pstack.size() - 1; + while (!parents && i > -1) { + parents = &pstack.at(i--); + } + } + + if (parents && parents->length()) { + if (tails && tails->length() > 0) { + for (size_t n = 0, nL = tails->length(); n < nL; ++n) { + for (size_t i = 0, iL = parents->length(); i < iL; ++i) { + Complex_Selector_Obj t = (*tails)[n]; + Complex_Selector_Obj parent = (*parents)[i]; + Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); + Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); + ss->tail(t ? SASS_MEMORY_CLONE(t) : 0); + Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); + // remove parent selector from sequence + if (h->length()) h->erase(h->begin()); + ss->head(h->length() ? &h : 0); + // adjust for parent selector (1 char) + if (h->length()) { + ParserState state(h->at(0)->pstate()); + state.offset.column += 1; + state.column -= 1; + (*h)[0]->pstate(state); + } + // keep old parser state + s->pstate(pstate()); + // append new tail + s->append(ctx, ss); + retval->append(s); + } + } + } + // have no tails but parents + // loop above is inside out + else { + for (size_t i = 0, iL = parents->length(); i < iL; ++i) { + Complex_Selector_Obj parent = (*parents)[i]; + Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); + Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); + // this is only if valid if the parent has no trailing op + // otherwise we cannot append more simple selectors to head + if (parent->last()->combinator() != ANCESTOR_OF) { + throw Exception::InvalidParent(&parent, &ss); + } + ss->tail(tail ? SASS_MEMORY_CLONE(tail) : 0); + Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); + // remove parent selector from sequence + if (h->length()) h->erase(h->begin()); + ss->head(h->length() ? &h : 0); + // \/ IMO ruby sass bug \/ + ss->has_line_feed(false); + // adjust for parent selector (1 char) + if (h->length()) { + ParserState state(h->at(0)->pstate()); + state.offset.column += 1; + state.column -= 1; + (*h)[0]->pstate(state); + } + // keep old parser state + s->pstate(pstate()); + // append new tail + s->append(ctx, &ss); + retval->append(s); + } + } + } + // have no parent but some tails + else { + if (tails && tails->length() > 0) { + for (size_t n = 0, nL = tails->length(); n < nL; ++n) { + Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); + cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); + cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); + for (size_t i = 1, L = this->head()->length(); i < L; ++i) + cpy->head()->append(&(*this->head())[i]); + if (!cpy->head()->length()) cpy->head(0); + retval->append(cpy->skip_empty_reference()); + } + } + // have no parent nor tails + else { + Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); + cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); + for (size_t i = 1, L = this->head()->length(); i < L; ++i) + cpy->head()->append(&(*this->head())[i]); + if (!cpy->head()->length()) cpy->head(0); + retval->append(cpy->skip_empty_reference()); + } + } + } + // no parent selector in head + else { + retval = this->tails(ctx, &tails); + } + + for (Simple_Selector_Obj ss : head->elements()) { + if (Wrapped_Selector_Ptr ws = SASS_MEMORY_CAST(Wrapped_Selector, ss)) { + if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) { + if (parents) ws->selector(sl->resolve_parent_refs(ctx, pstack, implicit_parent)); + } + } + } + + return retval.detach(); + + } + // has no head + else { + return this->tails(ctx, &tails); + } + + // unreachable + return 0; + } + + Selector_List_Ptr Complex_Selector::tails(Context& ctx, Selector_List_Ptr tails) + { + Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_); + if (tails && tails->length()) { + for (size_t i = 0, iL = tails->length(); i < iL; ++i) { + Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this); + pr->tail(tails->at(i)); + rv->append(pr); + } + } + else { + rv->append(this); + } + return rv; + } + + // return the last tail that is defined + Complex_Selector_Obj Complex_Selector::first() + { + // declare variables used in loop + Complex_Selector_Obj cur = this; + Compound_Selector_Obj head; + // processing loop + while (cur) + { + // get the head + head = cur->head_; + // abort (and return) if it is not a parent selector + if (!head || head->length() != 1 || !SASS_MEMORY_CAST(Parent_Selector, (*head)[0])) { + break; + } + // advance to next + cur = cur->tail_; + } + // result + return &cur; + } + + // return the last tail that is defined + Complex_Selector_Obj Complex_Selector::last() + { + // ToDo: implement with a while loop + return tail_? tail_->last() : this; + } + + Complex_Selector::Combinator Complex_Selector::clear_innermost() + { + Combinator c; + if (!tail() || tail()->tail() == 0) + { c = combinator(); combinator(ANCESTOR_OF); tail(0); } + else + { c = tail()->clear_innermost(); } + return c; + } + + void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c) + { + if (!tail()) + { tail(val); combinator(c); } + else + { tail()->set_innermost(val, c); } + } + + void Complex_Selector::cloneChildren() + { + if (head()) head(SASS_MEMORY_CLONE(head())); + if (tail()) tail(SASS_MEMORY_CLONE(tail())); + } + + void Compound_Selector::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + void Selector_List::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + void Wrapped_Selector::cloneChildren() + { + selector(SASS_MEMORY_CLONE(selector())); + } + + // remove parent selector references + // basically unwraps parsed selectors + void Selector_List::remove_parent_selectors() + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = length(); i < L; ++i) { + if (!(*this)[i]->head()) continue; + if ((*this)[i]->head()->is_empty_reference()) { + // simply move to the next tail if we have "no" combinator + if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) { + if ((*this)[i]->tail()) { + if ((*this)[i]->has_line_feed()) { + (*this)[i]->tail()->has_line_feed(true); + } + (*this)[i] = (*this)[i]->tail(); + } + } + // otherwise remove the first item from head + else { + (*this)[i]->head()->erase((*this)[i]->head()->begin()); + } + } + } + } + + bool Selector_List::has_parent_ref() + { + for (Complex_Selector_Obj s : elements()) { + if (s && s->has_parent_ref()) return true; + } + return false; + } + + bool Selector_List::has_real_parent_ref() + { + for (Complex_Selector_Obj s : elements()) { + if (s && s->has_real_parent_ref()) return true; + } + return false; + } + + bool Selector_Schema::has_parent_ref() + { + if (String_Schema_Obj schema = SASS_MEMORY_CAST(String_Schema, contents())) { + return schema->length() > 0 && SASS_MEMORY_CAST(Parent_Selector, schema->at(0)) != NULL; + } + return false; + } + + bool Selector_Schema::has_real_parent_ref() + { + if (String_Schema_Obj schema = SASS_MEMORY_CAST(String_Schema, contents())) { + Parent_Selector_Obj p = SASS_MEMORY_CAST(Parent_Selector, schema->at(0)); + return schema->length() > 0 && p && p->is_real_parent_ref(); + } + return false; + } + + void Selector_List::adjust_after_pushing(Complex_Selector_Obj c) + { + // if (c->has_reference()) has_reference(true); + } + + // it's a superselector if every selector of the right side + // list is a superselector of the given left side selector + bool Complex_Selector::is_superselector_of(Selector_List_Obj sub, std::string wrapping) + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = sub->length(); i < L; ++i) { + if (!is_superselector_of(&(*sub)[i], wrapping)) return false; + } + return true; + } + + // it's a superselector if every selector of the right side + // list is a superselector of the given left side selector + bool Selector_List::is_superselector_of(Selector_List_Obj sub, std::string wrapping) + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = sub->length(); i < L; ++i) { + if (!is_superselector_of(&(*sub)[i], wrapping)) return false; + } + return true; + } + + // it's a superselector if every selector on the right side + // is a superselector of any one of the left side selectors + bool Selector_List::is_superselector_of(Compound_Selector_Obj sub, std::string wrapping) + { + // Check every lhs selector against right hand + for(size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->is_superselector_of(sub, wrapping)) return true; + } + return false; + } + + // it's a superselector if every selector on the right side + // is a superselector of any one of the left side selectors + bool Selector_List::is_superselector_of(Complex_Selector_Obj sub, std::string wrapping) + { + // Check every lhs selector against right hand + for(size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->is_superselector_of(sub)) return true; + } + return false; + } + + Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs, Context& ctx) { + std::vector unified_complex_selectors; + // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` + for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { + Complex_Selector_Obj seq1 = (*this)[lhs_i]; + for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { + Complex_Selector_Ptr seq2 = &rhs->at(rhs_i); + + Selector_List_Obj result = seq1->unify_with(seq2, ctx); + if( result ) { + for(size_t i = 0, L = result->length(); i < L; ++i) { + unified_complex_selectors.push_back( &(*result)[i] ); + } + } + } + } + + // Creates the final Selector_List by combining all the complex selectors + Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate()); + for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) { + final_result->append(*itr); + } + return final_result; + } + + void Selector_List::populate_extends(Selector_List_Obj extendee, Context& ctx, Subset_Map& extends) + { + + Selector_List_Ptr extender = this; + for (auto complex_sel : extendee->elements()) { + Complex_Selector_Obj c = complex_sel; + + + // Ignore any parent selectors, until we find the first non Selectorerence head + Compound_Selector_Obj compound_sel = c->head(); + Complex_Selector_Obj pIter = complex_sel; + while (pIter) { + Compound_Selector_Obj pHead = pIter->head(); + if (pHead && SASS_MEMORY_CAST(Parent_Selector, pHead->elements()[0]) == NULL) { + compound_sel = pHead; + break; + } + + pIter = pIter->tail(); + } + + if (!pIter->head() || pIter->tail()) { + error("nested selectors may not be extended", c->pstate()); + } + + compound_sel->is_optional(extendee->is_optional()); + + for (size_t i = 0, L = extender->length(); i < L; ++i) { + extends.put(compound_sel, std::make_pair(&(*extender)[i], &compound_sel)); + } + } + }; + + std::vector Compound_Selector::to_str_vec() + { + std::vector result(length()); + for (size_t i = 0, L = length(); i < L; ++i) + { result.push_back((*this)[i]->to_string()); } + return result; + } + + void Compound_Selector::append(Simple_Selector_Ptr element) + { + Vectorized::append(element); + pstate_.offset += element->pstate().offset; + } + + Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs, Context& ctx) + { + Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate()); + // result->has_parent_reference(has_parent_reference()); + + // not very efficient because it needs to preserve order + for (size_t i = 0, L = length(); i < L; ++i) + { + bool found = false; + std::string thisSelector((*this)[i]->to_string(ctx.c_options)); + for (size_t j = 0, M = rhs->length(); j < M; ++j) + { + if (thisSelector == (*rhs)[j]->to_string(ctx.c_options)) + { + found = true; + break; + } + } + if (!found) result->append(&(*this)[i]); + } + + return result; + } + + void Compound_Selector::mergeSources(SourcesSet& sources, Context& ctx) + { + for (SourcesSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { + this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); + } + } + + Argument_Obj Arguments::get_rest_argument() + { + if (this->has_rest_argument()) { + for (Argument_Obj arg : this->elements()) { + if (arg->is_rest_argument()) { + return arg; + } + } + } + return NULL; + } + + Argument_Obj Arguments::get_keyword_argument() + { + if (this->has_keyword_argument()) { + for (Argument_Obj arg : this->elements()) { + if (arg->is_keyword_argument()) { + return arg; + } + } + } + return NULL; + } + + void Arguments::adjust_after_pushing(Argument_Obj a) + { + if (!a->name().empty()) { + if (/* has_rest_argument_ || */ has_keyword_argument_) { + error("named arguments must precede variable-length argument", a->pstate()); + } + has_named_arguments_ = true; + } + else if (a->is_rest_argument()) { + if (has_rest_argument_) { + error("functions and mixins may only be called with one variable-length argument", a->pstate()); + } + if (has_keyword_argument_) { + error("only keyword arguments may follow variable arguments", a->pstate()); + } + has_rest_argument_ = true; + } + else if (a->is_keyword_argument()) { + if (has_keyword_argument_) { + error("functions and mixins may only be called with one keyword argument", a->pstate()); + } + has_keyword_argument_ = true; + } + else { + if (has_rest_argument_) { + error("ordinal arguments must precede variable-length arguments", a->pstate()); + } + if (has_named_arguments_) { + error("ordinal arguments must precede named arguments", a->pstate()); + } + } + } + + bool Ruleset::is_invisible() const { + if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, selector())) { + for (size_t i = 0, L = sl->length(); i < L; ++i) + if (!(*sl)[i]->has_placeholder()) return false; + } + return true; + } + + bool Media_Block::is_invisible() const { + for (size_t i = 0, L = block()->length(); i < L; ++i) { + Statement_Obj stm = block()->at(i); + if (!stm->is_invisible()) return false; + } + return true; + } + + Number::Number(ParserState pstate, double val, std::string u, bool zero) + : Value(pstate), + value_(val), + zero_(zero), + numerator_units_(std::vector()), + denominator_units_(std::vector()), + hash_(0) + { + size_t l = 0, r = 0; + if (!u.empty()) { + bool nominator = true; + while (true) { + r = u.find_first_of("*/", l); + std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); + if (!unit.empty()) { + if (nominator) numerator_units_.push_back(unit); + else denominator_units_.push_back(unit); + } + if (r == std::string::npos) break; + // ToDo: should error for multiple slashes + // if (!nominator && u[r] == '/') error(...) + if (u[r] == '/') + nominator = false; + l = r + 1; + } + } + concrete_type(NUMBER); + } + + std::string Number::unit() const + { + std::string u; + for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { + if (i) u += '*'; + u += numerator_units_[i]; + } + if (!denominator_units_.empty()) u += '/'; + for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) { + if (i) u += '*'; + u += denominator_units_[i]; + } + return u; + } + + bool Number::is_valid_css_unit() const + { + return numerator_units().size() <= 1 && + denominator_units().size() == 0; + } + + bool Number::is_unitless() const + { return numerator_units_.empty() && denominator_units_.empty(); } + + void Number::normalize(const std::string& prefered, bool strict) + { + + // first make sure same units cancel each other out + // it seems that a map table will fit nicely to do this + // we basically construct exponents for each unit + // has the advantage that they will be pre-sorted + std::map exponents; + + // initialize by summing up occurences in unit vectors + for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]]; + for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]]; + + // the final conversion factor + double factor = 1; + + // get the first entry of numerators + // forward it when entry is converted + std::vector::iterator nom_it = numerator_units_.begin(); + std::vector::iterator nom_end = numerator_units_.end(); + std::vector::iterator denom_it = denominator_units_.begin(); + std::vector::iterator denom_end = denominator_units_.end(); + + // main normalization loop + // should be close to optimal + while (denom_it != denom_end) + { + // get and increment afterwards + const std::string denom = *(denom_it ++); + // skip already canceled out unit + if (exponents[denom] >= 0) continue; + // skip all units we don't know how to convert + if (string_to_unit(denom) == UNKNOWN) continue; + // now search for nominator + while (nom_it != nom_end) + { + // get and increment afterwards + const std::string nom = *(nom_it ++); + // skip already canceled out unit + if (exponents[nom] <= 0) continue; + // skip all units we don't know how to convert + if (string_to_unit(nom) == UNKNOWN) continue; + // we now have two convertable units + // add factor for current conversion + factor *= conversion_factor(nom, denom, strict); + // update nominator/denominator exponent + -- exponents[nom]; ++ exponents[denom]; + // inner loop done + break; + } + } + + // now we can build up the new unit arrays + numerator_units_.clear(); + denominator_units_.clear(); + + // build them by iterating over the exponents + for (auto exp : exponents) + { + // maybe there is more effecient way to push + // the same item multiple times to a vector? + for(size_t i = 0, S = abs(exp.second); i < S; ++i) + { + // opted to have these switches in the inner loop + // makes it more readable and should not cost much + if (!exp.first.empty()) { + if (exp.second < 0) denominator_units_.push_back(exp.first); + else if (exp.second > 0) numerator_units_.push_back(exp.first); + } + } + } + + // apply factor to value_ + // best precision this way + value_ *= factor; + + // maybe convert to other unit + // easier implemented on its own + try { convert(prefered, strict); } + catch (incompatibleUnits& err) + { error(err.what(), pstate()); } + catch (...) { throw; } + + } + + // this does not cover all cases (multiple prefered units) + double Number::convert_factor(const Number& n) const + { + + // first make sure same units cancel each other out + // it seems that a map table will fit nicely to do this + // we basically construct exponents for each unit class + // std::map exponents; + // initialize by summing up occurences in unit vectors + // for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[unit_to_class(numerator_units_[i])]; + // for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[unit_to_class(denominator_units_[i])]; + + std::vector l_miss_nums(0); + std::vector l_miss_dens(0); + // create copy since we need these for state keeping + std::vector r_nums(n.numerator_units_); + std::vector r_dens(n.denominator_units_); + + std::vector::const_iterator l_num_it = numerator_units_.begin(); + std::vector::const_iterator l_num_end = numerator_units_.end(); + + bool l_unitless = is_unitless(); + bool r_unitless = n.is_unitless(); + + // overall conversion + double factor = 1; + + // process all left numerators + while (l_num_it != l_num_end) + { + // get and increment afterwards + const std::string l_num = *(l_num_it ++); + + std::vector::iterator r_num_it = r_nums.begin(); + std::vector::iterator r_num_end = r_nums.end(); + + bool found = false; + // search for compatible numerator + while (r_num_it != r_num_end) + { + // get and increment afterwards + const std::string r_num = *(r_num_it); + // get possible converstion factor for units + double conversion = conversion_factor(l_num, r_num, false); + // skip incompatible numerator + if (conversion == 0) { + ++ r_num_it; + continue; + } + // apply to global factor + factor *= conversion; + // remove item from vector + r_nums.erase(r_num_it); + // found numerator + found = true; + break; + } + // maybe we did not find any + // left numerator is leftover + if (!found) l_miss_nums.push_back(l_num); + } + + std::vector::const_iterator l_den_it = denominator_units_.begin(); + std::vector::const_iterator l_den_end = denominator_units_.end(); + + // process all left denominators + while (l_den_it != l_den_end) + { + // get and increment afterwards + const std::string l_den = *(l_den_it ++); + + std::vector::iterator r_den_it = r_dens.begin(); + std::vector::iterator r_den_end = r_dens.end(); + + bool found = false; + // search for compatible denominator + while (r_den_it != r_den_end) + { + // get and increment afterwards + const std::string r_den = *(r_den_it); + // get possible converstion factor for units + double conversion = conversion_factor(l_den, r_den, false); + // skip incompatible denominator + if (conversion == 0) { + ++ r_den_it; + continue; + } + // apply to global factor + factor *= conversion; + // remove item from vector + r_dens.erase(r_den_it); + // found denominator + found = true; + break; + } + // maybe we did not find any + // left denominator is leftover + if (!found) l_miss_dens.push_back(l_den); + } + + // check left-overs (ToDo: might cancel out) + if (l_miss_nums.size() > 0 && !r_unitless) { + throw Exception::IncompatibleUnits(n, *this); + } + if (l_miss_dens.size() > 0 && !r_unitless) { + throw Exception::IncompatibleUnits(n, *this); + } + if (r_nums.size() > 0 && !l_unitless) { + throw Exception::IncompatibleUnits(n, *this); + } + if (r_dens.size() > 0 && !l_unitless) { + throw Exception::IncompatibleUnits(n, *this); + } + + return factor; + } + + // this does not cover all cases (multiple prefered units) + bool Number::convert(const std::string& prefered, bool strict) + { + // no conversion if unit is empty + if (prefered.empty()) return true; + + // first make sure same units cancel each other out + // it seems that a map table will fit nicely to do this + // we basically construct exponents for each unit + // has the advantage that they will be pre-sorted + std::map exponents; + + // initialize by summing up occurences in unit vectors + for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]]; + for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]]; + + // the final conversion factor + double factor = 1; + + std::vector::iterator denom_it = denominator_units_.begin(); + std::vector::iterator denom_end = denominator_units_.end(); + + // main normalization loop + // should be close to optimal + while (denom_it != denom_end) + { + // get and increment afterwards + const std::string denom = *(denom_it ++); + // check if conversion is needed + if (denom == prefered) continue; + // skip already canceled out unit + if (exponents[denom] >= 0) continue; + // skip all units we don't know how to convert + if (string_to_unit(denom) == UNKNOWN) continue; + // we now have two convertable units + // add factor for current conversion + factor *= conversion_factor(denom, prefered, strict); + // update nominator/denominator exponent + ++ exponents[denom]; -- exponents[prefered]; + } + + std::vector::iterator nom_it = numerator_units_.begin(); + std::vector::iterator nom_end = numerator_units_.end(); + + // now search for nominator + while (nom_it != nom_end) + { + // get and increment afterwards + const std::string nom = *(nom_it ++); + // check if conversion is needed + if (nom == prefered) continue; + // skip already canceled out unit + if (exponents[nom] <= 0) continue; + // skip all units we don't know how to convert + if (string_to_unit(nom) == UNKNOWN) continue; + // we now have two convertable units + // add factor for current conversion + factor *= conversion_factor(nom, prefered, strict); + // update nominator/denominator exponent + -- exponents[nom]; ++ exponents[prefered]; + } + + // now we can build up the new unit arrays + numerator_units_.clear(); + denominator_units_.clear(); + + // build them by iterating over the exponents + for (auto exp : exponents) + { + // maybe there is more effecient way to push + // the same item multiple times to a vector? + for(size_t i = 0, S = abs(exp.second); i < S; ++i) + { + // opted to have these switches in the inner loop + // makes it more readable and should not cost much + if (!exp.first.empty()) { + if (exp.second < 0) denominator_units_.push_back(exp.first); + else if (exp.second > 0) numerator_units_.push_back(exp.first); + } + } + } + + // apply factor to value_ + // best precision this way + value_ *= factor; + + // success? + return true; + + } + + // useful for making one number compatible with another + std::string Number::find_convertible_unit() const + { + for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { + std::string u(numerator_units_[i]); + if (string_to_unit(u) != UNKNOWN) return u; + } + for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) { + std::string u(denominator_units_[i]); + if (string_to_unit(u) != UNKNOWN) return u; + } + return std::string(); + } + + bool Custom_Warning::operator== (const Expression& rhs) const + { + if (Custom_Warning_Ptr_Const r = dynamic_cast(&rhs)) { + return message() == r->message(); + } + return false; + } + + bool Custom_Error::operator== (const Expression& rhs) const + { + if (Custom_Error_Ptr_Const r = dynamic_cast(&rhs)) { + return message() == r->message(); + } + return false; + } + + bool Number::eq (const Expression& rhs) const + { + if (Number_Ptr_Const r = dynamic_cast(&rhs)) { + size_t lhs_units = numerator_units_.size() + denominator_units_.size(); + size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); + if (!lhs_units && !rhs_units) { + return std::fabs(value() - r->value()) < NUMBER_EPSILON; + } + return (numerator_units_ == r->numerator_units_) && + (denominator_units_ == r->denominator_units_) && + std::fabs(value() - r->value()) < NUMBER_EPSILON; + } + return false; + } + + bool Number::operator== (const Expression& rhs) const + { + if (Number_Ptr_Const r = dynamic_cast(&rhs)) { + size_t lhs_units = numerator_units_.size() + denominator_units_.size(); + size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return std::fabs(value() - r->value()) < NUMBER_EPSILON; + } + return (numerator_units_ == r->numerator_units_) && + (denominator_units_ == r->denominator_units_) && + std::fabs(value() - r->value()) < NUMBER_EPSILON; + } + return false; + } + + bool Number::operator< (const Number& rhs) const + { + size_t lhs_units = numerator_units_.size() + denominator_units_.size(); + size_t rhs_units = rhs.numerator_units_.size() + rhs.denominator_units_.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return value() < rhs.value(); + } + + Number tmp_r(&rhs); // copy + tmp_r.normalize(find_convertible_unit()); + std::string l_unit(unit()); + std::string r_unit(tmp_r.unit()); + if (unit() != tmp_r.unit()) { + error("cannot compare numbers with incompatible units", pstate()); + } + return value() < tmp_r.value(); + } + + bool String_Quoted::operator== (const Expression& rhs) const + { + if (String_Quoted_Ptr_Const qstr = dynamic_cast(&rhs)) { + return (value() == qstr->value()); + } else if (String_Constant_Ptr_Const cstr = dynamic_cast(&rhs)) { + return (value() == cstr->value()); + } + return false; + } + + bool String_Constant::is_invisible() const { + return value_.empty() && quote_mark_ == 0; + } + + bool String_Constant::operator== (const Expression& rhs) const + { + if (String_Quoted_Ptr_Const qstr = dynamic_cast(&rhs)) { + return (value() == qstr->value()); + } else if (String_Constant_Ptr_Const cstr = dynamic_cast(&rhs)) { + return (value() == cstr->value()); + } + return false; + } + + bool String_Schema::is_left_interpolant(void) const + { + return length() && first()->is_left_interpolant(); + } + bool String_Schema::is_right_interpolant(void) const + { + return length() && last()->is_right_interpolant(); + } + + bool String_Schema::operator== (const Expression& rhs) const + { + if (String_Schema_Ptr_Const r = dynamic_cast(&rhs)) { + if (length() != r->length()) return false; + for (size_t i = 0, L = length(); i < L; ++i) { + Expression_Obj rv = (*r)[i]; + Expression_Obj lv = (*this)[i]; + if (!lv || !rv) return false; + if (!(*lv == *rv)) return false; + } + return true; + } + return false; + } + + bool Boolean::operator== (const Expression& rhs) const + { + if (Boolean_Ptr_Const r = dynamic_cast(&rhs)) { + return (value() == r->value()); + } + return false; + } + + bool Color::operator== (const Expression& rhs) const + { + if (Color_Ptr_Const r = dynamic_cast(&rhs)) { + return r_ == r->r() && + g_ == r->g() && + b_ == r->b() && + a_ == r->a(); + } + return false; + } + + bool List::operator== (const Expression& rhs) const + { + if (List_Ptr_Const r = dynamic_cast(&rhs)) { + if (length() != r->length()) return false; + if (separator() != r->separator()) return false; + for (size_t i = 0, L = length(); i < L; ++i) { + Expression_Obj rv = r->at(i); + Expression_Obj lv = this->at(i); + if (!lv || !rv) return false; + if (!(*lv == *rv)) return false; + } + return true; + } + return false; + } + + bool Map::operator== (const Expression& rhs) const + { + if (Map_Ptr_Const r = dynamic_cast(&rhs)) { + if (length() != r->length()) return false; + for (auto key : keys()) { + Expression_Obj lv = at(key); + Expression_Obj rv = r->at(key); + if (!rv || !lv) return false; + if (!(*lv == *rv)) return false; + } + return true; + } + return false; + } + + bool Null::operator== (const Expression& rhs) const + { + return rhs.concrete_type() == NULL_VAL; + } + + size_t List::size() const { + if (!is_arglist_) return length(); + // arglist expects a list of arguments + // so we need to break before keywords + for (size_t i = 0, L = length(); i < L; ++i) { + Expression_Obj obj = this->at(i); + if (Argument* arg = dynamic_cast(&obj)) { + if (!arg->name().empty()) return i; + } + } + return length(); + } + + Expression_Obj Hashed::at(Expression_Obj k) const + { + if (elements_.count(k)) + { return elements_.at(k); } + else { return NULL; } + } + + bool Binary_Expression::is_left_interpolant(void) const + { + return is_interpolant() || (left() && left()->is_left_interpolant()); + } + bool Binary_Expression::is_right_interpolant(void) const + { + return is_interpolant() || (right() && right()->is_right_interpolant()); + } + + std::string AST_Node::to_string(Sass_Inspect_Options opt) const + { + Sass_Output_Options out(opt); + Emitter emitter(out); + Inspect i(emitter); + i.in_declaration = true; + // ToDo: inspect should be const + const_cast(this)->perform(&i); + return i.get_buffer(); + } + + std::string AST_Node::to_string() const + { + return to_string({ NESTED, 5 }); + } + + std::string String_Quoted::inspect() const + { + return quote(value_, '*'); + } + + std::string String_Constant::inspect() const + { + return quote(value_, '*'); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Additional method on Lists to retrieve values directly or from an encompassed Argument. + ////////////////////////////////////////////////////////////////////////////////////////// + Expression_Obj List::value_at_index(size_t i) { + Expression_Obj obj = this->at(i); + if (is_arglist_) { + if (Argument* arg = dynamic_cast(&obj)) { + return arg->value(); + } else { + return &obj; + } + } else { + return &obj; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Convert map to (key, value) list. + ////////////////////////////////////////////////////////////////////////////////////////// + List_Obj Map::to_list(Context& ctx, ParserState& pstate) { + List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); + + for (auto key : keys()) { + List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); + l->append(&key); + l->append(at(key)); + ret->append(&l); + } + + return ret; + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Copy implementations + ////////////////////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUG_SHARED_PTR + + #define IMPLEMENT_AST_OPERATORS(klass) \ + klass##_Ptr klass::copy(std::string file, size_t line) const { \ + klass##_Ptr cpy = new klass(this); \ + cpy->trace(file, line); \ + return cpy; \ + } \ + klass##_Ptr klass::clone(std::string file, size_t line) const { \ + klass##_Ptr cpy = copy(file, line); \ + cpy->cloneChildren(); \ + return cpy; \ + } \ + + #else + + #define IMPLEMENT_AST_OPERATORS(klass) \ + klass##_Ptr klass::copy() const { \ + return new klass(this); \ + } \ + klass##_Ptr klass::clone() const { \ + klass##_Ptr cpy = copy(); \ + cpy->cloneChildren(); \ + return cpy; \ + } \ + + #endif + + IMPLEMENT_AST_OPERATORS(Supports_Operator); + IMPLEMENT_AST_OPERATORS(Supports_Negation); + IMPLEMENT_AST_OPERATORS(Compound_Selector); + IMPLEMENT_AST_OPERATORS(Complex_Selector); + IMPLEMENT_AST_OPERATORS(Element_Selector); + IMPLEMENT_AST_OPERATORS(Class_Selector); + IMPLEMENT_AST_OPERATORS(Id_Selector); + IMPLEMENT_AST_OPERATORS(Pseudo_Selector); + IMPLEMENT_AST_OPERATORS(Wrapped_Selector); + IMPLEMENT_AST_OPERATORS(Selector_List); + IMPLEMENT_AST_OPERATORS(Ruleset); + IMPLEMENT_AST_OPERATORS(Media_Block); + IMPLEMENT_AST_OPERATORS(Custom_Warning); + IMPLEMENT_AST_OPERATORS(Custom_Error); + IMPLEMENT_AST_OPERATORS(List); + IMPLEMENT_AST_OPERATORS(Map); + IMPLEMENT_AST_OPERATORS(Number); + IMPLEMENT_AST_OPERATORS(Binary_Expression); + IMPLEMENT_AST_OPERATORS(String_Schema); + IMPLEMENT_AST_OPERATORS(String_Constant); + IMPLEMENT_AST_OPERATORS(String_Quoted); + IMPLEMENT_AST_OPERATORS(Boolean); + IMPLEMENT_AST_OPERATORS(Color); + IMPLEMENT_AST_OPERATORS(Null); + IMPLEMENT_AST_OPERATORS(Parent_Selector); + IMPLEMENT_AST_OPERATORS(Import); + IMPLEMENT_AST_OPERATORS(Import_Stub); + IMPLEMENT_AST_OPERATORS(Function_Call); + IMPLEMENT_AST_OPERATORS(Directive); + IMPLEMENT_AST_OPERATORS(At_Root_Block); + IMPLEMENT_AST_OPERATORS(Supports_Block); + IMPLEMENT_AST_OPERATORS(While); + IMPLEMENT_AST_OPERATORS(Each); + IMPLEMENT_AST_OPERATORS(For); + IMPLEMENT_AST_OPERATORS(If); + IMPLEMENT_AST_OPERATORS(Mixin_Call); + IMPLEMENT_AST_OPERATORS(Extension); + IMPLEMENT_AST_OPERATORS(Media_Query); + IMPLEMENT_AST_OPERATORS(Media_Query_Expression); + IMPLEMENT_AST_OPERATORS(Debug); + IMPLEMENT_AST_OPERATORS(Error); + IMPLEMENT_AST_OPERATORS(Warning); + IMPLEMENT_AST_OPERATORS(Assignment); + IMPLEMENT_AST_OPERATORS(Return); + IMPLEMENT_AST_OPERATORS(At_Root_Query); + IMPLEMENT_AST_OPERATORS(Variable); + IMPLEMENT_AST_OPERATORS(Comment); + IMPLEMENT_AST_OPERATORS(Attribute_Selector); + IMPLEMENT_AST_OPERATORS(Supports_Interpolation); + IMPLEMENT_AST_OPERATORS(Supports_Declaration); + IMPLEMENT_AST_OPERATORS(Supports_Condition); + IMPLEMENT_AST_OPERATORS(Parameters); + IMPLEMENT_AST_OPERATORS(Parameter); + IMPLEMENT_AST_OPERATORS(Arguments); + IMPLEMENT_AST_OPERATORS(Argument); + IMPLEMENT_AST_OPERATORS(Unary_Expression); + IMPLEMENT_AST_OPERATORS(Function_Call_Schema); + IMPLEMENT_AST_OPERATORS(Block); + IMPLEMENT_AST_OPERATORS(Content); + IMPLEMENT_AST_OPERATORS(Textual); + IMPLEMENT_AST_OPERATORS(Trace); + IMPLEMENT_AST_OPERATORS(Keyframe_Rule); + IMPLEMENT_AST_OPERATORS(Bubble); + IMPLEMENT_AST_OPERATORS(Selector_Schema); + IMPLEMENT_AST_OPERATORS(Placeholder_Selector); + IMPLEMENT_AST_OPERATORS(Definition); + IMPLEMENT_AST_OPERATORS(Declaration); + +} diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp new file mode 100755 index 000000000..3df08888e --- /dev/null +++ b/src/libsass/src/ast.hpp @@ -0,0 +1,3091 @@ +#ifndef SASS_AST_H +#define SASS_AST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#ifdef DEBUG_SHARED_PTR + +#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy(std::string, size_t) const = 0; \ + virtual klass##_Ptr clone(std::string, size_t) const = 0; \ + +#define ATTACH_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy(std::string, size_t) const; \ + virtual klass##_Ptr clone(std::string, size_t) const; \ + +#else + +#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy() const = 0; \ + virtual klass##_Ptr clone() const = 0; \ + +#define ATTACH_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy() const; \ + virtual klass##_Ptr clone() const; \ + +#endif + +#ifdef __clang__ + +/* + * There are some overloads used here that trigger the clang overload + * hiding warning. Specifically: + * + * Type type() which hides string type() from Expression + * + */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Woverloaded-virtual" + +#endif + +#include "util.hpp" +#include "units.hpp" +#include "context.hpp" +#include "position.hpp" +#include "constants.hpp" +#include "operation.hpp" +#include "position.hpp" +#include "inspect.hpp" +#include "source_map.hpp" +#include "environment.hpp" +#include "error_handling.hpp" +#include "ast_def_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "source_map.hpp" + +#include "sass.h" + +namespace Sass { + + // easier to search with name + const bool DELAYED = true; + + // ToDo: should this really be hardcoded + // Note: most methods follow precision option + const double NUMBER_EPSILON = 0.00000000000001; + + // ToDo: where does this fit best? + // We don't share this with C-API? + class Operand { + public: + Operand(Sass_OP operand, bool ws_before = false, bool ws_after = false) + : operand(operand), ws_before(ws_before), ws_after(ws_after) + { } + public: + enum Sass_OP operand; + bool ws_before; + bool ws_after; + }; + + ////////////////////////////////////////////////////////// + // `hash_combine` comes from boost (functional/hash): + // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html + // Boost Software License - Version 1.0 + // http://www.boost.org/users/license.html + template + void hash_combine (std::size_t& seed, const T& val) + { + seed ^= std::hash()(val) + 0x9e3779b9 + + (seed<<6) + (seed>>2); + } + ////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////// + // Abstract base class for all abstract syntax tree nodes. + ////////////////////////////////////////////////////////// + class AST_Node : public SharedObj { + ADD_PROPERTY(ParserState, pstate) + public: + AST_Node(ParserState pstate) + : pstate_(pstate) + { } + AST_Node(const AST_Node* ptr) + : pstate_(ptr->pstate_) + { } + + // AST_Node(AST_Node& ptr) = delete; + + virtual ~AST_Node() = 0; + virtual size_t hash() { return 0; } + ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node); + virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } + virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } + virtual std::string to_string(Sass_Inspect_Options opt) const; + virtual std::string to_string() const; + virtual void cloneChildren() {}; + public: + void update_pstate(const ParserState& pstate); + void set_pstate_offset(const Offset& offset); + public: + Offset off() { return pstate(); } + Position pos() { return pstate(); } + ATTACH_OPERATIONS() + }; + inline AST_Node::~AST_Node() { } + + + ////////////////////////////////////////////////////////////////////// + // Abstract base class for expressions. This side of the AST hierarchy + // represents elements in value contexts, which exist primarily to be + // evaluated and returned. + ////////////////////////////////////////////////////////////////////// + class Expression : public AST_Node { + public: + enum Concrete_Type { + NONE, + BOOLEAN, + NUMBER, + COLOR, + STRING, + LIST, + MAP, + SELECTOR, + NULL_VAL, + C_WARNING, + C_ERROR, + FUNCTION, + NUM_TYPES + }; + enum Simple_Type { + SIMPLE, + ATTR_SEL, + PSEUDO_SEL, + WRAPPED_SEL, + }; + private: + // expressions in some contexts shouldn't be evaluated + ADD_PROPERTY(bool, is_delayed) + ADD_PROPERTY(bool, is_expanded) + ADD_PROPERTY(bool, is_interpolant) + ADD_PROPERTY(Concrete_Type, concrete_type) + public: + Expression(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : AST_Node(pstate), + is_delayed_(d), + is_expanded_(e), + is_interpolant_(i), + concrete_type_(ct) + { } + Expression(const Expression* ptr) + : AST_Node(ptr), + is_delayed_(ptr->is_delayed_), + is_expanded_(ptr->is_expanded_), + is_interpolant_(ptr->is_interpolant_), + concrete_type_(ptr->concrete_type_) + { } + virtual operator bool() { return true; } + virtual ~Expression() { } + virtual std::string type() { return ""; /* TODO: raise an error? */ } + virtual bool is_invisible() const { return false; } + static std::string type_name() { return ""; } + virtual bool is_false() { return false; } + // virtual bool is_true() { return !is_false(); } + virtual bool operator== (const Expression& rhs) const { return false; } + virtual bool eq(const Expression& rhs) const { return *this == rhs; }; + virtual void set_delayed(bool delayed) { is_delayed(delayed); } + virtual bool has_interpolant() const { return is_interpolant(); } + virtual bool is_left_interpolant() const { return is_interpolant(); } + virtual bool is_right_interpolant() const { return is_interpolant(); } + virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } + virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } + ATTACH_VIRTUAL_AST_OPERATIONS(Expression); + virtual size_t hash() { return 0; } + }; + + ////////////////////////////////////////////////////////////////////// + // Still just an expression, but with a to_string method + ////////////////////////////////////////////////////////////////////// + class PreValue : public Expression { + public: + PreValue(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : Expression(pstate, d, e, i, ct) + { } + PreValue(const PreValue* ptr) + : Expression(ptr) + { } + ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); + virtual ~PreValue() { } + }; + + ////////////////////////////////////////////////////////////////////// + // base class for values that support operations + ////////////////////////////////////////////////////////////////////// + class Value : public Expression { + public: + Value(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : Expression(pstate, d, e, i, ct) + { } + Value(const Value* ptr) + : Expression(ptr) + { } + ATTACH_VIRTUAL_AST_OPERATIONS(Value); + virtual bool operator== (const Expression& rhs) const = 0; + }; +} + +///////////////////////////////////////////////////////////////////////////////////// +// Hash method specializations for std::unordered_map to work with Sass::Expression +///////////////////////////////////////////////////////////////////////////////////// + +namespace std { + template<> + struct hash + { + size_t operator()(Sass::Expression_Obj s) const + { + return s->hash(); + } + }; + template<> + struct equal_to + { + bool operator()( Sass::Expression_Obj lhs, Sass::Expression_Obj rhs) const + { + return lhs->hash() == rhs->hash(); + } + }; +} + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like vectors. Uses the + // "Template Method" design pattern to allow subclasses to adjust their flags + // when certain objects are pushed. + ///////////////////////////////////////////////////////////////////////////// + template + class Vectorized { + std::vector elements_; + protected: + size_t hash_; + void reset_hash() { hash_ = 0; } + virtual void adjust_after_pushing(T element) { } + public: + Vectorized(size_t s = 0) : elements_(std::vector()), hash_(0) + { elements_.reserve(s); } + virtual ~Vectorized() = 0; + size_t length() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + void clear() { return elements_.clear(); } + T last() const { return elements_.back(); } + T first() const { return elements_.front(); } + T& operator[](size_t i) { return elements_[i]; } + virtual const T& at(size_t i) const { return elements_.at(i); } + virtual T& at(size_t i) { return elements_.at(i); } + const T& operator[](size_t i) const { return elements_[i]; } + virtual void append(T element) + { + if (element) { + reset_hash(); + elements_.push_back(element); + adjust_after_pushing(element); + } + } + virtual void concat(Vectorized* v) + { + for (size_t i = 0, L = v->length(); i < L; ++i) this->append((*v)[i]); + } + Vectorized& unshift(T element) + { + elements_.insert(elements_.begin(), element); + return *this; + } + std::vector& elements() { return elements_; } + const std::vector& elements() const { return elements_; } + std::vector& elements(std::vector& e) { elements_ = e; return elements_; } + + virtual size_t hash() + { + if (hash_ == 0) { + for (T& el : elements_) { + hash_combine(hash_, el->hash()); + } + } + return hash_; + } + + typename std::vector::iterator end() { return elements_.end(); } + typename std::vector::iterator begin() { return elements_.begin(); } + typename std::vector::const_iterator end() const { return elements_.end(); } + typename std::vector::const_iterator begin() const { return elements_.begin(); } + typename std::vector::iterator erase(typename std::vector::iterator el) { return elements_.erase(el); } + typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { return elements_.erase(el); } + + }; + template + inline Vectorized::~Vectorized() { } + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like a hash table. Uses an + // extra internally to maintain insertion order for interation. + ///////////////////////////////////////////////////////////////////////////// + class Hashed { + private: + ExpressionMap elements_; + std::vector list_; + protected: + size_t hash_; + Expression_Obj duplicate_key_; + void reset_hash() { hash_ = 0; } + void reset_duplicate_key() { duplicate_key_ = 0; } + virtual void adjust_after_pushing(std::pair p) { } + public: + Hashed(size_t s = 0) : elements_(ExpressionMap(s)), list_(std::vector()) + { elements_.reserve(s); list_.reserve(s); reset_duplicate_key(); } + virtual ~Hashed(); + size_t length() const { return list_.size(); } + bool empty() const { return list_.empty(); } + bool has(Expression_Obj k) const { return elements_.count(k) == 1; } + Expression_Obj at(Expression_Obj k) const; + bool has_duplicate_key() const { return duplicate_key_ != 0; } + Expression_Obj get_duplicate_key() const { return duplicate_key_; } + const ExpressionMap elements() { return elements_; } + Hashed& operator<<(std::pair p) + { + reset_hash(); + + if (!has(p.first)) list_.push_back(p.first); + else if (!duplicate_key_) duplicate_key_ = p.first; + + elements_[p.first] = p.second; + + adjust_after_pushing(p); + return *this; + } + Hashed& operator+=(Hashed* h) + { + if (length() == 0) { + this->elements_ = h->elements_; + this->list_ = h->list_; + return *this; + } + + for (auto key : h->keys()) { + *this << std::make_pair(key, h->at(key)); + } + + reset_duplicate_key(); + return *this; + } + const ExpressionMap& pairs() const { return elements_; } + const std::vector& keys() const { return list_; } + +// std::unordered_map::iterator end() { return elements_.end(); } +// std::unordered_map::iterator begin() { return elements_.begin(); } +// std::unordered_map::const_iterator end() const { return elements_.end(); } +// std::unordered_map::const_iterator begin() const { return elements_.begin(); } + + }; + inline Hashed::~Hashed() { } + + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements. This side of the AST hierarchy + // represents elements in expansion contexts, which exist primarily to be + // rewritten and macro-expanded. + ///////////////////////////////////////////////////////////////////////// + class Statement : public AST_Node { + public: + enum Statement_Type { + NONE, + RULESET, + MEDIA, + DIRECTIVE, + SUPPORTS, + ATROOT, + BUBBLE, + CONTENT, + KEYFRAMERULE, + DECLARATION, + ASSIGNMENT, + IMPORT_STUB, + IMPORT, + COMMENT, + WARNING, + RETURN, + EXTEND, + ERROR, + DEBUGSTMT, + WHILE, + EACH, + FOR, + IF + }; + private: + ADD_PROPERTY(Statement_Type, statement_type) + ADD_PROPERTY(size_t, tabs) + ADD_PROPERTY(bool, group_end) + public: + Statement(ParserState pstate, Statement_Type st = NONE, size_t t = 0) + : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false) + { } + Statement(const Statement* ptr) + : AST_Node(ptr), + statement_type_(ptr->statement_type_), + tabs_(ptr->tabs_), + group_end_(ptr->group_end_) + { } + virtual ~Statement() = 0; + // needed for rearranging nested rulesets during CSS emission + virtual bool is_invisible() const { return false; } + virtual bool bubbles() { return false; } + virtual bool has_content() + { + return statement_type_ == CONTENT; + } + }; + inline Statement::~Statement() { } + + //////////////////////// + // Blocks of statements. + //////////////////////// + class Block : public Statement, public Vectorized { + ADD_PROPERTY(bool, is_root) + ADD_PROPERTY(bool, is_at_root); + // needed for properly formatted CSS emission + protected: + void adjust_after_pushing(Statement_Obj s) + { + } + public: + Block(ParserState pstate, size_t s = 0, bool r = false) + : Statement(pstate), + Vectorized(s), + is_root_(r), + is_at_root_(false) + { } + Block(const Block* ptr) + : Statement(ptr), + Vectorized(*ptr), + is_root_(ptr->is_root_), + is_at_root_(ptr->is_at_root_) + { } + virtual bool has_content() + { + for (size_t i = 0, L = elements().size(); i < L; ++i) { + if (elements()[i]->has_content()) return true; + } + return Statement::has_content(); + } + ATTACH_AST_OPERATIONS(Block) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for statements that contain blocks of statements. + //////////////////////////////////////////////////////////////////////// + class Has_Block : public Statement { + ADD_PROPERTY(Block_Obj, block) + public: + Has_Block(ParserState pstate, Block_Obj b) + : Statement(pstate), block_(b) + { } + Has_Block(const Has_Block* ptr) + : Statement(ptr), block_(ptr->block_) + { } + virtual bool has_content() + { + return (block_ && block_->has_content()) || Statement::has_content(); + } + virtual ~Has_Block() = 0; + }; + inline Has_Block::~Has_Block() { } + + ///////////////////////////////////////////////////////////////////////////// + // Rulesets (i.e., sets of styles headed by a selector and containing a block + // of style declarations. + ///////////////////////////////////////////////////////////////////////////// + class Ruleset : public Has_Block { + ADD_PROPERTY(Selector_Obj, selector) + ADD_PROPERTY(bool, at_root); + ADD_PROPERTY(bool, is_root); + public: + Ruleset(ParserState pstate, Selector_Obj s = 0, Block_Obj b = 0) + : Has_Block(pstate, b), selector_(s), at_root_(false), is_root_(false) + { statement_type(RULESET); } + Ruleset(const Ruleset* ptr) + : Has_Block(ptr), + selector_(ptr->selector_), + at_root_(ptr->at_root_), + is_root_(ptr->is_root_) + { statement_type(RULESET); } + bool is_invisible() const; + ATTACH_AST_OPERATIONS(Ruleset) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Bubble. + ///////////////// + class Bubble : public Statement { + ADD_PROPERTY(Statement_Obj, node) + ADD_PROPERTY(bool, group_end) + public: + Bubble(ParserState pstate, Statement_Obj n, Statement_Obj g = 0, size_t t = 0) + : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == 0) + { } + Bubble(const Bubble* ptr) + : Statement(ptr), + node_(ptr->node_), + group_end_(ptr->group_end_) + { } + bool bubbles() { return true; } + ATTACH_AST_OPERATIONS(Bubble) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Trace. + ///////////////// + class Trace : public Has_Block { + ADD_PROPERTY(std::string, name) + public: + Trace(ParserState pstate, std::string n, Block_Obj b = 0) + : Has_Block(pstate, b), name_(n) + { } + Trace(const Trace* ptr) + : Has_Block(ptr), + name_(ptr->name_) + { } + ATTACH_AST_OPERATIONS(Trace) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Media queries. + ///////////////// + class Media_Block : public Has_Block { + ADD_PROPERTY(List_Obj, media_queries) + public: + Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b) + : Has_Block(pstate, b), media_queries_(mqs) + { statement_type(MEDIA); } + Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b, Selector_Obj s) + : Has_Block(pstate, b), media_queries_(mqs) + { statement_type(MEDIA); } + Media_Block(const Media_Block* ptr) + : Has_Block(ptr), media_queries_(ptr->media_queries_) + { statement_type(MEDIA); } + bool bubbles() { return true; } + bool is_invisible() const; + ATTACH_AST_OPERATIONS(Media_Block) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // At-rules -- arbitrary directives beginning with "@" that may have an + // optional statement block. + /////////////////////////////////////////////////////////////////////// + class Directive : public Has_Block { + ADD_PROPERTY(std::string, keyword) + ADD_PROPERTY(Selector_Obj, selector) + ADD_PROPERTY(Expression_Obj, value) + public: + Directive(ParserState pstate, std::string kwd, Selector_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0) + : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed + { statement_type(DIRECTIVE); } + Directive(const Directive* ptr) + : Has_Block(ptr), + keyword_(ptr->keyword_), + selector_(ptr->selector_), + value_(ptr->value_) // set value manually if needed + { statement_type(DIRECTIVE); } + bool bubbles() { return is_keyframes() || is_media(); } + bool is_media() { + return keyword_.compare("@-webkit-media") == 0 || + keyword_.compare("@-moz-media") == 0 || + keyword_.compare("@-o-media") == 0 || + keyword_.compare("@media") == 0; + } + bool is_keyframes() { + return keyword_.compare("@-webkit-keyframes") == 0 || + keyword_.compare("@-moz-keyframes") == 0 || + keyword_.compare("@-o-keyframes") == 0 || + keyword_.compare("@keyframes") == 0; + } + ATTACH_AST_OPERATIONS(Directive) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // Keyframe-rules -- the child blocks of "@keyframes" nodes. + /////////////////////////////////////////////////////////////////////// + class Keyframe_Rule : public Has_Block { + // according to css spec, this should be + // = | + ADD_PROPERTY(Selector_Obj, name) + public: + Keyframe_Rule(ParserState pstate, Block_Obj b) + : Has_Block(pstate, b), name_() + { statement_type(KEYFRAMERULE); } + Keyframe_Rule(const Keyframe_Rule* ptr) + : Has_Block(ptr), name_(ptr->name_) + { statement_type(KEYFRAMERULE); } + ATTACH_AST_OPERATIONS(Keyframe_Rule) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Declarations -- style rules consisting of a property name and values. + //////////////////////////////////////////////////////////////////////// + class Declaration : public Has_Block { + ADD_PROPERTY(String_Obj, property) + ADD_PROPERTY(Expression_Obj, value) + ADD_PROPERTY(bool, is_important) + ADD_PROPERTY(bool, is_indented) + public: + Declaration(ParserState pstate, + String_Obj prop, Expression_Obj val, bool i = false, Block_Obj b = 0) + : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_indented_(false) + { statement_type(DECLARATION); } + Declaration(const Declaration* ptr) + : Has_Block(ptr), + property_(ptr->property_), + value_(ptr->value_), + is_important_(ptr->is_important_), + is_indented_(ptr->is_indented_) + { statement_type(DECLARATION); } + ATTACH_AST_OPERATIONS(Declaration) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////// + // Assignments -- variable and value. + ///////////////////////////////////// + class Assignment : public Statement { + ADD_PROPERTY(std::string, variable) + ADD_PROPERTY(Expression_Obj, value) + ADD_PROPERTY(bool, is_default) + ADD_PROPERTY(bool, is_global) + public: + Assignment(ParserState pstate, + std::string var, Expression_Obj val, + bool is_default = false, + bool is_global = false) + : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global) + { statement_type(ASSIGNMENT); } + Assignment(const Assignment* ptr) + : Statement(ptr), + variable_(ptr->variable_), + value_(ptr->value_), + is_default_(ptr->is_default_), + is_global_(ptr->is_global_) + { statement_type(ASSIGNMENT); } + ATTACH_AST_OPERATIONS(Assignment) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Import directives. CSS and Sass import lists can be intermingled, so it's + // necessary to store a list of each in an Import node. + //////////////////////////////////////////////////////////////////////////// + class Import : public Statement { + std::vector urls_; + std::vector incs_; + ADD_PROPERTY(List_Obj, import_queries); + public: + Import(ParserState pstate) + : Statement(pstate), + urls_(std::vector()), + incs_(std::vector()), + import_queries_() + { statement_type(IMPORT); } + Import(const Import* ptr) + : Statement(ptr), + urls_(ptr->urls_), + incs_(ptr->incs_), + import_queries_(ptr->import_queries_) + { statement_type(IMPORT); } + std::vector& urls() { return urls_; } + std::vector& incs() { return incs_; } + ATTACH_AST_OPERATIONS(Import) + ATTACH_OPERATIONS() + }; + + // not yet resolved single import + // so far we only know requested name + class Import_Stub : public Statement { + Include resource_; + public: + std::string abs_path() { return resource_.abs_path; }; + std::string imp_path() { return resource_.imp_path; }; + Include resource() { return resource_; }; + + Import_Stub(ParserState pstate, Include res) + : Statement(pstate), resource_(res) + { statement_type(IMPORT_STUB); } + Import_Stub(const Import_Stub* ptr) + : Statement(ptr), resource_(ptr->resource_) + { statement_type(IMPORT_STUB); } + ATTACH_AST_OPERATIONS(Import_Stub) + ATTACH_OPERATIONS() + }; + + ////////////////////////////// + // The Sass `@warn` directive. + ////////////////////////////// + class Warning : public Statement { + ADD_PROPERTY(Expression_Obj, message) + public: + Warning(ParserState pstate, Expression_Obj msg) + : Statement(pstate), message_(msg) + { statement_type(WARNING); } + Warning(const Warning* ptr) + : Statement(ptr), message_(ptr->message_) + { statement_type(WARNING); } + ATTACH_AST_OPERATIONS(Warning) + ATTACH_OPERATIONS() + }; + + /////////////////////////////// + // The Sass `@error` directive. + /////////////////////////////// + class Error : public Statement { + ADD_PROPERTY(Expression_Obj, message) + public: + Error(ParserState pstate, Expression_Obj msg) + : Statement(pstate), message_(msg) + { statement_type(ERROR); } + Error(const Error* ptr) + : Statement(ptr), message_(ptr->message_) + { statement_type(ERROR); } + ATTACH_AST_OPERATIONS(Error) + ATTACH_OPERATIONS() + }; + + /////////////////////////////// + // The Sass `@debug` directive. + /////////////////////////////// + class Debug : public Statement { + ADD_PROPERTY(Expression_Obj, value) + public: + Debug(ParserState pstate, Expression_Obj val) + : Statement(pstate), value_(val) + { statement_type(DEBUGSTMT); } + Debug(const Debug* ptr) + : Statement(ptr), value_(ptr->value_) + { statement_type(DEBUGSTMT); } + ATTACH_AST_OPERATIONS(Debug) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////// + // CSS comments. These may be interpolated. + /////////////////////////////////////////// + class Comment : public Statement { + ADD_PROPERTY(String_Obj, text) + ADD_PROPERTY(bool, is_important) + public: + Comment(ParserState pstate, String_Obj txt, bool is_important) + : Statement(pstate), text_(txt), is_important_(is_important) + { statement_type(COMMENT); } + Comment(const Comment* ptr) + : Statement(ptr), + text_(ptr->text_), + is_important_(ptr->is_important_) + { statement_type(COMMENT); } + virtual bool is_invisible() const + { return /* is_important() == */ false; } + ATTACH_AST_OPERATIONS(Comment) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////// + // The Sass `@if` control directive. + //////////////////////////////////// + class If : public Has_Block { + ADD_PROPERTY(Expression_Obj, predicate) + ADD_PROPERTY(Block_Obj, alternative) + public: + If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0) + : Has_Block(pstate, &con), predicate_(pred), alternative_(alt) + { statement_type(IF); } + If(const If* ptr) + : Has_Block(ptr), + predicate_(ptr->predicate_), + alternative_(ptr->alternative_) + { statement_type(IF); } + virtual bool has_content() + { + return Has_Block::has_content() || (alternative_ && alternative_->has_content()); + } + ATTACH_AST_OPERATIONS(If) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////// + // The Sass `@for` control directive. + ///////////////////////////////////// + class For : public Has_Block { + ADD_PROPERTY(std::string, variable) + ADD_PROPERTY(Expression_Obj, lower_bound) + ADD_PROPERTY(Expression_Obj, upper_bound) + ADD_PROPERTY(bool, is_inclusive) + public: + For(ParserState pstate, + std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc) + : Has_Block(pstate, b), + variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) + { statement_type(FOR); } + For(const For* ptr) + : Has_Block(ptr), + variable_(ptr->variable_), + lower_bound_(ptr->lower_bound_), + upper_bound_(ptr->upper_bound_), + is_inclusive_(ptr->is_inclusive_) + { statement_type(FOR); } + ATTACH_AST_OPERATIONS(For) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////// + // The Sass `@each` control directive. + ////////////////////////////////////// + class Each : public Has_Block { + ADD_PROPERTY(std::vector, variables) + ADD_PROPERTY(Expression_Obj, list) + public: + Each(ParserState pstate, std::vector vars, Expression_Obj lst, Block_Obj b) + : Has_Block(pstate, b), variables_(vars), list_(lst) + { statement_type(EACH); } + Each(const Each* ptr) + : Has_Block(ptr), variables_(ptr->variables_), list_(ptr->list_) + { statement_type(EACH); } + ATTACH_AST_OPERATIONS(Each) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////// + // The Sass `@while` control directive. + /////////////////////////////////////// + class While : public Has_Block { + ADD_PROPERTY(Expression_Obj, predicate) + public: + While(ParserState pstate, Expression_Obj pred, Block_Obj b) + : Has_Block(pstate, b), predicate_(pred) + { statement_type(WHILE); } + While(const While* ptr) + : Has_Block(ptr), predicate_(ptr->predicate_) + { statement_type(WHILE); } + ATTACH_AST_OPERATIONS(While) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////// + // The @return directive for use inside SassScript functions. + ///////////////////////////////////////////////////////////// + class Return : public Statement { + ADD_PROPERTY(Expression_Obj, value) + public: + Return(ParserState pstate, Expression_Obj val) + : Statement(pstate), value_(val) + { statement_type(RETURN); } + Return(const Return* ptr) + : Statement(ptr), value_(ptr->value_) + { statement_type(RETURN); } + ATTACH_AST_OPERATIONS(Return) + ATTACH_OPERATIONS() + }; + + //////////////////////////////// + // The Sass `@extend` directive. + //////////////////////////////// + class Extension : public Statement { + ADD_PROPERTY(Selector_Obj, selector) + public: + Extension(ParserState pstate, Selector_Obj s) + : Statement(pstate), selector_(s) + { statement_type(EXTEND); } + Extension(const Extension* ptr) + : Statement(ptr), selector_(ptr->selector_) + { statement_type(EXTEND); } + ATTACH_AST_OPERATIONS(Extension) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////////////// + // Definitions for both mixins and functions. The two cases are distinguished + // by a type tag. + ///////////////////////////////////////////////////////////////////////////// + struct Backtrace; + typedef Environment Env; + typedef const char* Signature; + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); + class Definition : public Has_Block { + public: + enum Type { MIXIN, FUNCTION }; + ADD_PROPERTY(std::string, name) + ADD_PROPERTY(Parameters_Obj, parameters) + ADD_PROPERTY(Env*, environment) + ADD_PROPERTY(Type, type) + ADD_PROPERTY(Native_Function, native_function) + ADD_PROPERTY(Sass_Function_Entry, c_function) + ADD_PROPERTY(void*, cookie) + ADD_PROPERTY(bool, is_overload_stub) + ADD_PROPERTY(Signature, signature) + public: + Definition(const Definition* ptr) + : Has_Block(ptr), + name_(ptr->name_), + parameters_(ptr->parameters_), + environment_(ptr->environment_), + type_(ptr->type_), + native_function_(ptr->native_function_), + c_function_(ptr->c_function_), + cookie_(ptr->cookie_), + is_overload_stub_(ptr->is_overload_stub_), + signature_(ptr->signature_) + { } + + Definition(ParserState pstate, + std::string n, + Parameters_Obj params, + Block_Obj b, + Type t) + : Has_Block(pstate, b), + name_(n), + parameters_(params), + environment_(0), + type_(t), + native_function_(0), + c_function_(0), + cookie_(0), + is_overload_stub_(false), + signature_(0) + { } + Definition(ParserState pstate, + Signature sig, + std::string n, + Parameters_Obj params, + Native_Function func_ptr, + bool overload_stub = false) + : Has_Block(pstate, 0), + name_(n), + parameters_(params), + environment_(0), + type_(FUNCTION), + native_function_(func_ptr), + c_function_(0), + cookie_(0), + is_overload_stub_(overload_stub), + signature_(sig) + { } + Definition(ParserState pstate, + Signature sig, + std::string n, + Parameters_Obj params, + Sass_Function_Entry c_func, + bool whatever, + bool whatever2) + : Has_Block(pstate, 0), + name_(n), + parameters_(params), + environment_(0), + type_(FUNCTION), + native_function_(0), + c_function_(c_func), + cookie_(sass_function_get_cookie(c_func)), + is_overload_stub_(false), + signature_(sig) + { } + ATTACH_AST_OPERATIONS(Definition) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////// + // Mixin calls (i.e., `@include ...`). + ////////////////////////////////////// + class Mixin_Call : public Has_Block { + ADD_PROPERTY(std::string, name) + ADD_PROPERTY(Arguments_Obj, arguments) + public: + Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0) + : Has_Block(pstate, b), name_(n), arguments_(args) + { } + Mixin_Call(const Mixin_Call* ptr) + : Has_Block(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_) + { } + ATTACH_AST_OPERATIONS(Mixin_Call) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////// + // The @content directive for mixin content blocks. + /////////////////////////////////////////////////// + class Content : public Statement { + ADD_PROPERTY(Media_Block_Obj, media_block) + public: + Content(ParserState pstate) : Statement(pstate) + { statement_type(CONTENT); } + Content(const Content* ptr) : Statement(ptr) + { statement_type(CONTENT); } + ATTACH_AST_OPERATIONS(Content) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // Lists of values, both comma- and space-separated (distinguished by a + // type-tag.) Also used to represent variable-length argument lists. + /////////////////////////////////////////////////////////////////////// + class List : public Value, public Vectorized { + void adjust_after_pushing(Expression_Obj e) { is_expanded(false); } + private: + ADD_PROPERTY(enum Sass_Separator, separator) + ADD_PROPERTY(bool, is_arglist) + ADD_PROPERTY(bool, from_selector) + public: + List(ParserState pstate, + size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false) + : Value(pstate), + Vectorized(size), + separator_(sep), + is_arglist_(argl), + from_selector_(false) + { concrete_type(LIST); } + List(const List* ptr) + : Value(ptr), + Vectorized(*ptr), + separator_(ptr->separator_), + is_arglist_(ptr->is_arglist_), + from_selector_(ptr->from_selector_) + { concrete_type(LIST); } + std::string type() { return is_arglist_ ? "arglist" : "list"; } + static std::string type_name() { return "list"; } + const char* sep_string(bool compressed = false) const { + return separator() == SASS_SPACE ? + " " : (compressed ? "," : ", "); + } + bool is_invisible() const { return empty(); } + Expression_Obj value_at_index(size_t i); + + virtual size_t size() const; + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(sep_string()); + for (size_t i = 0, L = length(); i < L; ++i) + hash_combine(hash_, (elements()[i])->hash()); + } + return hash_; + } + + virtual void set_delayed(bool delayed) + { + is_delayed(delayed); + // don't set children + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(List) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // Key value paris. + /////////////////////////////////////////////////////////////////////// + class Map : public Value, public Hashed { + void adjust_after_pushing(std::pair p) { is_expanded(false); } + public: + Map(ParserState pstate, + size_t size = 0) + : Value(pstate), + Hashed(size) + { concrete_type(MAP); } + Map(const Map* ptr) + : Value(ptr), + Hashed(*ptr) + { concrete_type(MAP); } + std::string type() { return "map"; } + static std::string type_name() { return "map"; } + bool is_invisible() const { return empty(); } + List_Obj to_list(Context& ctx, ParserState& pstate); + + virtual size_t hash() + { + if (hash_ == 0) { + for (auto key : keys()) { + hash_combine(hash_, key->hash()); + hash_combine(hash_, at(key)->hash()); + } + } + + return hash_; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Map) + ATTACH_OPERATIONS() + }; + + inline static const std::string sass_op_to_name(enum Sass_OP op) { + switch (op) { + case AND: return "and"; break; + case OR: return "or"; break; + case EQ: return "eq"; break; + case NEQ: return "neq"; break; + case GT: return "gt"; break; + case GTE: return "gte"; break; + case LT: return "lt"; break; + case LTE: return "lte"; break; + case ADD: return "plus"; break; + case SUB: return "sub"; break; + case MUL: return "times"; break; + case DIV: return "div"; break; + case MOD: return "mod"; break; + // this is only used internally! + case NUM_OPS: return "[OPS]"; break; + default: return "invalid"; break; + } + } + + ////////////////////////////////////////////////////////////////////////// + // Binary expressions. Represents logical, relational, and arithmetic + // operations. Templatized to avoid large switch statements and repetitive + // subclassing. + ////////////////////////////////////////////////////////////////////////// + class Binary_Expression : public PreValue { + private: + ADD_HASHED(Operand, op) + ADD_HASHED(Expression_Obj, left) + ADD_HASHED(Expression_Obj, right) + size_t hash_; + public: + Binary_Expression(ParserState pstate, + Operand op, Expression_Obj lhs, Expression_Obj rhs) + : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) + { } + Binary_Expression(const Binary_Expression* ptr) + : PreValue(ptr), + op_(ptr->op_), + left_(ptr->left_), + right_(ptr->right_), + hash_(ptr->hash_) + { } + const std::string type_name() { + switch (type()) { + case AND: return "and"; break; + case OR: return "or"; break; + case EQ: return "eq"; break; + case NEQ: return "neq"; break; + case GT: return "gt"; break; + case GTE: return "gte"; break; + case LT: return "lt"; break; + case LTE: return "lte"; break; + case ADD: return "add"; break; + case SUB: return "sub"; break; + case MUL: return "mul"; break; + case DIV: return "div"; break; + case MOD: return "mod"; break; + // this is only used internally! + case NUM_OPS: return "[OPS]"; break; + default: return "invalid"; break; + } + } + const std::string separator() { + switch (type()) { + case AND: return "&&"; break; + case OR: return "||"; break; + case EQ: return "=="; break; + case NEQ: return "!="; break; + case GT: return ">"; break; + case GTE: return ">="; break; + case LT: return "<"; break; + case LTE: return "<="; break; + case ADD: return "+"; break; + case SUB: return "-"; break; + case MUL: return "*"; break; + case DIV: return "/"; break; + case MOD: return "%"; break; + // this is only used internally! + case NUM_OPS: return "[OPS]"; break; + default: return "invalid"; break; + } + } + bool is_left_interpolant(void) const; + bool is_right_interpolant(void) const; + bool has_interpolant() const + { + return is_left_interpolant() || + is_right_interpolant(); + } + virtual void set_delayed(bool delayed) + { + right()->set_delayed(delayed); + left()->set_delayed(delayed); + is_delayed(delayed); + } + virtual bool operator==(const Expression& rhs) const + { + try + { + Binary_Expression_Ptr_Const m = dynamic_cast(&rhs); + if (m == 0) return false; + return type() == m->type() && + left() == m->left() && + right() == m->right(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(type()); + hash_combine(hash_, left()->hash()); + hash_combine(hash_, right()->hash()); + } + return hash_; + } + enum Sass_OP type() const { return op_.operand; } + ATTACH_AST_OPERATIONS(Binary_Expression) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Arithmetic negation (logical negation is just an ordinary function call). + //////////////////////////////////////////////////////////////////////////// + class Unary_Expression : public Expression { + public: + enum Type { PLUS, MINUS, NOT }; + private: + ADD_HASHED(Type, type) + ADD_HASHED(Expression_Obj, operand) + size_t hash_; + public: + Unary_Expression(ParserState pstate, Type t, Expression_Obj o) + : Expression(pstate), type_(t), operand_(o), hash_(0) + { } + Unary_Expression(const Unary_Expression* ptr) + : Expression(ptr), + type_(ptr->type_), + operand_(ptr->operand_), + hash_(ptr->hash_) + { } + const std::string type_name() { + switch (type_) { + case PLUS: return "plus"; break; + case MINUS: return "minus"; break; + case NOT: return "not"; break; + default: return "invalid"; break; + } + } + virtual bool operator==(const Expression& rhs) const + { + try + { + Unary_Expression_Ptr_Const m = dynamic_cast(&rhs); + if (m == 0) return false; + return type() == m->type() && + operand() == m->operand(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(type_); + hash_combine(hash_, operand()->hash()); + }; + return hash_; + } + ATTACH_AST_OPERATIONS(Unary_Expression) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////// + // Individual argument objects for mixin and function calls. + //////////////////////////////////////////////////////////// + class Argument : public Expression { + ADD_HASHED(Expression_Obj, value) + ADD_HASHED(std::string, name) + ADD_PROPERTY(bool, is_rest_argument) + ADD_PROPERTY(bool, is_keyword_argument) + size_t hash_; + public: + Argument(ParserState pstate, Expression_Obj val, std::string n = "", bool rest = false, bool keyword = false) + : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) + { + if (!name_.empty() && is_rest_argument_) { + error("variable-length argument may not be passed by name", pstate_); + } + } + Argument(const Argument* ptr) + : Expression(ptr), + value_(ptr->value_), + name_(ptr->name_), + is_rest_argument_(ptr->is_rest_argument_), + is_keyword_argument_(ptr->is_keyword_argument_), + hash_(ptr->hash_) + { + if (!name_.empty() && is_rest_argument_) { + error("variable-length argument may not be passed by name", pstate_); + } + } + + virtual void set_delayed(bool delayed); + virtual bool operator==(const Expression& rhs) const + { + try + { + Argument_Ptr_Const m = dynamic_cast(&rhs); + if (!(m && name() == m->name())) return false; + return *value() == *m->value(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(name()); + hash_combine(hash_, value()->hash()); + } + return hash_; + } + + ATTACH_AST_OPERATIONS(Argument) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Argument lists -- in their own class to facilitate context-sensitive + // error checking (e.g., ensuring that all ordinal arguments precede all + // named arguments). + //////////////////////////////////////////////////////////////////////// + class Arguments : public Expression, public Vectorized { + ADD_PROPERTY(bool, has_named_arguments) + ADD_PROPERTY(bool, has_rest_argument) + ADD_PROPERTY(bool, has_keyword_argument) + protected: + void adjust_after_pushing(Argument_Obj a); + public: + Arguments(ParserState pstate) + : Expression(pstate), + Vectorized(), + has_named_arguments_(false), + has_rest_argument_(false), + has_keyword_argument_(false) + { } + Arguments(const Arguments* ptr) + : Expression(ptr), + Vectorized(*ptr), + has_named_arguments_(ptr->has_named_arguments_), + has_rest_argument_(ptr->has_rest_argument_), + has_keyword_argument_(ptr->has_keyword_argument_) + { } + + virtual void set_delayed(bool delayed); + + Argument_Obj get_rest_argument(); + Argument_Obj get_keyword_argument(); + + ATTACH_AST_OPERATIONS(Arguments) + ATTACH_OPERATIONS() + }; + + ////////////////// + // Function calls. + ////////////////// + class Function_Call : public PreValue { + ADD_HASHED(std::string, name) + ADD_HASHED(Arguments_Obj, arguments) + ADD_PROPERTY(bool, via_call) + ADD_PROPERTY(void*, cookie) + size_t hash_; + public: + Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie) + : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(cookie), hash_(0) + { concrete_type(FUNCTION); } + Function_Call(ParserState pstate, std::string n, Arguments_Obj args) + : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) + { concrete_type(FUNCTION); } + Function_Call(const Function_Call* ptr) + : PreValue(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_), + via_call_(ptr->via_call_), + cookie_(ptr->cookie_), + hash_(ptr->hash_) + { concrete_type(FUNCTION); } + + virtual bool operator==(const Expression& rhs) const + { + try + { + Function_Call_Ptr_Const m = dynamic_cast(&rhs); + if (!(m && name() == m->name())) return false; + if (!(m && arguments()->length() == m->arguments()->length())) return false; + for (size_t i =0, L = arguments()->length(); i < L; ++i) + if (!((*arguments())[i] == (*m->arguments())[i])) return false; + return true; + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(name()); + for (auto argument : arguments()->elements()) + hash_combine(hash_, argument->hash()); + } + return hash_; + } + ATTACH_AST_OPERATIONS(Function_Call) + ATTACH_OPERATIONS() + }; + + ///////////////////////// + // Function call schemas. + ///////////////////////// + class Function_Call_Schema : public Expression { + ADD_PROPERTY(String_Obj, name) + ADD_PROPERTY(Arguments_Obj, arguments) + public: + Function_Call_Schema(ParserState pstate, String_Obj n, Arguments_Obj args) + : Expression(pstate), name_(n), arguments_(args) + { concrete_type(STRING); } + Function_Call_Schema(const Function_Call_Schema* ptr) + : Expression(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_) + { concrete_type(STRING); } + ATTACH_AST_OPERATIONS(Function_Call_Schema) + ATTACH_OPERATIONS() + }; + + /////////////////////// + // Variable references. + /////////////////////// + class Variable : public PreValue { + ADD_PROPERTY(std::string, name) + public: + Variable(ParserState pstate, std::string n) + : PreValue(pstate), name_(n) + { } + Variable(const Variable* ptr) + : PreValue(ptr), name_(ptr->name_) + { } + + virtual bool operator==(const Expression& rhs) const + { + try + { + Variable_Ptr_Const e = dynamic_cast(&rhs); + return e && name() == e->name(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + return std::hash()(name()); + } + + ATTACH_AST_OPERATIONS(Variable) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Textual (i.e., unevaluated) numeric data. Variants are distinguished with + // a type tag. + //////////////////////////////////////////////////////////////////////////// + class Textual : public Expression { + public: + enum Type { NUMBER, PERCENTAGE, DIMENSION, HEX }; + private: + ADD_HASHED(Type, type) + ADD_HASHED(std::string, value) + size_t hash_; + public: + Textual(ParserState pstate, Type t, std::string val) + : Expression(pstate, DELAYED), type_(t), value_(val), + hash_(0) + { } + Textual(const Textual* ptr) + : Expression(ptr), + type_(ptr->type_), + value_(ptr->value_), + hash_(ptr->hash_) + { } + + virtual bool operator==(const Expression& rhs) const + { + try + { + Textual_Ptr_Const e = dynamic_cast(&rhs); + return e && value() == e->value() && type() == e->type(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + hash_combine(hash_, std::hash()(type_)); + } + return hash_; + } + + ATTACH_AST_OPERATIONS(Textual) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////// + // Numbers, percentages, dimensions, and colors. + //////////////////////////////////////////////// + class Number : public Value { + ADD_HASHED(double, value) + ADD_PROPERTY(bool, zero) + std::vector numerator_units_; + std::vector denominator_units_; + size_t hash_; + public: + Number(ParserState pstate, double val, std::string u = "", bool zero = true); + + Number(const Number* ptr) + : Value(ptr), + value_(ptr->value_), zero_(ptr->zero_), + numerator_units_(ptr->numerator_units_), + denominator_units_(ptr->denominator_units_), + hash_(ptr->hash_) + { concrete_type(NUMBER); } + + bool zero() { return zero_; } + bool is_valid_css_unit() const; + std::vector& numerator_units() { return numerator_units_; } + std::vector& denominator_units() { return denominator_units_; } + const std::vector& numerator_units() const { return numerator_units_; } + const std::vector& denominator_units() const { return denominator_units_; } + std::string type() { return "number"; } + static std::string type_name() { return "number"; } + std::string unit() const; + + bool is_unitless() const; + double convert_factor(const Number&) const; + bool convert(const std::string& unit = "", bool strict = false); + void normalize(const std::string& unit = "", bool strict = false); + // useful for making one number compatible with another + std::string find_convertible_unit() const; + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + for (const auto numerator : numerator_units()) + hash_combine(hash_, std::hash()(numerator)); + for (const auto denominator : denominator_units()) + hash_combine(hash_, std::hash()(denominator)); + } + return hash_; + } + + virtual bool operator< (const Number& rhs) const; + virtual bool operator== (const Expression& rhs) const; + virtual bool eq(const Expression& rhs) const; + ATTACH_AST_OPERATIONS(Number) + ATTACH_OPERATIONS() + }; + + ////////// + // Colors. + ////////// + class Color : public Value { + ADD_HASHED(double, r) + ADD_HASHED(double, g) + ADD_HASHED(double, b) + ADD_HASHED(double, a) + ADD_PROPERTY(std::string, disp) + size_t hash_; + public: + Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "") + : Value(pstate), r_(r), g_(g), b_(b), a_(a), disp_(disp), + hash_(0) + { concrete_type(COLOR); } + Color(const Color* ptr) + : Value(ptr), + r_(ptr->r_), + g_(ptr->g_), + b_(ptr->b_), + a_(ptr->a_), + disp_(ptr->disp_), + hash_(ptr->hash_) + { concrete_type(COLOR); } + std::string type() { return "color"; } + static std::string type_name() { return "color"; } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(a_); + hash_combine(hash_, std::hash()(r_)); + hash_combine(hash_, std::hash()(g_)); + hash_combine(hash_, std::hash()(b_)); + } + return hash_; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Color) + ATTACH_OPERATIONS() + }; + + ////////////////////////////// + // Errors from Sass_Values. + ////////////////////////////// + class Custom_Error : public Value { + ADD_PROPERTY(std::string, message) + public: + Custom_Error(ParserState pstate, std::string msg) + : Value(pstate), message_(msg) + { concrete_type(C_ERROR); } + Custom_Error(const Custom_Error* ptr) + : Value(ptr), message_(ptr->message_) + { concrete_type(C_ERROR); } + virtual bool operator== (const Expression& rhs) const; + ATTACH_AST_OPERATIONS(Custom_Error) + ATTACH_OPERATIONS() + }; + + ////////////////////////////// + // Warnings from Sass_Values. + ////////////////////////////// + class Custom_Warning : public Value { + ADD_PROPERTY(std::string, message) + public: + Custom_Warning(ParserState pstate, std::string msg) + : Value(pstate), message_(msg) + { concrete_type(C_WARNING); } + Custom_Warning(const Custom_Warning* ptr) + : Value(ptr), message_(ptr->message_) + { concrete_type(C_WARNING); } + virtual bool operator== (const Expression& rhs) const; + ATTACH_AST_OPERATIONS(Custom_Warning) + ATTACH_OPERATIONS() + }; + + //////////// + // Booleans. + //////////// + class Boolean : public Value { + ADD_HASHED(bool, value) + size_t hash_; + public: + Boolean(ParserState pstate, bool val) + : Value(pstate), value_(val), + hash_(0) + { concrete_type(BOOLEAN); } + Boolean(const Boolean* ptr) + : Value(ptr), + value_(ptr->value_), + hash_(ptr->hash_) + { concrete_type(BOOLEAN); } + virtual operator bool() { return value_; } + std::string type() { return "bool"; } + static std::string type_name() { return "bool"; } + virtual bool is_false() { return !value_; } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + } + return hash_; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Boolean) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for Sass string values. Includes interpolated and + // "flat" strings. + //////////////////////////////////////////////////////////////////////// + class String : public Value { + public: + String(ParserState pstate, bool delayed = false) + : Value(pstate, delayed) + { concrete_type(STRING); } + String(const String* ptr) + : Value(ptr) + { concrete_type(STRING); } + static std::string type_name() { return "string"; } + virtual ~String() = 0; + virtual void rtrim() = 0; + virtual void ltrim() = 0; + virtual void trim() = 0; + virtual bool operator==(const Expression& rhs) const = 0; + virtual bool operator<(const Expression& rhs) const { + return this->to_string() < rhs.to_string(); + }; + ATTACH_VIRTUAL_AST_OPERATIONS(String); + ATTACH_OPERATIONS() + }; + inline String::~String() { }; + + /////////////////////////////////////////////////////////////////////// + // Interpolated strings. Meant to be reduced to flat strings during the + // evaluation phase. + /////////////////////////////////////////////////////////////////////// + class String_Schema : public String, public Vectorized { + // ADD_PROPERTY(bool, has_interpolants) + size_t hash_; + public: + String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false) + : String(pstate), Vectorized(size), hash_(0) + { concrete_type(STRING); } + String_Schema(const String_Schema* ptr) + : String(ptr), + Vectorized(*ptr), + hash_(ptr->hash_) + { concrete_type(STRING); } + + std::string type() { return "string"; } + static std::string type_name() { return "string"; } + + bool is_left_interpolant(void) const; + bool is_right_interpolant(void) const; + // void has_interpolants(bool tc) { } + bool has_interpolants() { + for (auto el : elements()) { + if (el->is_interpolant()) return true; + } + return false; + } + virtual void rtrim(); + virtual void ltrim(); + virtual void trim(); + + virtual size_t hash() + { + if (hash_ == 0) { + for (auto string : elements()) + hash_combine(hash_, string->hash()); + } + return hash_; + } + + virtual void set_delayed(bool delayed) { + is_delayed(delayed); + } + + virtual bool operator==(const Expression& rhs) const; + ATTACH_AST_OPERATIONS(String_Schema) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////// + // Flat strings -- the lowest level of raw textual data. + //////////////////////////////////////////////////////// + class String_Constant : public String { + ADD_PROPERTY(char, quote_mark) + ADD_PROPERTY(bool, can_compress_whitespace) + ADD_HASHED(std::string, value) + protected: + size_t hash_; + public: + String_Constant(const String_Constant* ptr) + : String(ptr), + quote_mark_(ptr->quote_mark_), + can_compress_whitespace_(ptr->can_compress_whitespace_), + value_(ptr->value_), + hash_(ptr->hash_) + { } + String_Constant(ParserState pstate, std::string val) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val)), hash_(0) + { } + String_Constant(ParserState pstate, const char* beg) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg))), hash_(0) + { } + String_Constant(ParserState pstate, const char* beg, const char* end) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg))), hash_(0) + { } + String_Constant(ParserState pstate, const Token& tok) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end))), hash_(0) + { } + std::string type() { return "string"; } + static std::string type_name() { return "string"; } + virtual bool is_invisible() const; + virtual void rtrim(); + virtual void ltrim(); + virtual void trim(); + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + } + return hash_; + } + + virtual bool operator==(const Expression& rhs) const; + virtual std::string inspect() const; // quotes are forced on inspection + + // static char auto_quote() { return '*'; } + static char double_quote() { return '"'; } + static char single_quote() { return '\''; } + + ATTACH_AST_OPERATIONS(String_Constant) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////// + // Possibly quoted string (unquote on instantiation) + //////////////////////////////////////////////////////// + class String_Quoted : public String_Constant { + public: + String_Quoted(ParserState pstate, std::string val, char q = 0, + bool keep_utf8_escapes = false, bool skip_unquoting = false, + bool strict_unquoting = true) + : String_Constant(pstate, val) + { + if (skip_unquoting == false) { + value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); + } + if (q && quote_mark_) quote_mark_ = q; + } + String_Quoted(const String_Quoted* ptr) + : String_Constant(ptr) + { } + virtual bool operator==(const Expression& rhs) const; + virtual std::string inspect() const; // quotes are forced on inspection + ATTACH_AST_OPERATIONS(String_Quoted) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Media queries. + ///////////////// + class Media_Query : public Expression, + public Vectorized { + ADD_PROPERTY(String_Obj, media_type) + ADD_PROPERTY(bool, is_negated) + ADD_PROPERTY(bool, is_restricted) + public: + Media_Query(ParserState pstate, + String_Obj t = 0, size_t s = 0, bool n = false, bool r = false) + : Expression(pstate), Vectorized(s), + media_type_(t), is_negated_(n), is_restricted_(r) + { } + Media_Query(const Media_Query* ptr) + : Expression(ptr), + Vectorized(*ptr), + media_type_(ptr->media_type_), + is_negated_(ptr->is_negated_), + is_restricted_(ptr->is_restricted_) + { } + ATTACH_AST_OPERATIONS(Media_Query) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////// + // Media expressions (for use inside media queries). + //////////////////////////////////////////////////// + class Media_Query_Expression : public Expression { + ADD_PROPERTY(Expression_Obj, feature) + ADD_PROPERTY(Expression_Obj, value) + ADD_PROPERTY(bool, is_interpolated) + public: + Media_Query_Expression(ParserState pstate, + Expression_Obj f, Expression_Obj v, bool i = false) + : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) + { } + Media_Query_Expression(const Media_Query_Expression* ptr) + : Expression(ptr), + feature_(ptr->feature_), + value_(ptr->value_), + is_interpolated_(ptr->is_interpolated_) + { } + ATTACH_AST_OPERATIONS(Media_Query_Expression) + ATTACH_OPERATIONS() + }; + + //////////////////// + // `@supports` rule. + //////////////////// + class Supports_Block : public Has_Block { + ADD_PROPERTY(Supports_Condition_Obj, condition) + public: + Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block = 0) + : Has_Block(pstate, block), condition_(condition) + { statement_type(SUPPORTS); } + Supports_Block(const Supports_Block* ptr) + : Has_Block(ptr), condition_(ptr->condition_) + { statement_type(SUPPORTS); } + bool bubbles() { return true; } + ATTACH_AST_OPERATIONS(Supports_Block) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////////////////////// + // The abstract superclass of all Supports conditions. + ////////////////////////////////////////////////////// + class Supports_Condition : public Expression { + public: + Supports_Condition(ParserState pstate) + : Expression(pstate) + { } + Supports_Condition(const Supports_Condition* ptr) + : Expression(ptr) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Condition) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////// + // An operator condition (e.g. `CONDITION1 and CONDITION2`). + //////////////////////////////////////////////////////////// + class Supports_Operator : public Supports_Condition { + public: + enum Operand { AND, OR }; + private: + ADD_PROPERTY(Supports_Condition_Obj, left); + ADD_PROPERTY(Supports_Condition_Obj, right); + ADD_PROPERTY(Operand, operand); + public: + Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o) + : Supports_Condition(pstate), left_(l), right_(r), operand_(o) + { } + Supports_Operator(const Supports_Operator* ptr) + : Supports_Condition(ptr), + left_(ptr->left_), + right_(ptr->right_), + operand_(ptr->operand_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const; + ATTACH_AST_OPERATIONS(Supports_Operator) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////////// + // A negation condition (`not CONDITION`). + ////////////////////////////////////////// + class Supports_Negation : public Supports_Condition { + private: + ADD_PROPERTY(Supports_Condition_Obj, condition); + public: + Supports_Negation(ParserState pstate, Supports_Condition_Obj c) + : Supports_Condition(pstate), condition_(c) + { } + Supports_Negation(const Supports_Negation* ptr) + : Supports_Condition(ptr), condition_(ptr->condition_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const; + ATTACH_AST_OPERATIONS(Supports_Negation) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////// + // A declaration condition (e.g. `(feature: value)`). + ///////////////////////////////////////////////////// + class Supports_Declaration : public Supports_Condition { + private: + ADD_PROPERTY(Expression_Obj, feature); + ADD_PROPERTY(Expression_Obj, value); + public: + Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v) + : Supports_Condition(pstate), feature_(f), value_(v) + { } + Supports_Declaration(const Supports_Declaration* ptr) + : Supports_Condition(ptr), + feature_(ptr->feature_), + value_(ptr->value_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Declaration) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////// + // An interpolation condition (e.g. `#{$var}`). + /////////////////////////////////////////////// + class Supports_Interpolation : public Supports_Condition { + private: + ADD_PROPERTY(Expression_Obj, value); + public: + Supports_Interpolation(ParserState pstate, Expression_Obj v) + : Supports_Condition(pstate), value_(v) + { } + Supports_Interpolation(const Supports_Interpolation* ptr) + : Supports_Condition(ptr), + value_(ptr->value_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Interpolation) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////// + // At root expressions (for use inside @at-root). + ///////////////////////////////////////////////// + class At_Root_Query : public Expression { + private: + ADD_PROPERTY(Expression_Obj, feature) + ADD_PROPERTY(Expression_Obj, value) + public: + At_Root_Query(ParserState pstate, Expression_Obj f = 0, Expression_Obj v = 0, bool i = false) + : Expression(pstate), feature_(f), value_(v) + { } + At_Root_Query(const At_Root_Query* ptr) + : Expression(ptr), + feature_(ptr->feature_), + value_(ptr->value_) + { } + bool exclude(std::string str); + ATTACH_AST_OPERATIONS(At_Root_Query) + ATTACH_OPERATIONS() + }; + + /////////// + // At-root. + /////////// + class At_Root_Block : public Has_Block { + ADD_PROPERTY(At_Root_Query_Obj, expression) + public: + At_Root_Block(ParserState pstate, Block_Obj b = 0, At_Root_Query_Obj e = 0) + : Has_Block(pstate, b), expression_(e) + { statement_type(ATROOT); } + At_Root_Block(const At_Root_Block* ptr) + : Has_Block(ptr), expression_(ptr->expression_) + { statement_type(ATROOT); } + bool bubbles() { return true; } + bool exclude_node(Statement_Obj s) { + if (expression() == 0) + { + return s->statement_type() == Statement::RULESET; + } + + if (s->statement_type() == Statement::DIRECTIVE) + { + if (Directive_Obj dir = SASS_MEMORY_CAST(Directive, s)) + { + std::string keyword(dir->keyword()); + if (keyword.length() > 0) keyword.erase(0, 1); + return expression()->exclude(keyword); + } + } + if (s->statement_type() == Statement::MEDIA) + { + return expression()->exclude("media"); + } + if (s->statement_type() == Statement::RULESET) + { + return expression()->exclude("rule"); + } + if (s->statement_type() == Statement::SUPPORTS) + { + return expression()->exclude("supports"); + } + if (Directive_Obj dir = SASS_MEMORY_CAST(Directive, s)) + { + if (dir->is_keyframes()) return expression()->exclude("keyframes"); + } + return false; + } + ATTACH_AST_OPERATIONS(At_Root_Block) + ATTACH_OPERATIONS() + }; + + ////////////////// + // The null value. + ////////////////// + class Null : public Value { + public: + Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); } + Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); } + std::string type() { return "null"; } + static std::string type_name() { return "null"; } + bool is_invisible() const { return true; } + operator bool() { return false; } + bool is_false() { return true; } + + virtual size_t hash() + { + return -1; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Null) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////// + // Thunks for delayed evaluation. + ///////////////////////////////// + class Thunk : public Expression { + ADD_PROPERTY(Expression_Obj, expression) + ADD_PROPERTY(Env*, environment) + public: + Thunk(ParserState pstate, Expression_Obj exp, Env* env = 0) + : Expression(pstate), expression_(exp), environment_(env) + { } + }; + + ///////////////////////////////////////////////////////// + // Individual parameter objects for mixins and functions. + ///////////////////////////////////////////////////////// + class Parameter : public AST_Node { + ADD_PROPERTY(std::string, name) + ADD_PROPERTY(Expression_Obj, default_value) + ADD_PROPERTY(bool, is_rest_parameter) + public: + Parameter(ParserState pstate, + std::string n, Expression_Obj def = 0, bool rest = false) + : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) + { + if (default_value_ && is_rest_parameter_) { + error("variable-length parameter may not have a default value", pstate_); + } + } + Parameter(const Parameter* ptr) + : AST_Node(ptr), + name_(ptr->name_), + default_value_(ptr->default_value_), + is_rest_parameter_(ptr->is_rest_parameter_) + { + if (default_value_ && is_rest_parameter_) { + error("variable-length parameter may not have a default value", pstate_); + } + } + ATTACH_AST_OPERATIONS(Parameter) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////////// + // Parameter lists -- in their own class to facilitate context-sensitive + // error checking (e.g., ensuring that all optional parameters follow all + // required parameters). + ///////////////////////////////////////////////////////////////////////// + class Parameters : public AST_Node, public Vectorized { + ADD_PROPERTY(bool, has_optional_parameters) + ADD_PROPERTY(bool, has_rest_parameter) + protected: + void adjust_after_pushing(Parameter_Obj p) + { + if (p->default_value()) { + if (has_rest_parameter_) { + error("optional parameters may not be combined with variable-length parameters", p->pstate()); + } + has_optional_parameters_ = true; + } + else if (p->is_rest_parameter()) { + if (has_rest_parameter_) { + error("functions and mixins cannot have more than one variable-length parameter", p->pstate()); + } + has_rest_parameter_ = true; + } + else { + if (has_rest_parameter_) { + error("required parameters must precede variable-length parameters", p->pstate()); + } + if (has_optional_parameters_) { + error("required parameters must precede optional parameters", p->pstate()); + } + } + } + public: + Parameters(ParserState pstate) + : AST_Node(pstate), + Vectorized(), + has_optional_parameters_(false), + has_rest_parameter_(false) + { } + Parameters(const Parameters* ptr) + : AST_Node(ptr), + Vectorized(*ptr), + has_optional_parameters_(ptr->has_optional_parameters_), + has_rest_parameter_(ptr->has_rest_parameter_) + { } + ATTACH_AST_OPERATIONS(Parameters) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////// + // Abstract base class for CSS selectors. + ///////////////////////////////////////// + class Selector : public Expression { + // ADD_PROPERTY(bool, has_reference) + // line break before list separator + ADD_PROPERTY(bool, has_line_feed) + // line break after list separator + ADD_PROPERTY(bool, has_line_break) + // maybe we have optional flag + ADD_PROPERTY(bool, is_optional) + // parent block pointers + + // must not be a reference counted object + // otherwise we create circular references + ADD_PROPERTY(Media_Block_Ptr, media_block) + protected: + size_t hash_; + public: + Selector(ParserState pstate, bool r = false, bool h = false) + : Expression(pstate), + // has_reference_(r), + has_line_feed_(false), + has_line_break_(false), + is_optional_(false), + media_block_(0), + hash_(0) + { concrete_type(SELECTOR); } + Selector(const Selector* ptr) + : Expression(ptr), + // has_reference_(ptr->has_reference_), + has_line_feed_(ptr->has_line_feed_), + has_line_break_(ptr->has_line_break_), + is_optional_(ptr->is_optional_), + media_block_(ptr->media_block_), + hash_(ptr->hash_) + { concrete_type(SELECTOR); } + virtual ~Selector() = 0; + virtual size_t hash() = 0; + virtual unsigned long specificity() { + return 0; + } + virtual void set_media_block(Media_Block_Ptr mb) { + media_block(mb); + } + virtual bool has_parent_ref() { + return false; + } + virtual bool has_real_parent_ref() { + return false; + } + // dispatch to correct handlers + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + ATTACH_VIRTUAL_AST_OPERATIONS(Selector); + }; + inline Selector::~Selector() { } + + ///////////////////////////////////////////////////////////////////////// + // Interpolated selectors -- the interpolated String will be expanded and + // re-parsed into a normal selector class. + ///////////////////////////////////////////////////////////////////////// + class Selector_Schema : public Selector { + ADD_PROPERTY(String_Obj, contents) + ADD_PROPERTY(bool, at_root); + public: + Selector_Schema(ParserState pstate, String_Obj c) + : Selector(pstate), contents_(c), at_root_(false) + { } + Selector_Schema(const Selector_Schema* ptr) + : Selector(ptr), + contents_(ptr->contents_), + at_root_(ptr->at_root_) + { } + virtual bool has_parent_ref(); + virtual bool has_real_parent_ref(); + virtual size_t hash() { + if (hash_ == 0) { + hash_combine(hash_, contents_->hash()); + } + return hash_; + } + ATTACH_AST_OPERATIONS(Selector_Schema) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////// + // Abstract base class for simple selectors. + //////////////////////////////////////////// + class Simple_Selector : public Selector { + ADD_PROPERTY(std::string, ns) + ADD_PROPERTY(std::string, name) + ADD_PROPERTY(Simple_Type, simple_type) + ADD_PROPERTY(bool, has_ns) + public: + Simple_Selector(ParserState pstate, std::string n = "") + : Selector(pstate), ns_(""), name_(n), has_ns_(false) + { + simple_type(SIMPLE); + size_t pos = n.find('|'); + // found some namespace + if (pos != std::string::npos) { + has_ns_ = true; + ns_ = n.substr(0, pos); + name_ = n.substr(pos + 1); + } + } + Simple_Selector(const Simple_Selector* ptr) + : Selector(ptr), + ns_(ptr->ns_), + name_(ptr->name_), + has_ns_(ptr->has_ns_) + { simple_type(SIMPLE); } + virtual bool unique() const + { + return false; + } + virtual std::string ns_name() const + { + std::string name(""); + if (has_ns_) + name += ns_ + "|"; + return name + name_; + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, std::hash()(SELECTOR)); + hash_combine(hash_, std::hash()(ns())); + hash_combine(hash_, std::hash()(name())); + } + return hash_; + } + // namespace query functions + bool is_universal_ns() const + { + return has_ns_ && ns_ == "*"; + } + bool has_universal_ns() const + { + return !has_ns_ || ns_ == "*"; + } + bool is_empty_ns() const + { + return !has_ns_ || ns_ == ""; + } + bool has_empty_ns() const + { + return has_ns_ && ns_ == ""; + } + bool has_qualified_ns() const + { + return has_ns_ && ns_ != "" && ns_ != "*"; + } + // name query functions + bool is_universal() const + { + return name_ == "*"; + } + + virtual bool has_placeholder() { + return false; + } + + virtual ~Simple_Selector() = 0; + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + virtual bool has_parent_ref() { return false; }; + virtual bool has_real_parent_ref() { return false; }; + virtual bool is_pseudo_element() { return false; } + virtual bool is_pseudo_class() { return false; } + + virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; } + + virtual bool operator==(const Selector& rhs) const; + virtual bool operator==(const Simple_Selector& rhs) const; + inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); } + + bool operator<(const Selector& rhs) const; + bool operator<(const Simple_Selector& rhs) const; + // default implementation should work for most of the simple selectors (otherwise overload) + ATTACH_VIRTUAL_AST_OPERATIONS(Simple_Selector); + ATTACH_OPERATIONS(); + }; + inline Simple_Selector::~Simple_Selector() { } + + + ////////////////////////////////// + // The Parent Selector Expression. + ////////////////////////////////// + // parent selectors can occur in selectors but also + // inside strings in declarations (Compound_Selector). + // only one simple parent selector means the first case. + class Parent_Selector : public Simple_Selector { + ADD_PROPERTY(bool, real) + public: + Parent_Selector(ParserState pstate, bool r = true) + : Simple_Selector(pstate, "&"), real_(r) + { /* has_reference(true); */ } + Parent_Selector(const Parent_Selector* ptr) + : Simple_Selector(ptr), real_(ptr->real_) + { /* has_reference(true); */ } + bool is_real_parent_ref() { return real(); }; + virtual bool has_parent_ref() { return true; }; + virtual bool has_real_parent_ref() { return is_real_parent_ref(); }; + virtual unsigned long specificity() + { + return 0; + } + std::string type() { return "selector"; } + static std::string type_name() { return "selector"; } + ATTACH_AST_OPERATIONS(Parent_Selector) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////////// + // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. + ///////////////////////////////////////////////////////////////////////// + class Placeholder_Selector : public Simple_Selector { + public: + Placeholder_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Placeholder_Selector(const Placeholder_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual unsigned long specificity() + { + return Constants::Specificity_Base; + } + virtual bool has_placeholder() { + return true; + } + virtual ~Placeholder_Selector() {}; + ATTACH_AST_OPERATIONS(Placeholder_Selector) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////// + // Element selectors (and the universal selector) -- e.g., div, span, *. + ///////////////////////////////////////////////////////////////////// + class Element_Selector : public Simple_Selector { + public: + Element_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Element_Selector(const Element_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual unsigned long specificity() + { + if (name() == "*") return 0; + else return Constants::Specificity_Element; + } + virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr, Context&); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + ATTACH_AST_OPERATIONS(Element_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////// + // Class selectors -- i.e., .foo. + //////////////////////////////////////////////// + class Class_Selector : public Simple_Selector { + public: + Class_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Class_Selector(const Class_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual bool unique() const + { + return false; + } + virtual unsigned long specificity() + { + return Constants::Specificity_Class; + } + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + ATTACH_AST_OPERATIONS(Class_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////// + // ID selectors -- i.e., #foo. + //////////////////////////////////////////////// + class Id_Selector : public Simple_Selector { + public: + Id_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Id_Selector(const Id_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual bool unique() const + { + return true; + } + virtual unsigned long specificity() + { + return Constants::Specificity_ID; + } + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + ATTACH_AST_OPERATIONS(Id_Selector) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////// + // Attribute selectors -- e.g., [src*=".jpg"], etc. + /////////////////////////////////////////////////// + class Attribute_Selector : public Simple_Selector { + ADD_PROPERTY(std::string, matcher) + // this cannot be changed to obj atm!!!!!!????!!!!!!! + ADD_PROPERTY(String_Obj, value) // might be interpolated + public: + Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v) + : Simple_Selector(pstate, n), matcher_(m), value_(v) + { simple_type(ATTR_SEL); } + Attribute_Selector(const Attribute_Selector* ptr) + : Simple_Selector(ptr), + matcher_(ptr->matcher_), + value_(ptr->value_) + { simple_type(ATTR_SEL); } + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + hash_combine(hash_, std::hash()(matcher())); + if (value_) hash_combine(hash_, value_->hash()); + } + return hash_; + } + virtual unsigned long specificity() + { + return Constants::Specificity_Attr; + } + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Attribute_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Attribute_Selector& rhs) const; + ATTACH_AST_OPERATIONS(Attribute_Selector) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////////////////////////////////// + // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. + ////////////////////////////////////////////////////////////////// + /* '::' starts a pseudo-element, ':' a pseudo-class */ + /* Except :first-line, :first-letter, :before and :after */ + /* Note that pseudo-elements are restricted to one per selector */ + /* and occur only in the last simple_selector_sequence. */ + inline bool is_pseudo_class_element(const std::string& name) + { + return name == ":before" || + name == ":after" || + name == ":first-line" || + name == ":first-letter"; + } + + // Pseudo Selector cannot have any namespace? + class Pseudo_Selector : public Simple_Selector { + ADD_PROPERTY(String_Obj, expression) + public: + Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr = 0) + : Simple_Selector(pstate, n), expression_(expr) + { simple_type(PSEUDO_SEL); } + Pseudo_Selector(const Pseudo_Selector* ptr) + : Simple_Selector(ptr), expression_(ptr->expression_) + { simple_type(PSEUDO_SEL); } + + // A pseudo-class always consists of a "colon" (:) followed by the name + // of the pseudo-class and optionally by a value between parentheses. + virtual bool is_pseudo_class() + { + return (name_[0] == ':' && name_[1] != ':') + && ! is_pseudo_class_element(name_); + } + + // A pseudo-element is made of two colons (::) followed by the name. + // The `::` notation is introduced by the current document in order to + // establish a discrimination between pseudo-classes and pseudo-elements. + // For compatibility with existing style sheets, user agents must also + // accept the previous one-colon notation for pseudo-elements introduced + // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and + // :after). This compatibility is not allowed for the new pseudo-elements + // introduced in this specification. + virtual bool is_pseudo_element() + { + return (name_[0] == ':' && name_[1] == ':') + || is_pseudo_class_element(name_); + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + if (expression_) hash_combine(hash_, expression_->hash()); + } + return hash_; + } + virtual unsigned long specificity() + { + if (is_pseudo_element()) + return Constants::Specificity_Element; + return Constants::Specificity_Pseudo; + } + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Pseudo_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Pseudo_Selector& rhs) const; + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + ATTACH_AST_OPERATIONS(Pseudo_Selector) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////// + // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir) + ///////////////////////////////////////////////// + class Wrapped_Selector : public Simple_Selector { + ADD_PROPERTY(Selector_Obj, selector) + public: + Wrapped_Selector(ParserState pstate, std::string n, Selector_Obj sel) + : Simple_Selector(pstate, n), selector_(sel) + { simple_type(WRAPPED_SEL); } + Wrapped_Selector(const Wrapped_Selector* ptr) + : Simple_Selector(ptr), selector_(ptr->selector_) + { simple_type(WRAPPED_SEL); } + virtual bool is_superselector_of(Wrapped_Selector_Obj sub); + // Selectors inside the negation pseudo-class are counted like any + // other, but the negation itself does not count as a pseudo-class. + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + if (selector_) hash_combine(hash_, selector_->hash()); + } + return hash_; + } + virtual bool has_parent_ref() { + // if (has_reference()) return true; + if (!selector()) return false; + return selector()->has_parent_ref(); + } + virtual bool has_real_parent_ref() { + // if (has_reference()) return true; + if (!selector()) return false; + return selector()->has_real_parent_ref(); + } + virtual unsigned long specificity() + { + return selector_ ? selector_->specificity() : 0; + } + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Wrapped_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Wrapped_Selector& rhs) const; + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Wrapped_Selector) + ATTACH_OPERATIONS() + }; + + struct Complex_Selector_Pointer_Compare { + bool operator() (const Complex_Selector_Obj& pLeft, const Complex_Selector_Obj& pRight) const; + }; + + //////////////////////////////////////////////////////////////////////////// + // Simple selector sequences. Maintains flags indicating whether it contains + // any parent references or placeholders, to simplify expansion. + //////////////////////////////////////////////////////////////////////////// + typedef std::set SourcesSet; + class Compound_Selector : public Selector, public Vectorized { + private: + SourcesSet sources_; + ADD_PROPERTY(bool, extended); + ADD_PROPERTY(bool, has_parent_reference); + protected: + void adjust_after_pushing(Simple_Selector_Obj s) + { + // if (s->has_reference()) has_reference(true); + // if (s->has_placeholder()) has_placeholder(true); + } + public: + Compound_Selector(ParserState pstate, size_t s = 0) + : Selector(pstate), + Vectorized(s), + extended_(false), + has_parent_reference_(false) + { } + Compound_Selector(const Compound_Selector* ptr) + : Selector(ptr), + Vectorized(*ptr), + extended_(ptr->extended_), + has_parent_reference_(ptr->has_parent_reference_) + { } + bool contains_placeholder() { + for (size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->has_placeholder()) return true; + } + return false; + }; + + void append(Simple_Selector_Ptr element); + + bool is_universal() const + { + return length() == 1 && (*this)[0]->is_universal(); + } + + Complex_Selector_Obj to_complex(); + Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs, Context& ctx); + // virtual Placeholder_Selector_Ptr find_placeholder(); + virtual bool has_parent_ref(); + virtual bool has_real_parent_ref(); + Simple_Selector_Ptr base() + { + // Implement non-const in terms of const. Safe to const_cast since this method is non-const + return const_cast(static_cast(this)->base()); + } + Simple_Selector_Ptr_Const base() const { + if (length() == 0) return 0; + // ToDo: why is this needed? + if (SASS_MEMORY_CAST(Element_Selector, (*this)[0])) + return &(*this)[0]; + return 0; + } + virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = ""); + virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapped = ""); + virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapped = ""); + virtual size_t hash() + { + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, std::hash()(SELECTOR)); + if (length()) hash_combine(Selector::hash_, Vectorized::hash()); + } + return Selector::hash_; + } + virtual unsigned long specificity() + { + int sum = 0; + for (size_t i = 0, L = length(); i < L; ++i) + { sum += (*this)[i]->specificity(); } + return sum; + } + + virtual bool has_placeholder() + { + if (length() == 0) return false; + if (Simple_Selector_Obj ss = elements().front()) { + if (ss->has_placeholder()) return true; + } + return false; + } + + bool is_empty_reference() + { + return length() == 1 && + SASS_MEMORY_CAST(Parent_Selector, (*this)[0]); + } + std::vector to_str_vec(); // sometimes need to convert to a flat "by-value" data structure + + virtual bool operator<(const Compound_Selector& rhs) const; + virtual bool operator==(const Compound_Selector& rhs) const; + inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); } + + SourcesSet& sources() { return sources_; } + void clearSources() { sources_.clear(); } + void mergeSources(SourcesSet& sources, Context& ctx); + + Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs, Context& ctx); + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Compound_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // General selectors -- i.e., simple sequences combined with one of the four + // CSS selector combinators (">", "+", "~", and whitespace). Essentially a + // linked list. + //////////////////////////////////////////////////////////////////////////// + class Complex_Selector : public Selector { + public: + enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE }; + private: + ADD_PROPERTY(Combinator, combinator) + ADD_PROPERTY(Compound_Selector_Obj, head) + ADD_PROPERTY(Complex_Selector_Obj, tail) + ADD_PROPERTY(String_Obj, reference); + public: + bool contains_placeholder() { + if (head() && head()->contains_placeholder()) return true; + if (tail() && tail()->contains_placeholder()) return true; + return false; + }; + Complex_Selector(ParserState pstate, + Combinator c = ANCESTOR_OF, + Compound_Selector_Obj h = 0, + Complex_Selector_Obj t = 0, + String_Obj r = 0) + : Selector(pstate), + combinator_(c), + head_(h), tail_(t), + reference_(r) + {} + Complex_Selector(const Complex_Selector* ptr) + : Selector(ptr), + combinator_(ptr->combinator_), + head_(ptr->head_), tail_(ptr->tail_), + reference_(ptr->reference_) + {}; + virtual bool has_parent_ref(); + virtual bool has_real_parent_ref(); + + Complex_Selector_Obj skip_empty_reference() + { + if ((!head_ || !head_->length() || head_->is_empty_reference()) && + combinator() == Combinator::ANCESTOR_OF) + { + if (!tail_) return 0; + tail_->has_line_feed_ = this->has_line_feed_; + // tail_->has_line_break_ = this->has_line_break_; + return tail_->skip_empty_reference(); + } + return this; + } + + // can still have a tail + bool is_empty_ancestor() const + { + return (!head() || head()->length() == 0) && + combinator() == Combinator::ANCESTOR_OF; + } + + Complex_Selector_Obj context(Context&); + + + Selector_List_Ptr tails(Context& ctx, Selector_List_Ptr tails); + + // front returns the first real tail + // skips over parent and empty ones + Complex_Selector_Obj first(); + // last returns the last real tail + Complex_Selector_Obj last(); + + // some shortcuts that should be removed + Complex_Selector_Obj innermost() { return last(); }; + + size_t length() const; + Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent = true); + virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); + Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs, Context& ctx); + Combinator clear_innermost(); + void append(Context&, Complex_Selector_Obj); + void set_innermost(Complex_Selector_Obj, Combinator); + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, std::hash()(SELECTOR)); + hash_combine(hash_, std::hash()(combinator_)); + if (head_) hash_combine(hash_, head_->hash()); + if (tail_) hash_combine(hash_, tail_->hash()); + } + return hash_; + } + virtual unsigned long specificity() const + { + int sum = 0; + if (head()) sum += head()->specificity(); + if (tail()) sum += tail()->specificity(); + return sum; + } + virtual void set_media_block(Media_Block_Ptr mb) { + media_block(mb); + if (tail_) tail_->set_media_block(mb); + if (head_) head_->set_media_block(mb); + } + virtual bool has_placeholder() { + if (head_ && head_->has_placeholder()) return true; + if (tail_ && tail_->has_placeholder()) return true; + return false; + } + virtual bool operator<(const Complex_Selector& rhs) const; + virtual bool operator==(const Complex_Selector& rhs) const; + inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); } + SourcesSet sources() + { + //s = Set.new + //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} + //s + + SourcesSet srcs; + + Compound_Selector_Obj pHead = head(); + Complex_Selector_Obj pTail = tail(); + + if (pHead) { + SourcesSet& headSources = pHead->sources(); + srcs.insert(headSources.begin(), headSources.end()); + } + + if (pTail) { + SourcesSet tailSources = pTail->sources(); + srcs.insert(tailSources.begin(), tailSources.end()); + } + + return srcs; + } + void addSources(SourcesSet& sources, Context& ctx) { + // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} + Complex_Selector_Ptr pIter = this; + while (pIter) { + Compound_Selector_Ptr pHead = &pIter->head(); + + if (pHead) { + pHead->mergeSources(sources, ctx); + } + + pIter = &pIter->tail(); + } + } + void clearSources() { + Complex_Selector_Ptr pIter = this; + while (pIter) { + Compound_Selector_Ptr pHead = &pIter->head(); + + if (pHead) { + pHead->clearSources(); + } + + pIter = &pIter->tail(); + } + } + + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Complex_Selector) + ATTACH_OPERATIONS() + }; + + typedef std::deque ComplexSelectorDeque; + + /////////////////////////////////// + // Comma-separated selector groups. + /////////////////////////////////// + class Selector_List : public Selector, public Vectorized { + ADD_PROPERTY(std::vector, wspace) + protected: + void adjust_after_pushing(Complex_Selector_Obj c); + public: + Selector_List(ParserState pstate, size_t s = 0) + : Selector(pstate), Vectorized(s), wspace_(0) + { } + Selector_List(const Selector_List* ptr) + : Selector(ptr), + Vectorized(*ptr), + wspace_(ptr->wspace_) + { } + std::string type() { return "list"; } + // remove parent selector references + // basically unwraps parsed selectors + virtual bool has_parent_ref(); + virtual bool has_real_parent_ref(); + void remove_parent_selectors(); + Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent = true); + virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); + Selector_List_Ptr unify_with(Selector_List_Ptr, Context&); + void populate_extends(Selector_List_Obj, Context&, Subset_Map&); + virtual size_t hash() + { + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, std::hash()(SELECTOR)); + hash_combine(Selector::hash_, Vectorized::hash()); + } + return Selector::hash_; + } + virtual unsigned long specificity() + { + unsigned long sum = 0; + unsigned long specificity = 0; + for (size_t i = 0, L = length(); i < L; ++i) + { + specificity = (*this)[i]->specificity(); + if (sum < specificity) sum = specificity; + } + return sum; + } + virtual void set_media_block(Media_Block_Ptr mb) { + media_block(mb); + for (Complex_Selector_Obj cs : elements()) { + cs->set_media_block(mb); + } + } + virtual bool has_placeholder() { + for (Complex_Selector_Obj cs : elements()) { + if (cs->has_placeholder()) return true; + } + return false; + } + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + virtual bool operator<(const Selector_List& rhs) const; + virtual bool operator==(const Selector_List& rhs) const; + // Selector Lists can be compared to comma lists + virtual bool operator==(const Expression& rhs) const; + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Selector_List) + ATTACH_OPERATIONS() + }; + + template + bool selectors_equal(const SelectorType& one, const SelectorType& two, bool simpleSelectorOrderDependent) { + // Test for equality among selectors while differentiating between checks that demand the underlying Simple_Selector + // ordering to be the same or not. This works because operator< (which doesn't make a whole lot of sense for selectors, but + // is required for proper stl collection ordering) is implemented using string comparision. This gives stable sorting + // behavior, and can be used to determine if the selectors would have exactly idential output. operator== matches the + // ruby sass implementations for eql, which sometimes perform order independent comparisions (like set comparisons of the + // members of a SimpleSequence (Compound_Selector)). + // + // Due to the reliance on operator== and operater< behavior, this templated method is currently only intended for + // use with Compound_Selector and Complex_Selector objects. + if (simpleSelectorOrderDependent) { + return !(one < two) && !(two < one); + } else { + return one == two; + } + } + + // compare function for sorting and probably other other uses + struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } }; + struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } }; + struct cmp_simple_selector { inline bool operator() (const Simple_Selector_Obj l, const Simple_Selector_Obj r) { return (*l < *r); } }; + +} + +#ifdef __clang__ + +#pragma clang diagnostic pop + +#endif + +#endif diff --git a/src/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp new file mode 100755 index 000000000..f8124629a --- /dev/null +++ b/src/libsass/src/ast_def_macros.hpp @@ -0,0 +1,55 @@ +#ifndef SASS_AST_DEF_MACROS_H +#define SASS_AST_DEF_MACROS_H + +// Helper class to switch a flag and revert once we go out of scope +template +class LocalOption { + private: + T* var; // pointer to original variable + T orig; // copy of the original option + public: + LocalOption(T& var) + { + this->var = &var; + this->orig = var; + } + LocalOption(T& var, T orig) + { + this->var = &var; + this->orig = var; + *(this->var) = orig; + } + ~LocalOption() { + *(this->var) = this->orig; + } +}; + +#define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) + +#define ATTACH_OPERATIONS()\ +virtual void perform(Operation* op) { (*op)(this); }\ +virtual AST_Node_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual Statement_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual Expression_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual Selector_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual std::string perform(Operation* op) { return (*op)(this); }\ +virtual union Sass_Value* perform(Operation* op) { return (*op)(this); }\ +virtual Value_Ptr perform(Operation* op) { return (*op)(this); } + +#define ADD_PROPERTY(type, name)\ +protected:\ + type name##_;\ +public:\ + type name() const { return name##_; }\ + type name(type name##__) { return name##_ = name##__; }\ +private: + +#define ADD_HASHED(type, name)\ +protected:\ + type name##_;\ +public:\ + type name() const { return name##_; }\ + type name(type name##__) { hash_ = 0; return name##_ = name##__; }\ +private: + +#endif diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp new file mode 100755 index 000000000..374ca9198 --- /dev/null +++ b/src/libsass/src/ast_fwd_decl.hpp @@ -0,0 +1,390 @@ +#ifndef SASS_AST_FWD_DECL_H +#define SASS_AST_FWD_DECL_H + +#include +#include +#include +#include +#include +#include "memory/SharedPtr.hpp" + +///////////////////////////////////////////// +// Forward declarations for the AST visitors. +///////////////////////////////////////////// +namespace Sass { + + class AST_Node; + typedef AST_Node* AST_Node_Ptr; + typedef AST_Node const* AST_Node_Ptr_Const; + + class Has_Block; + typedef Has_Block* Has_Block_Ptr; + typedef Has_Block const* Has_Block_Ptr_Const; + + class Simple_Selector; + typedef Simple_Selector* Simple_Selector_Ptr; + typedef Simple_Selector const* Simple_Selector_Ptr_Const; + + class PreValue; + typedef PreValue* PreValue_Ptr; + typedef PreValue const* PreValue_Ptr_Const; + class Thunk; + typedef Thunk* Thunk_Ptr; + typedef Thunk const* Thunk_Ptr_Const; + class Block; + typedef Block* Block_Ptr; + typedef Block const* Block_Ptr_Const; + class Expression; + typedef Expression* Expression_Ptr; + typedef Expression const* Expression_Ptr_Const; + class Statement; + typedef Statement* Statement_Ptr; + typedef Statement const* Statement_Ptr_Const; + class Value; + typedef Value* Value_Ptr; + typedef Value const* Value_Ptr_Const; + class Declaration; + typedef Declaration* Declaration_Ptr; + typedef Declaration const* Declaration_Ptr_Const; + class Ruleset; + typedef Ruleset* Ruleset_Ptr; + typedef Ruleset const* Ruleset_Ptr_Const; + class Bubble; + typedef Bubble* Bubble_Ptr; + typedef Bubble const* Bubble_Ptr_Const; + class Trace; + typedef Trace* Trace_Ptr; + typedef Trace const* Trace_Ptr_Const; + + class Media_Block; + typedef Media_Block* Media_Block_Ptr; + typedef Media_Block const* Media_Block_Ptr_Const; + class Supports_Block; + typedef Supports_Block* Supports_Block_Ptr; + typedef Supports_Block const* Supports_Block_Ptr_Const; + class Directive; + typedef Directive* Directive_Ptr; + typedef Directive const* Directive_Ptr_Const; + + + class Keyframe_Rule; + typedef Keyframe_Rule* Keyframe_Rule_Ptr; + typedef Keyframe_Rule const* Keyframe_Rule_Ptr_Const; + class At_Root_Block; + typedef At_Root_Block* At_Root_Block_Ptr; + typedef At_Root_Block const* At_Root_Block_Ptr_Const; + class Assignment; + typedef Assignment* Assignment_Ptr; + typedef Assignment const* Assignment_Ptr_Const; + + class Import; + typedef Import* Import_Ptr; + typedef Import const* Import_Ptr_Const; + class Import_Stub; + typedef Import_Stub* Import_Stub_Ptr; + typedef Import_Stub const* Import_Stub_Ptr_Const; + class Warning; + typedef Warning* Warning_Ptr; + typedef Warning const* Warning_Ptr_Const; + + class Error; + typedef Error* Error_Ptr; + typedef Error const* Error_Ptr_Const; + class Debug; + typedef Debug* Debug_Ptr; + typedef Debug const* Debug_Ptr_Const; + class Comment; + typedef Comment* Comment_Ptr; + typedef Comment const* Comment_Ptr_Const; + + class If; + typedef If* If_Ptr; + typedef If const* If_Ptr_Const; + class For; + typedef For* For_Ptr; + typedef For const* For_Ptr_Const; + class Each; + typedef Each* Each_Ptr; + typedef Each const* Each_Ptr_Const; + class While; + typedef While* While_Ptr; + typedef While const* While_Ptr_Const; + class Return; + typedef Return* Return_Ptr; + typedef Return const* Return_Ptr_Const; + class Content; + typedef Content* Content_Ptr; + typedef Content const* Content_Ptr_Const; + class Extension; + typedef Extension* Extension_Ptr; + typedef Extension const* Extension_Ptr_Const; + class Definition; + typedef Definition* Definition_Ptr; + typedef Definition const* Definition_Ptr_Const; + + class List; + typedef List* List_Ptr; + typedef List const* List_Ptr_Const; + class Map; + typedef Map* Map_Ptr; + typedef Map const* Map_Ptr_Const; + + class Mixin_Call; + typedef Mixin_Call* Mixin_Call_Ptr; + typedef Mixin_Call const* Mixin_Call_Ptr_Const; + class Binary_Expression; + typedef Binary_Expression* Binary_Expression_Ptr; + typedef Binary_Expression const* Binary_Expression_Ptr_Const; + class Unary_Expression; + typedef Unary_Expression* Unary_Expression_Ptr; + typedef Unary_Expression const* Unary_Expression_Ptr_Const; + class Function_Call; + typedef Function_Call* Function_Call_Ptr; + typedef Function_Call const* Function_Call_Ptr_Const; + class Function_Call_Schema; + typedef Function_Call_Schema* Function_Call_Schema_Ptr; + typedef Function_Call_Schema const* Function_Call_Schema_Ptr_Const; + class Custom_Warning; + typedef Custom_Warning* Custom_Warning_Ptr; + typedef Custom_Warning const* Custom_Warning_Ptr_Const; + class Custom_Error; + typedef Custom_Error* Custom_Error_Ptr; + typedef Custom_Error const* Custom_Error_Ptr_Const; + + class Variable; + typedef Variable* Variable_Ptr; + typedef Variable const* Variable_Ptr_Const; + class Textual; + typedef Textual* Textual_Ptr; + typedef Textual const* Textual_Ptr_Const; + class Number; + typedef Number* Number_Ptr; + typedef Number const* Number_Ptr_Const; + class Color; + typedef Color* Color_Ptr; + typedef Color const* Color_Ptr_Const; + class Boolean; + typedef Boolean* Boolean_Ptr; + typedef Boolean const* Boolean_Ptr_Const; + class String; + typedef String* String_Ptr; + typedef String const* String_Ptr_Const; + + class String_Schema; + typedef String_Schema* String_Schema_Ptr; + typedef String_Schema const* String_Schema_Ptr_Const; + class String_Constant; + typedef String_Constant* String_Constant_Ptr; + typedef String_Constant const* String_Constant_Ptr_Const; + class String_Quoted; + typedef String_Quoted* String_Quoted_Ptr; + typedef String_Quoted const* String_Quoted_Ptr_Const; + + class Media_Query; + typedef Media_Query* Media_Query_Ptr; + typedef Media_Query const* Media_Query_Ptr_Const; + class Media_Query_Expression; + typedef Media_Query_Expression* Media_Query_Expression_Ptr; + typedef Media_Query_Expression const* Media_Query_Expression_Ptr_Const; + class Supports_Condition; + typedef Supports_Condition* Supports_Condition_Ptr; + typedef Supports_Condition const* Supports_Condition_Ptr_Const; + class Supports_Operator; + typedef Supports_Operator* Supports_Operator_Ptr; + typedef Supports_Operator const* Supports_Operator_Ptr_Const; + class Supports_Negation; + typedef Supports_Negation* Supports_Negation_Ptr; + typedef Supports_Negation const* Supports_Negation_Ptr_Const; + class Supports_Declaration; + typedef Supports_Declaration* Supports_Declaration_Ptr; + typedef Supports_Declaration const* Supports_Declaration_Ptr_Const; + class Supports_Interpolation; + typedef Supports_Interpolation* Supports_Interpolation_Ptr; + typedef Supports_Interpolation const* Supports_Interpolation_Ptr_Const; + + + class Null; + typedef Null* Null_Ptr; + typedef Null const* Null_Ptr_Const; + + class At_Root_Query; + typedef At_Root_Query* At_Root_Query_Ptr; + typedef At_Root_Query const* At_Root_Query_Ptr_Const; + class Parent_Selector; + typedef Parent_Selector* Parent_Selector_Ptr; + typedef Parent_Selector const* Parent_Selector_Ptr_Const; + class Parameter; + typedef Parameter* Parameter_Ptr; + typedef Parameter const* Parameter_Ptr_Const; + class Parameters; + typedef Parameters* Parameters_Ptr; + typedef Parameters const* Parameters_Ptr_Const; + class Argument; + typedef Argument* Argument_Ptr; + typedef Argument const* Argument_Ptr_Const; + class Arguments; + typedef Arguments* Arguments_Ptr; + typedef Arguments const* Arguments_Ptr_Const; + class Selector; + typedef Selector* Selector_Ptr; + typedef Selector const* Selector_Ptr_Const; + + + class Selector_Schema; + typedef Selector_Schema* Selector_Schema_Ptr; + typedef Selector_Schema const* Selector_Schema_Ptr_Const; + class Placeholder_Selector; + typedef Placeholder_Selector* Placeholder_Selector_Ptr; + typedef Placeholder_Selector const* Placeholder_Selector_Ptr_Const; + class Element_Selector; + typedef Element_Selector* Element_Selector_Ptr; + typedef Element_Selector const* Element_Selector_Ptr_Const; + class Class_Selector; + typedef Class_Selector* Class_Selector_Ptr; + typedef Class_Selector const* Class_Selector_Ptr_Const; + class Id_Selector; + typedef Id_Selector* Id_Selector_Ptr; + typedef Id_Selector const* Id_Selector_Ptr_Const; + class Attribute_Selector; + typedef Attribute_Selector* Attribute_Selector_Ptr; + typedef Attribute_Selector const* Attribute_Selector_Ptr_Const; + + class Pseudo_Selector; + typedef Pseudo_Selector* Pseudo_Selector_Ptr; + typedef Pseudo_Selector const * Pseudo_Selector_Ptr_Const; + class Wrapped_Selector; + typedef Wrapped_Selector* Wrapped_Selector_Ptr; + typedef Wrapped_Selector const * Wrapped_Selector_Ptr_Const; + class Compound_Selector; + typedef Compound_Selector* Compound_Selector_Ptr; + typedef Compound_Selector const * Compound_Selector_Ptr_Const; + class Complex_Selector; + typedef Complex_Selector* Complex_Selector_Ptr; + typedef Complex_Selector const * Complex_Selector_Ptr_Const; + class Selector_List; + typedef Selector_List* Selector_List_Ptr; + typedef Selector_List const * Selector_List_Ptr_Const; + + + // common classes + class Context; + class Expand; + class Eval; + + // declare classes that are instances of memory nodes + // #define IMPL_MEM_OBJ(type) using type##_Obj = SharedImpl + #define IMPL_MEM_OBJ(type) typedef SharedImpl type##_Obj + + IMPL_MEM_OBJ(AST_Node); + IMPL_MEM_OBJ(Statement); + IMPL_MEM_OBJ(Block); + IMPL_MEM_OBJ(Ruleset); + IMPL_MEM_OBJ(Bubble); + IMPL_MEM_OBJ(Trace); + IMPL_MEM_OBJ(Media_Block); + IMPL_MEM_OBJ(Supports_Block); + IMPL_MEM_OBJ(Directive); + IMPL_MEM_OBJ(Keyframe_Rule); + IMPL_MEM_OBJ(At_Root_Block); + IMPL_MEM_OBJ(Declaration); + IMPL_MEM_OBJ(Assignment); + IMPL_MEM_OBJ(Import); + IMPL_MEM_OBJ(Import_Stub); + IMPL_MEM_OBJ(Warning); + IMPL_MEM_OBJ(Error); + IMPL_MEM_OBJ(Debug); + IMPL_MEM_OBJ(Comment); + IMPL_MEM_OBJ(PreValue); + IMPL_MEM_OBJ(Has_Block); + IMPL_MEM_OBJ(Thunk); + IMPL_MEM_OBJ(If); + IMPL_MEM_OBJ(For); + IMPL_MEM_OBJ(Each); + IMPL_MEM_OBJ(While); + IMPL_MEM_OBJ(Return); + IMPL_MEM_OBJ(Content); + IMPL_MEM_OBJ(Extension); + IMPL_MEM_OBJ(Definition); + IMPL_MEM_OBJ(Mixin_Call); + IMPL_MEM_OBJ(Value); + IMPL_MEM_OBJ(Expression); + IMPL_MEM_OBJ(List); + IMPL_MEM_OBJ(Map); + IMPL_MEM_OBJ(Binary_Expression); + IMPL_MEM_OBJ(Unary_Expression); + IMPL_MEM_OBJ(Function_Call); + IMPL_MEM_OBJ(Function_Call_Schema); + IMPL_MEM_OBJ(Custom_Warning); + IMPL_MEM_OBJ(Custom_Error); + IMPL_MEM_OBJ(Variable); + IMPL_MEM_OBJ(Textual); + IMPL_MEM_OBJ(Number); + IMPL_MEM_OBJ(Color); + IMPL_MEM_OBJ(Boolean); + IMPL_MEM_OBJ(String_Schema); + IMPL_MEM_OBJ(String); + IMPL_MEM_OBJ(String_Constant); + IMPL_MEM_OBJ(String_Quoted); + IMPL_MEM_OBJ(Media_Query); + IMPL_MEM_OBJ(Media_Query_Expression); + IMPL_MEM_OBJ(Supports_Condition); + IMPL_MEM_OBJ(Supports_Operator); + IMPL_MEM_OBJ(Supports_Negation); + IMPL_MEM_OBJ(Supports_Declaration); + IMPL_MEM_OBJ(Supports_Interpolation); + IMPL_MEM_OBJ(At_Root_Query); + IMPL_MEM_OBJ(Null); + IMPL_MEM_OBJ(Parent_Selector); + IMPL_MEM_OBJ(Parameter); + IMPL_MEM_OBJ(Parameters); + IMPL_MEM_OBJ(Argument); + IMPL_MEM_OBJ(Arguments); + IMPL_MEM_OBJ(Selector); + IMPL_MEM_OBJ(Selector_Schema); + IMPL_MEM_OBJ(Simple_Selector); + IMPL_MEM_OBJ(Placeholder_Selector); + IMPL_MEM_OBJ(Element_Selector); + IMPL_MEM_OBJ(Class_Selector); + IMPL_MEM_OBJ(Id_Selector); + IMPL_MEM_OBJ(Attribute_Selector); + IMPL_MEM_OBJ(Pseudo_Selector); + IMPL_MEM_OBJ(Wrapped_Selector); + IMPL_MEM_OBJ(Compound_Selector); + IMPL_MEM_OBJ(Complex_Selector); + IMPL_MEM_OBJ(Selector_List); + + + struct HashExpression { + size_t operator() (Expression_Obj ex) const; + }; + struct CompareExpression { + bool operator()(const Expression_Obj& lhs, const Expression_Obj& rhs) const; + }; + + struct HashSimpleSelector { + size_t operator() (Simple_Selector_Obj ex) const; + }; + + struct CompareSimpleSelector { + bool operator()(Simple_Selector_Obj lhs, Simple_Selector_Obj rhs) const; + }; + + typedef std::unordered_map< + Expression_Obj, // key + Expression_Obj, // value + HashExpression, // hasher + CompareExpression // compare + > ExpressionMap; + typedef std::unordered_set< + Expression_Obj, // value + HashExpression, // hasher + CompareExpression // compare + > ExpressionSet; + + typedef std::string Subset_Map_Key; + typedef std::vector Subset_Map_Arr; + typedef std::pair Subset_Map_Val; + +} + +#endif diff --git a/src/libsass/src/b64/cencode.h b/src/libsass/src/b64/cencode.h new file mode 100755 index 000000000..1d71e83fd --- /dev/null +++ b/src/libsass/src/b64/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/src/libsass/src/b64/encode.h b/src/libsass/src/b64/encode.h new file mode 100755 index 000000000..fac91e767 --- /dev/null +++ b/src/libsass/src/b64/encode.h @@ -0,0 +1,77 @@ +// :mode=c++: +/* +encode.h - c++ wrapper for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ +#ifndef BASE64_ENCODE_H +#define BASE64_ENCODE_H + +#include + +namespace base64 +{ + extern "C" + { + #include "cencode.h" + } + + struct encoder + { + base64_encodestate _state; + int _buffersize; + + encoder(int buffersize_in = BUFFERSIZE) + : _buffersize(buffersize_in) + {} + + int encode(char value_in) + { + return base64_encode_value(value_in); + } + + int encode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_encode_block(code_in, length_in, plaintext_out, &_state); + } + + int encode_end(char* plaintext_out) + { + return base64_encode_blockend(plaintext_out, &_state); + } + + void encode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_encodestate(&_state); + // + const int N = _buffersize; + char* plaintext = new char[N]; + char* code = new char[2*N]; + int plainlength; + int codelength; + + do + { + istream_in.read(plaintext, N); + plainlength = static_cast(istream_in.gcount()); + // + codelength = encode(plaintext, plainlength, code); + ostream_in.write(code, codelength); + } + while (istream_in.good() && plainlength > 0); + + codelength = encode_end(code); + ostream_in.write(code, codelength); + // + base64_init_encodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_ENCODE_H + diff --git a/src/libsass/src/backtrace.hpp b/src/libsass/src/backtrace.hpp new file mode 100755 index 000000000..884413589 --- /dev/null +++ b/src/libsass/src/backtrace.hpp @@ -0,0 +1,76 @@ +#ifndef SASS_BACKTRACE_H +#define SASS_BACKTRACE_H + +#include + +#include "file.hpp" +#include "position.hpp" + +namespace Sass { + + + struct Backtrace { + + Backtrace* parent; + ParserState pstate; + std::string caller; + + Backtrace(Backtrace* prn, ParserState pstate, std::string c) + : parent(prn), + pstate(pstate), + caller(c) + { } + + std::string to_string(bool warning = false) + { + size_t i = -1; + std::stringstream ss; + std::string cwd(Sass::File::get_cwd()); + Backtrace* this_point = this; + + if (!warning) ss << std::endl << "Backtrace:"; + // the first tracepoint (which is parent-less) is an empty placeholder + while (this_point->parent) { + + // make path relative to the current directory + std::string rel_path(Sass::File::abs2rel(this_point->pstate.path, cwd, cwd)); + + if (warning) { + ss << std::endl + << "\t" + << (++i == 0 ? "on" : "from") + << " line " + << this_point->pstate.line + 1 + << " of " + << rel_path; + } else { + ss << std::endl + << "\t" + << rel_path + << ":" + << this_point->pstate.line + 1 + << this_point->parent->caller; + } + + this_point = this_point->parent; + } + + return ss.str(); + } + + size_t depth() + { + size_t d = 0; + Backtrace* p = parent; + while (p) { + ++d; + p = p->parent; + } + return d-1; + } + + }; + +} + +#endif diff --git a/src/libsass/src/base64vlq.cpp b/src/libsass/src/base64vlq.cpp new file mode 100755 index 000000000..be2fb4926 --- /dev/null +++ b/src/libsass/src/base64vlq.cpp @@ -0,0 +1,44 @@ +#include "sass.hpp" +#include "base64vlq.hpp" + +namespace Sass { + + std::string Base64VLQ::encode(const int number) const + { + std::string encoded = ""; + + int vlq = to_vlq_signed(number); + + do { + int digit = vlq & VLQ_BASE_MASK; + vlq >>= VLQ_BASE_SHIFT; + if (vlq > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64_encode(digit); + } while (vlq > 0); + + return encoded; + } + + char Base64VLQ::base64_encode(const int number) const + { + int index = number; + if (index < 0) index = 0; + if (index > 63) index = 63; + return CHARACTERS[index]; + } + + int Base64VLQ::to_vlq_signed(const int number) const + { + return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; + } + + const char* Base64VLQ::CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const int Base64VLQ::VLQ_BASE_SHIFT = 5; + const int Base64VLQ::VLQ_BASE = 1 << VLQ_BASE_SHIFT; + const int Base64VLQ::VLQ_BASE_MASK = VLQ_BASE - 1; + const int Base64VLQ::VLQ_CONTINUATION_BIT = VLQ_BASE; + +} diff --git a/src/libsass/src/base64vlq.hpp b/src/libsass/src/base64vlq.hpp new file mode 100755 index 000000000..aca315a21 --- /dev/null +++ b/src/libsass/src/base64vlq.hpp @@ -0,0 +1,30 @@ +#ifndef SASS_BASE64VLQ_H +#define SASS_BASE64VLQ_H + +#include + +namespace Sass { + + class Base64VLQ { + + public: + + std::string encode(const int number) const; + + private: + + char base64_encode(const int number) const; + + int to_vlq_signed(const int number) const; + + static const char* CHARACTERS; + + static const int VLQ_BASE_SHIFT; + static const int VLQ_BASE; + static const int VLQ_BASE_MASK; + static const int VLQ_CONTINUATION_BIT; + }; + +} + +#endif diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp new file mode 100755 index 000000000..bd9d7ad34 --- /dev/null +++ b/src/libsass/src/bind.cpp @@ -0,0 +1,289 @@ +#include "sass.hpp" +#include "bind.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "eval.hpp" +#include +#include +#include + +namespace Sass { + + void bind(std::string type, std::string name, Parameters_Obj ps, Arguments_Obj as, Context* ctx, Env* env, Eval* eval) + { + std::string callee(type + " " + name); + + std::map param_map; + + for (size_t i = 0, L = as->length(); i < L; ++i) { + if (auto str = SASS_MEMORY_CAST(String_Quoted, (*as)[i]->value())) { + // force optional quotes (only if needed) + if (str->quote_mark()) { + str->quote_mark('*'); + } + } + } + + // Set up a map to ensure named arguments refer to actual parameters. Also + // eval each default value left-to-right, wrt env, populating env as we go. + for (size_t i = 0, L = ps->length(); i < L; ++i) { + Parameter_Obj p = ps->at(i); + param_map[p->name()] = p; + // if (p->default_value()) { + // env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env)); + // } + } + + // plug in all args; if we have leftover params, deal with it later + size_t ip = 0, LP = ps->length(); + size_t ia = 0, LA = as->length(); + while (ia < LA) { + Argument_Obj a = as->at(ia); + if (ip >= LP) { + // skip empty rest arguments + if (a->is_rest_argument()) { + if (List_Obj l = SASS_MEMORY_CAST(List, a->value())) { + if (l->length() == 0) { + ++ ia; continue; + } + } + } + std::stringstream msg; + msg << "wrong number of arguments (" << LA << " for " << LP << ")"; + msg << " for `" << name << "'"; + return error(msg.str(), as->pstate()); + } + Parameter_Obj p = ps->at(ip); + + // If the current parameter is the rest parameter, process and break the loop + if (p->is_rest_parameter()) { + // The next argument by coincidence provides a rest argument + if (a->is_rest_argument()) { + + // We should always get a list for rest arguments + if (List_Obj rest = SASS_MEMORY_CAST(List, a->value())) { + // create a new list object for wrapped items + List_Ptr arglist = SASS_MEMORY_NEW(List, + p->pstate(), + 0, + rest->separator(), + true); + // wrap each item from list as an argument + for (Expression_Obj item : rest->elements()) { + if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, item)) { + arglist->append(SASS_MEMORY_COPY(arg)); // copy + } else { + arglist->append(SASS_MEMORY_NEW(Argument, + item->pstate(), + &item, + "", + false, + false)); + } + } + // assign new arglist to environment + env->local_frame()[p->name()] = arglist; + } + // invalid state + else { + throw std::runtime_error("invalid state"); + } + } else if (a->is_keyword_argument()) { + + // expand keyword arguments into their parameters + List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); + env->local_frame()[p->name()] = arglist; + Map_Obj argmap = SASS_MEMORY_CAST(Map, a->value()); + for (auto key : argmap->keys()) { + std::string name = unquote(SASS_MEMORY_CAST(String_Constant, key)->value()); + arglist->append(SASS_MEMORY_NEW(Argument, + key->pstate(), + argmap->at(key), + "$" + name, + false, + false)); + } + + } else { + + // create a new list object for wrapped items + List_Obj arglist = SASS_MEMORY_NEW(List, + p->pstate(), + 0, + SASS_COMMA, + true); + // consume the next args + while (ia < LA) { + // get and post inc + a = (*as)[ia++]; + // maybe we have another list as argument + List_Obj ls = SASS_MEMORY_CAST(List, a->value()); + // skip any list completely if empty + if (ls && ls->empty() && a->is_rest_argument()) continue; + + Expression_Obj value = a->value(); + if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, value)) { + arglist->append(&arg); + } + // check if we have rest argument + else if (a->is_rest_argument()) { + // preserve the list separator from rest args + if (List_Obj rest = SASS_MEMORY_CAST(List, a->value())) { + arglist->separator(rest->separator()); + + for (size_t i = 0, L = rest->size(); i < L; ++i) { + Expression_Obj obj = rest->at(i); + arglist->append(SASS_MEMORY_NEW(Argument, + obj->pstate(), + &obj, + "", + false, + false)); + } + } + // no more arguments + break; + } + // wrap all other value types into Argument + else { + arglist->append(SASS_MEMORY_NEW(Argument, + a->pstate(), + a->value(), + a->name(), + false, + false)); + } + } + // assign new arglist to environment + env->local_frame()[p->name()] = &arglist; + } + // consumed parameter + ++ip; + // no more paramaters + break; + } + + // If the current argument is the rest argument, extract a value for processing + else if (a->is_rest_argument()) { + // normal param and rest arg + List_Obj arglist = SASS_MEMORY_CAST(List, a->value()); + // empty rest arg - treat all args as default values + if (!arglist->length()) { + break; + } else { + if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { + size_t arg_count = (arglist->length() + LA - 1); + std::stringstream msg; + msg << callee << " takes " << LP; + msg << (LP == 1 ? " argument" : " arguments"); + msg << " but " << arg_count; + msg << (arg_count == 1 ? " was passed" : " were passed."); + deprecated_bind(msg.str(), as->pstate()); + + while (arglist->length() > LP - ip) { + arglist->elements().erase(arglist->elements().end() - 1); + } + } + } + // otherwise move one of the rest args into the param, converting to argument if necessary + Expression_Obj obj = arglist->at(0); + if (!(a = SASS_MEMORY_CAST(Argument, obj))) { + Expression_Ptr a_to_convert = &obj; + a = SASS_MEMORY_NEW(Argument, + a_to_convert->pstate(), + a_to_convert, + "", + false, + false); + } + arglist->elements().erase(arglist->elements().begin()); + if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) { + ++ia; + } + + } else if (a->is_keyword_argument()) { + Map_Obj argmap = SASS_MEMORY_CAST(Map, a->value()); + + for (auto key : argmap->keys()) { + std::string name = "$" + unquote(SASS_MEMORY_CAST(String_Constant, key)->value()); + + if (!param_map.count(name)) { + std::stringstream msg; + msg << callee << " has no parameter named " << name; + error(msg.str(), a->pstate()); + } + env->local_frame()[name] = &argmap->at(&key); + } + ++ia; + continue; + } else { + ++ia; + } + + if (a->name().empty()) { + if (env->has_local(p->name())) { + std::stringstream msg; + msg << "parameter " << p->name() + << " provided more than once in call to " << callee; + error(msg.str(), a->pstate()); + } + // ordinal arg -- bind it to the next param + env->local_frame()[p->name()] = &a->value(); + ++ip; + } + else { + // named arg -- bind it to the appropriately named param + if (!param_map.count(a->name())) { + std::stringstream msg; + msg << callee << " has no parameter named " << a->name(); + error(msg.str(), a->pstate()); + } + if (param_map[a->name()]->is_rest_parameter()) { + std::stringstream msg; + msg << "argument " << a->name() << " of " << callee + << "cannot be used as named argument"; + error(msg.str(), a->pstate()); + } + if (env->has_local(a->name())) { + std::stringstream msg; + msg << "parameter " << p->name() + << "provided more than once in call to " << callee; + error(msg.str(), a->pstate()); + } + env->local_frame()[a->name()] = &a->value(); + } + } + // EO while ia + + // If we make it here, we're out of args but may have leftover params. + // That's only okay if they have default values, or were already bound by + // named arguments, or if it's a single rest-param. + for (size_t i = ip; i < LP; ++i) { + Parameter_Obj leftover = ps->at(i); + // cerr << "env for default params:" << endl; + // env->print(); + // cerr << "********" << endl; + if (!env->has_local(leftover->name())) { + if (leftover->is_rest_parameter()) { + env->local_frame()[leftover->name()] = SASS_MEMORY_NEW(List, + leftover->pstate(), + 0, + SASS_COMMA, + true); + } + else if (leftover->default_value()) { + Expression_Ptr dv = leftover->default_value()->perform(eval); + env->local_frame()[leftover->name()] = dv; + } + else { + // param is unbound and has no default value -- error + throw Exception::MissingArgument(as->pstate(), name, leftover->name(), type); + } + } + } + + return; + } + + +} diff --git a/src/libsass/src/bind.hpp b/src/libsass/src/bind.hpp new file mode 100755 index 000000000..4d17d0197 --- /dev/null +++ b/src/libsass/src/bind.hpp @@ -0,0 +1,14 @@ +#ifndef SASS_BIND_H +#define SASS_BIND_H + +#include +#include "environment.hpp" +#include "ast_fwd_decl.hpp" + +namespace Sass { + typedef Environment Env; + + void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Context*, Env*, Eval*); +} + +#endif diff --git a/src/libsass/src/c99func.c b/src/libsass/src/c99func.c new file mode 100755 index 000000000..f846eee80 --- /dev/null +++ b/src/libsass/src/c99func.c @@ -0,0 +1,54 @@ +/* + Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#if defined(_MSC_VER) && _MSC_VER < 1900 + +#include +#include +#include + +static int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +int snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} + +#endif diff --git a/src/libsass/src/cencode.c b/src/libsass/src/cencode.c new file mode 100755 index 000000000..18f1806c9 --- /dev/null +++ b/src/libsass/src/cencode.c @@ -0,0 +1,102 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "b64/cencode.h" + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + } + } + /* control should not reach here */ + return (int)(codechar - code_out); +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return (int)(codechar - code_out); +} + diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp new file mode 100755 index 000000000..5fc8542e3 --- /dev/null +++ b/src/libsass/src/check_nesting.cpp @@ -0,0 +1,376 @@ +#include "sass.hpp" +#include + +#include "check_nesting.hpp" + +namespace Sass { + + CheckNesting::CheckNesting() + : parents(std::vector()), + parent(0), + current_mixin_definition(0) + { } + + Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) + { + Statement_Ptr old_parent = this->parent; + + if (At_Root_Block_Ptr root = SASS_MEMORY_CAST_PTR(At_Root_Block, parent)) { + std::vector old_parents = this->parents; + std::vector new_parents; + + for (size_t i = 0, L = this->parents.size(); i < L; i++) { + Statement_Ptr p = this->parents.at(i); + if (!root->exclude_node(p)) { + new_parents.push_back(p); + } + } + this->parents = new_parents; + + for (size_t i = this->parents.size(); i > 0; i--) { + Statement_Ptr p = 0; + Statement_Ptr gp = 0; + if (i > 0) p = this->parents.at(i - 1); + if (i > 1) gp = this->parents.at(i - 2); + + if (!this->is_transparent_parent(p, gp)) { + this->parent = p; + break; + } + } + + At_Root_Block_Ptr ar = SASS_MEMORY_CAST_PTR(At_Root_Block, parent); + Statement_Ptr ret = this->visit_children(&ar->block()); + + this->parent = old_parent; + this->parents = old_parents; + + return ret; + } + + if (!this->is_transparent_parent(parent, old_parent)) { + this->parent = parent; + } + + this->parents.push_back(parent); + + Block_Ptr b = SASS_MEMORY_CAST_PTR(Block, parent); + + if (!b) { + if (Has_Block_Ptr bb = SASS_MEMORY_CAST(Has_Block, *parent)) { + b = &bb->block(); + } + } + + if (b) { + for (auto n : b->elements()) { + n->perform(this); + } + } + this->parent = old_parent; + this->parents.pop_back(); + + return b; + } + + + Statement_Ptr CheckNesting::operator()(Block_Ptr b) + { + return this->visit_children(b); + } + + Statement_Ptr CheckNesting::operator()(Definition_Ptr n) + { + if (!this->should_visit(n)) return NULL; + if (!is_mixin(n)) { + visit_children(n); + return n; + } + + Definition_Ptr old_mixin_definition = this->current_mixin_definition; + this->current_mixin_definition = n; + + visit_children(n); + + this->current_mixin_definition = old_mixin_definition; + + return n; + } + + Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) + { + Block_Ptr b1 = SASS_MEMORY_CAST_PTR(Block, s); + Has_Block_Ptr b2 = SASS_MEMORY_CAST_PTR(Has_Block, s); + return b1 || b2 ? visit_children(s) : s; + } + + bool CheckNesting::should_visit(Statement_Ptr node) + { + if (!this->parent) return true; + + if (SASS_MEMORY_CAST_PTR(Content, node)) + { this->invalid_content_parent(this->parent); } + + if (is_charset(node)) + { this->invalid_charset_parent(this->parent); } + + if (SASS_MEMORY_CAST_PTR(Extension, node)) + { this->invalid_extend_parent(this->parent); } + + // if (SASS_MEMORY_CAST(Import, node)) + // { this->invalid_import_parent(this->parent); } + + if (this->is_mixin(node)) + { this->invalid_mixin_definition_parent(this->parent); } + + if (this->is_function(node)) + { this->invalid_function_parent(this->parent); } + + if (this->is_function(this->parent)) + { this->invalid_function_child(node); } + + if (SASS_MEMORY_CAST_PTR(Declaration, node)) + { this->invalid_prop_parent(this->parent); } + + if (SASS_MEMORY_CAST_PTR(Declaration, this->parent)) + { this->invalid_prop_child(node); } + + if (SASS_MEMORY_CAST_PTR(Return, node)) + { this->invalid_return_parent(this->parent); } + + return true; + } + + void CheckNesting::invalid_content_parent(Statement_Ptr parent) + { + if (!this->current_mixin_definition) { + throw Exception::InvalidSass( + parent->pstate(), + "@content may only be used within a mixin." + ); + } + } + + void CheckNesting::invalid_charset_parent(Statement_Ptr parent) + { + if (!( + is_root_node(parent) + )) { + throw Exception::InvalidSass( + parent->pstate(), + "@charset may only be used at the root of a document." + ); + } + } + + void CheckNesting::invalid_extend_parent(Statement_Ptr parent) + { + if (!( + SASS_MEMORY_CAST_PTR(Ruleset, parent) || + SASS_MEMORY_CAST_PTR(Mixin_Call, parent) || + is_mixin(parent) + )) { + throw Exception::InvalidSass( + parent->pstate(), + "Extend directives may only be used within rules." + ); + } + } + + // void CheckNesting::invalid_import_parent(Statement_Ptr parent) + // { + // for (auto pp : this->parents) { + // if ( + // SASS_MEMORY_CAST(Each, pp) || + // SASS_MEMORY_CAST(For, pp) || + // SASS_MEMORY_CAST(If, pp) || + // SASS_MEMORY_CAST(While, pp) || + // SASS_MEMORY_CAST(Trace, pp) || + // SASS_MEMORY_CAST(Mixin_Call, pp) || + // is_mixin(pp) + // ) { + // throw Exception::InvalidSass( + // parent->pstate(), + // "Import directives may not be defined within control directives or other mixins." + // ); + // } + // } + + // if (this->is_root_node(parent)) { + // return; + // } + + // if (false/*n.css_import?*/) { + // throw Exception::InvalidSass( + // parent->pstate(), + // "CSS import directives may only be used at the root of a document." + // ); + // } + // } + + void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent) + { + for (Statement_Ptr pp : this->parents) { + if ( + SASS_MEMORY_CAST_PTR(Each, pp) || + SASS_MEMORY_CAST_PTR(For, pp) || + SASS_MEMORY_CAST_PTR(If, pp) || + SASS_MEMORY_CAST_PTR(While, pp) || + SASS_MEMORY_CAST_PTR(Trace, pp) || + SASS_MEMORY_CAST_PTR(Mixin_Call, pp) || + is_mixin(pp) + ) { + throw Exception::InvalidSass( + parent->pstate(), + "Mixins may not be defined within control directives or other mixins." + ); + } + } + } + + void CheckNesting::invalid_function_parent(Statement_Ptr parent) + { + for (Statement_Ptr pp : this->parents) { + if ( + SASS_MEMORY_CAST_PTR(Each, pp) || + SASS_MEMORY_CAST_PTR(For, pp) || + SASS_MEMORY_CAST_PTR(If, pp) || + SASS_MEMORY_CAST_PTR(While, pp) || + SASS_MEMORY_CAST_PTR(Trace, pp) || + SASS_MEMORY_CAST_PTR(Mixin_Call, pp) || + is_mixin(pp) + ) { + throw Exception::InvalidSass( + parent->pstate(), + "Functions may not be defined within control directives or other mixins." + ); + } + } + } + + void CheckNesting::invalid_function_child(Statement_Ptr child) + { + if (!( + SASS_MEMORY_CAST_PTR(Each, child) || + SASS_MEMORY_CAST_PTR(For, child) || + SASS_MEMORY_CAST_PTR(If, child) || + SASS_MEMORY_CAST_PTR(While, child) || + SASS_MEMORY_CAST_PTR(Trace, child) || + SASS_MEMORY_CAST_PTR(Comment, child) || + SASS_MEMORY_CAST_PTR(Debug, child) || + SASS_MEMORY_CAST_PTR(Return, child) || + SASS_MEMORY_CAST_PTR(Variable, child) || + // Ruby Sass doesn't distinguish variables and assignments + SASS_MEMORY_CAST_PTR(Assignment, child) || + SASS_MEMORY_CAST_PTR(Warning, child) || + SASS_MEMORY_CAST_PTR(Error, child) + )) { + throw Exception::InvalidSass( + child->pstate(), + "Functions can only contain variable declarations and control directives." + ); + } + } + + void CheckNesting::invalid_prop_child(Statement_Ptr child) + { + if (!( + SASS_MEMORY_CAST_PTR(Each, child) || + SASS_MEMORY_CAST_PTR(For, child) || + SASS_MEMORY_CAST_PTR(If, child) || + SASS_MEMORY_CAST_PTR(While, child) || + SASS_MEMORY_CAST_PTR(Trace, child) || + SASS_MEMORY_CAST_PTR(Comment, child) || + SASS_MEMORY_CAST_PTR(Declaration, child) || + SASS_MEMORY_CAST_PTR(Mixin_Call, child) + )) { + throw Exception::InvalidSass( + child->pstate(), + "Illegal nesting: Only properties may be nested beneath properties." + ); + } + } + + void CheckNesting::invalid_prop_parent(Statement_Ptr parent) + { + if (!( + is_mixin(parent) || + is_directive_node(parent) || + SASS_MEMORY_CAST_PTR(Ruleset, parent) || + SASS_MEMORY_CAST_PTR(Keyframe_Rule, parent) || + SASS_MEMORY_CAST_PTR(Declaration, parent) || + SASS_MEMORY_CAST_PTR(Mixin_Call, parent) + )) { + throw Exception::InvalidSass( + parent->pstate(), + "Properties are only allowed within rules, directives, mixin includes, or other properties." + ); + } + } + + void CheckNesting::invalid_return_parent(Statement_Ptr parent) + { + if (!this->is_function(parent)) { + throw Exception::InvalidSass( + parent->pstate(), + "@return may only be used within a function." + ); + } + } + + bool CheckNesting::is_transparent_parent(Statement_Ptr parent, Statement_Ptr grandparent) + { + bool parent_bubbles = parent && parent->bubbles(); + + bool valid_bubble_node = parent_bubbles && + !is_root_node(grandparent) && + !is_at_root_node(grandparent); + + return SASS_MEMORY_CAST_PTR(Import, parent) || + SASS_MEMORY_CAST_PTR(Each, parent) || + SASS_MEMORY_CAST_PTR(For, parent) || + SASS_MEMORY_CAST_PTR(If, parent) || + SASS_MEMORY_CAST_PTR(While, parent) || + SASS_MEMORY_CAST_PTR(Trace, parent) || + valid_bubble_node; + } + + bool CheckNesting::is_charset(Statement_Ptr n) + { + Directive_Ptr d = SASS_MEMORY_CAST_PTR(Directive, n); + return d && d->keyword() == "charset"; + } + + bool CheckNesting::is_mixin(Statement_Ptr n) + { + Definition_Ptr def = SASS_MEMORY_CAST_PTR(Definition, n); + return def && def->type() == Definition::MIXIN; + } + + bool CheckNesting::is_function(Statement_Ptr n) + { + Definition_Ptr def = SASS_MEMORY_CAST_PTR(Definition, n); + return def && def->type() == Definition::FUNCTION; + } + + bool CheckNesting::is_root_node(Statement_Ptr n) + { + if (SASS_MEMORY_CAST_PTR(Ruleset, n)) return false; + + Block_Ptr b = SASS_MEMORY_CAST_PTR(Block, n); + return b && b->is_root(); + } + + bool CheckNesting::is_at_root_node(Statement_Ptr n) + { + return SASS_MEMORY_CAST_PTR(At_Root_Block, n) != NULL; + } + + bool CheckNesting::is_directive_node(Statement_Ptr n) + { + return SASS_MEMORY_CAST_PTR(Directive, n) || + SASS_MEMORY_CAST_PTR(Import, n) || + SASS_MEMORY_CAST_PTR(Media_Block, n) || + SASS_MEMORY_CAST_PTR(Supports_Block, n); + } +} diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp new file mode 100755 index 000000000..c3a165a81 --- /dev/null +++ b/src/libsass/src/check_nesting.hpp @@ -0,0 +1,64 @@ +#ifndef SASS_CHECK_NESTING_H +#define SASS_CHECK_NESTING_H + +#include "ast.hpp" +#include "operation.hpp" + +namespace Sass { + + typedef Environment Env; + + class CheckNesting : public Operation_CRTP { + + std::vector parents; + Statement_Ptr parent; + Definition_Ptr current_mixin_definition; + + Statement_Ptr fallback_impl(Statement_Ptr); + Statement_Ptr before(Statement_Ptr); + Statement_Ptr visit_children(Statement_Ptr); + + public: + CheckNesting(); + ~CheckNesting() { } + + Statement_Ptr operator()(Block_Ptr); + Statement_Ptr operator()(Definition_Ptr); + + template + Statement_Ptr fallback(U x) { + Statement_Ptr n = SASS_MEMORY_CAST_PTR(Statement, x); + if (this->should_visit(n)) { + return fallback_impl(n); + } + return NULL; + } + + private: + void invalid_content_parent(Statement_Ptr); + void invalid_charset_parent(Statement_Ptr); + void invalid_extend_parent(Statement_Ptr); + // void invalid_import_parent(Statement_Ptr); + void invalid_mixin_definition_parent(Statement_Ptr); + void invalid_function_parent(Statement_Ptr); + + void invalid_function_child(Statement_Ptr); + void invalid_prop_child(Statement_Ptr); + void invalid_prop_parent(Statement_Ptr); + void invalid_return_parent(Statement_Ptr); + + bool is_transparent_parent(Statement_Ptr, Statement_Ptr); + + bool should_visit(Statement_Ptr); + + bool is_charset(Statement_Ptr); + bool is_mixin(Statement_Ptr); + bool is_function(Statement_Ptr); + bool is_root_node(Statement_Ptr); + bool is_at_root_node(Statement_Ptr); + bool is_directive_node(Statement_Ptr); + }; + +} + +#endif diff --git a/src/libsass/src/color_maps.cpp b/src/libsass/src/color_maps.cpp new file mode 100755 index 000000000..f21e9e029 --- /dev/null +++ b/src/libsass/src/color_maps.cpp @@ -0,0 +1,644 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "color_maps.hpp" + +namespace Sass { + + namespace ColorNames + { + const char aliceblue [] = "aliceblue"; + const char antiquewhite [] = "antiquewhite"; + const char cyan [] = "cyan"; + const char aqua [] = "aqua"; + const char aquamarine [] = "aquamarine"; + const char azure [] = "azure"; + const char beige [] = "beige"; + const char bisque [] = "bisque"; + const char black [] = "black"; + const char blanchedalmond [] = "blanchedalmond"; + const char blue [] = "blue"; + const char blueviolet [] = "blueviolet"; + const char brown [] = "brown"; + const char burlywood [] = "burlywood"; + const char cadetblue [] = "cadetblue"; + const char chartreuse [] = "chartreuse"; + const char chocolate [] = "chocolate"; + const char coral [] = "coral"; + const char cornflowerblue [] = "cornflowerblue"; + const char cornsilk [] = "cornsilk"; + const char crimson [] = "crimson"; + const char darkblue [] = "darkblue"; + const char darkcyan [] = "darkcyan"; + const char darkgoldenrod [] = "darkgoldenrod"; + const char darkgray [] = "darkgray"; + const char darkgrey [] = "darkgrey"; + const char darkgreen [] = "darkgreen"; + const char darkkhaki [] = "darkkhaki"; + const char darkmagenta [] = "darkmagenta"; + const char darkolivegreen [] = "darkolivegreen"; + const char darkorange [] = "darkorange"; + const char darkorchid [] = "darkorchid"; + const char darkred [] = "darkred"; + const char darksalmon [] = "darksalmon"; + const char darkseagreen [] = "darkseagreen"; + const char darkslateblue [] = "darkslateblue"; + const char darkslategray [] = "darkslategray"; + const char darkslategrey [] = "darkslategrey"; + const char darkturquoise [] = "darkturquoise"; + const char darkviolet [] = "darkviolet"; + const char deeppink [] = "deeppink"; + const char deepskyblue [] = "deepskyblue"; + const char dimgray [] = "dimgray"; + const char dimgrey [] = "dimgrey"; + const char dodgerblue [] = "dodgerblue"; + const char firebrick [] = "firebrick"; + const char floralwhite [] = "floralwhite"; + const char forestgreen [] = "forestgreen"; + const char magenta [] = "magenta"; + const char fuchsia [] = "fuchsia"; + const char gainsboro [] = "gainsboro"; + const char ghostwhite [] = "ghostwhite"; + const char gold [] = "gold"; + const char goldenrod [] = "goldenrod"; + const char gray [] = "gray"; + const char grey [] = "grey"; + const char green [] = "green"; + const char greenyellow [] = "greenyellow"; + const char honeydew [] = "honeydew"; + const char hotpink [] = "hotpink"; + const char indianred [] = "indianred"; + const char indigo [] = "indigo"; + const char ivory [] = "ivory"; + const char khaki [] = "khaki"; + const char lavender [] = "lavender"; + const char lavenderblush [] = "lavenderblush"; + const char lawngreen [] = "lawngreen"; + const char lemonchiffon [] = "lemonchiffon"; + const char lightblue [] = "lightblue"; + const char lightcoral [] = "lightcoral"; + const char lightcyan [] = "lightcyan"; + const char lightgoldenrodyellow [] = "lightgoldenrodyellow"; + const char lightgray [] = "lightgray"; + const char lightgrey [] = "lightgrey"; + const char lightgreen [] = "lightgreen"; + const char lightpink [] = "lightpink"; + const char lightsalmon [] = "lightsalmon"; + const char lightseagreen [] = "lightseagreen"; + const char lightskyblue [] = "lightskyblue"; + const char lightslategray [] = "lightslategray"; + const char lightslategrey [] = "lightslategrey"; + const char lightsteelblue [] = "lightsteelblue"; + const char lightyellow [] = "lightyellow"; + const char lime [] = "lime"; + const char limegreen [] = "limegreen"; + const char linen [] = "linen"; + const char maroon [] = "maroon"; + const char mediumaquamarine [] = "mediumaquamarine"; + const char mediumblue [] = "mediumblue"; + const char mediumorchid [] = "mediumorchid"; + const char mediumpurple [] = "mediumpurple"; + const char mediumseagreen [] = "mediumseagreen"; + const char mediumslateblue [] = "mediumslateblue"; + const char mediumspringgreen [] = "mediumspringgreen"; + const char mediumturquoise [] = "mediumturquoise"; + const char mediumvioletred [] = "mediumvioletred"; + const char midnightblue [] = "midnightblue"; + const char mintcream [] = "mintcream"; + const char mistyrose [] = "mistyrose"; + const char moccasin [] = "moccasin"; + const char navajowhite [] = "navajowhite"; + const char navy [] = "navy"; + const char oldlace [] = "oldlace"; + const char olive [] = "olive"; + const char olivedrab [] = "olivedrab"; + const char orange [] = "orange"; + const char orangered [] = "orangered"; + const char orchid [] = "orchid"; + const char palegoldenrod [] = "palegoldenrod"; + const char palegreen [] = "palegreen"; + const char paleturquoise [] = "paleturquoise"; + const char palevioletred [] = "palevioletred"; + const char papayawhip [] = "papayawhip"; + const char peachpuff [] = "peachpuff"; + const char peru [] = "peru"; + const char pink [] = "pink"; + const char plum [] = "plum"; + const char powderblue [] = "powderblue"; + const char purple [] = "purple"; + const char red [] = "red"; + const char rosybrown [] = "rosybrown"; + const char royalblue [] = "royalblue"; + const char saddlebrown [] = "saddlebrown"; + const char salmon [] = "salmon"; + const char sandybrown [] = "sandybrown"; + const char seagreen [] = "seagreen"; + const char seashell [] = "seashell"; + const char sienna [] = "sienna"; + const char silver [] = "silver"; + const char skyblue [] = "skyblue"; + const char slateblue [] = "slateblue"; + const char slategray [] = "slategray"; + const char slategrey [] = "slategrey"; + const char snow [] = "snow"; + const char springgreen [] = "springgreen"; + const char steelblue [] = "steelblue"; + const char tan [] = "tan"; + const char teal [] = "teal"; + const char thistle [] = "thistle"; + const char tomato [] = "tomato"; + const char turquoise [] = "turquoise"; + const char violet [] = "violet"; + const char wheat [] = "wheat"; + const char white [] = "white"; + const char whitesmoke [] = "whitesmoke"; + const char yellow [] = "yellow"; + const char yellowgreen [] = "yellowgreen"; + const char rebeccapurple [] = "rebeccapurple"; + const char transparent [] = "transparent"; + } + + namespace Colors { + const ParserState color_table("[COLOR TABLE]"); + const Color aliceblue(color_table, 240, 248, 255, 1); + const Color antiquewhite(color_table, 250, 235, 215, 1); + const Color cyan(color_table, 0, 255, 255, 1); + const Color aqua(color_table, 0, 255, 255, 1); + const Color aquamarine(color_table, 127, 255, 212, 1); + const Color azure(color_table, 240, 255, 255, 1); + const Color beige(color_table, 245, 245, 220, 1); + const Color bisque(color_table, 255, 228, 196, 1); + const Color black(color_table, 0, 0, 0, 1); + const Color blanchedalmond(color_table, 255, 235, 205, 1); + const Color blue(color_table, 0, 0, 255, 1); + const Color blueviolet(color_table, 138, 43, 226, 1); + const Color brown(color_table, 165, 42, 42, 1); + const Color burlywood(color_table, 222, 184, 135, 1); + const Color cadetblue(color_table, 95, 158, 160, 1); + const Color chartreuse(color_table, 127, 255, 0, 1); + const Color chocolate(color_table, 210, 105, 30, 1); + const Color coral(color_table, 255, 127, 80, 1); + const Color cornflowerblue(color_table, 100, 149, 237, 1); + const Color cornsilk(color_table, 255, 248, 220, 1); + const Color crimson(color_table, 220, 20, 60, 1); + const Color darkblue(color_table, 0, 0, 139, 1); + const Color darkcyan(color_table, 0, 139, 139, 1); + const Color darkgoldenrod(color_table, 184, 134, 11, 1); + const Color darkgray(color_table, 169, 169, 169, 1); + const Color darkgrey(color_table, 169, 169, 169, 1); + const Color darkgreen(color_table, 0, 100, 0, 1); + const Color darkkhaki(color_table, 189, 183, 107, 1); + const Color darkmagenta(color_table, 139, 0, 139, 1); + const Color darkolivegreen(color_table, 85, 107, 47, 1); + const Color darkorange(color_table, 255, 140, 0, 1); + const Color darkorchid(color_table, 153, 50, 204, 1); + const Color darkred(color_table, 139, 0, 0, 1); + const Color darksalmon(color_table, 233, 150, 122, 1); + const Color darkseagreen(color_table, 143, 188, 143, 1); + const Color darkslateblue(color_table, 72, 61, 139, 1); + const Color darkslategray(color_table, 47, 79, 79, 1); + const Color darkslategrey(color_table, 47, 79, 79, 1); + const Color darkturquoise(color_table, 0, 206, 209, 1); + const Color darkviolet(color_table, 148, 0, 211, 1); + const Color deeppink(color_table, 255, 20, 147, 1); + const Color deepskyblue(color_table, 0, 191, 255, 1); + const Color dimgray(color_table, 105, 105, 105, 1); + const Color dimgrey(color_table, 105, 105, 105, 1); + const Color dodgerblue(color_table, 30, 144, 255, 1); + const Color firebrick(color_table, 178, 34, 34, 1); + const Color floralwhite(color_table, 255, 250, 240, 1); + const Color forestgreen(color_table, 34, 139, 34, 1); + const Color magenta(color_table, 255, 0, 255, 1); + const Color fuchsia(color_table, 255, 0, 255, 1); + const Color gainsboro(color_table, 220, 220, 220, 1); + const Color ghostwhite(color_table, 248, 248, 255, 1); + const Color gold(color_table, 255, 215, 0, 1); + const Color goldenrod(color_table, 218, 165, 32, 1); + const Color gray(color_table, 128, 128, 128, 1); + const Color grey(color_table, 128, 128, 128, 1); + const Color green(color_table, 0, 128, 0, 1); + const Color greenyellow(color_table, 173, 255, 47, 1); + const Color honeydew(color_table, 240, 255, 240, 1); + const Color hotpink(color_table, 255, 105, 180, 1); + const Color indianred(color_table, 205, 92, 92, 1); + const Color indigo(color_table, 75, 0, 130, 1); + const Color ivory(color_table, 255, 255, 240, 1); + const Color khaki(color_table, 240, 230, 140, 1); + const Color lavender(color_table, 230, 230, 250, 1); + const Color lavenderblush(color_table, 255, 240, 245, 1); + const Color lawngreen(color_table, 124, 252, 0, 1); + const Color lemonchiffon(color_table, 255, 250, 205, 1); + const Color lightblue(color_table, 173, 216, 230, 1); + const Color lightcoral(color_table, 240, 128, 128, 1); + const Color lightcyan(color_table, 224, 255, 255, 1); + const Color lightgoldenrodyellow(color_table, 250, 250, 210, 1); + const Color lightgray(color_table, 211, 211, 211, 1); + const Color lightgrey(color_table, 211, 211, 211, 1); + const Color lightgreen(color_table, 144, 238, 144, 1); + const Color lightpink(color_table, 255, 182, 193, 1); + const Color lightsalmon(color_table, 255, 160, 122, 1); + const Color lightseagreen(color_table, 32, 178, 170, 1); + const Color lightskyblue(color_table, 135, 206, 250, 1); + const Color lightslategray(color_table, 119, 136, 153, 1); + const Color lightslategrey(color_table, 119, 136, 153, 1); + const Color lightsteelblue(color_table, 176, 196, 222, 1); + const Color lightyellow(color_table, 255, 255, 224, 1); + const Color lime(color_table, 0, 255, 0, 1); + const Color limegreen(color_table, 50, 205, 50, 1); + const Color linen(color_table, 250, 240, 230, 1); + const Color maroon(color_table, 128, 0, 0, 1); + const Color mediumaquamarine(color_table, 102, 205, 170, 1); + const Color mediumblue(color_table, 0, 0, 205, 1); + const Color mediumorchid(color_table, 186, 85, 211, 1); + const Color mediumpurple(color_table, 147, 112, 219, 1); + const Color mediumseagreen(color_table, 60, 179, 113, 1); + const Color mediumslateblue(color_table, 123, 104, 238, 1); + const Color mediumspringgreen(color_table, 0, 250, 154, 1); + const Color mediumturquoise(color_table, 72, 209, 204, 1); + const Color mediumvioletred(color_table, 199, 21, 133, 1); + const Color midnightblue(color_table, 25, 25, 112, 1); + const Color mintcream(color_table, 245, 255, 250, 1); + const Color mistyrose(color_table, 255, 228, 225, 1); + const Color moccasin(color_table, 255, 228, 181, 1); + const Color navajowhite(color_table, 255, 222, 173, 1); + const Color navy(color_table, 0, 0, 128, 1); + const Color oldlace(color_table, 253, 245, 230, 1); + const Color olive(color_table, 128, 128, 0, 1); + const Color olivedrab(color_table, 107, 142, 35, 1); + const Color orange(color_table, 255, 165, 0, 1); + const Color orangered(color_table, 255, 69, 0, 1); + const Color orchid(color_table, 218, 112, 214, 1); + const Color palegoldenrod(color_table, 238, 232, 170, 1); + const Color palegreen(color_table, 152, 251, 152, 1); + const Color paleturquoise(color_table, 175, 238, 238, 1); + const Color palevioletred(color_table, 219, 112, 147, 1); + const Color papayawhip(color_table, 255, 239, 213, 1); + const Color peachpuff(color_table, 255, 218, 185, 1); + const Color peru(color_table, 205, 133, 63, 1); + const Color pink(color_table, 255, 192, 203, 1); + const Color plum(color_table, 221, 160, 221, 1); + const Color powderblue(color_table, 176, 224, 230, 1); + const Color purple(color_table, 128, 0, 128, 1); + const Color red(color_table, 255, 0, 0, 1); + const Color rosybrown(color_table, 188, 143, 143, 1); + const Color royalblue(color_table, 65, 105, 225, 1); + const Color saddlebrown(color_table, 139, 69, 19, 1); + const Color salmon(color_table, 250, 128, 114, 1); + const Color sandybrown(color_table, 244, 164, 96, 1); + const Color seagreen(color_table, 46, 139, 87, 1); + const Color seashell(color_table, 255, 245, 238, 1); + const Color sienna(color_table, 160, 82, 45, 1); + const Color silver(color_table, 192, 192, 192, 1); + const Color skyblue(color_table, 135, 206, 235, 1); + const Color slateblue(color_table, 106, 90, 205, 1); + const Color slategray(color_table, 112, 128, 144, 1); + const Color slategrey(color_table, 112, 128, 144, 1); + const Color snow(color_table, 255, 250, 250, 1); + const Color springgreen(color_table, 0, 255, 127, 1); + const Color steelblue(color_table, 70, 130, 180, 1); + const Color tan(color_table, 210, 180, 140, 1); + const Color teal(color_table, 0, 128, 128, 1); + const Color thistle(color_table, 216, 191, 216, 1); + const Color tomato(color_table, 255, 99, 71, 1); + const Color turquoise(color_table, 64, 224, 208, 1); + const Color violet(color_table, 238, 130, 238, 1); + const Color wheat(color_table, 245, 222, 179, 1); + const Color white(color_table, 255, 255, 255, 1); + const Color whitesmoke(color_table, 245, 245, 245, 1); + const Color yellow(color_table, 255, 255, 0, 1); + const Color yellowgreen(color_table, 154, 205, 50, 1); + const Color rebeccapurple(color_table, 102, 51, 153, 1); + const Color transparent(color_table, 0, 0, 0, 0); + } + + const std::map colors_to_names { + { 240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue }, + { 250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite }, + { 0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan }, + { 127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine }, + { 240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure }, + { 245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige }, + { 255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque }, + { 0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black }, + { 255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond }, + { 0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue }, + { 138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet }, + { 165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown }, + { 222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood }, + { 95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue }, + { 127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse }, + { 210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate }, + { 255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral }, + { 100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue }, + { 255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk }, + { 220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson }, + { 0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue }, + { 0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan }, + { 184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod }, + { 169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray }, + { 0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen }, + { 189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki }, + { 139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta }, + { 85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen }, + { 255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange }, + { 153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid }, + { 139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred }, + { 233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon }, + { 143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen }, + { 72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue }, + { 47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray }, + { 0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise }, + { 148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet }, + { 255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink }, + { 0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue }, + { 105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray }, + { 30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue }, + { 178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick }, + { 255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite }, + { 34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen }, + { 255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta }, + { 220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro }, + { 248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite }, + { 255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold }, + { 218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod }, + { 128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray }, + { 0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green }, + { 173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow }, + { 240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew }, + { 255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink }, + { 205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred }, + { 75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo }, + { 255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory }, + { 240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki }, + { 230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender }, + { 255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush }, + { 124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen }, + { 255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon }, + { 173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue }, + { 240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral }, + { 224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan }, + { 250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow }, + { 211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray }, + { 144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen }, + { 255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink }, + { 255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon }, + { 32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen }, + { 135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue }, + { 119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray }, + { 176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue }, + { 255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow }, + { 0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime }, + { 50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen }, + { 250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen }, + { 128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon }, + { 102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine }, + { 0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue }, + { 186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid }, + { 147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple }, + { 60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen }, + { 123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue }, + { 0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen }, + { 72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise }, + { 199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred }, + { 25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue }, + { 245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream }, + { 255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose }, + { 255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin }, + { 255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite }, + { 0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy }, + { 253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace }, + { 128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive }, + { 107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab }, + { 255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange }, + { 255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered }, + { 218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid }, + { 238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod }, + { 152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen }, + { 175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise }, + { 219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred }, + { 255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip }, + { 255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff }, + { 205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru }, + { 255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink }, + { 221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum }, + { 176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue }, + { 128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple }, + { 255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red }, + { 188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown }, + { 65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue }, + { 139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown }, + { 250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon }, + { 244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown }, + { 46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen }, + { 255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell }, + { 160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna }, + { 192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver }, + { 135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue }, + { 106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue }, + { 112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray }, + { 255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow }, + { 0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen }, + { 70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue }, + { 210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan }, + { 0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal }, + { 216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle }, + { 255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato }, + { 64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise }, + { 238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet }, + { 245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat }, + { 255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white }, + { 245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke }, + { 255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow }, + { 154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen }, + { 102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple } + }; + + const std::map names_to_colors + { + { ColorNames::aliceblue, &Colors::aliceblue }, + { ColorNames::antiquewhite, &Colors::antiquewhite }, + { ColorNames::cyan, &Colors::cyan }, + { ColorNames::aqua, &Colors::aqua }, + { ColorNames::aquamarine, &Colors::aquamarine }, + { ColorNames::azure, &Colors::azure }, + { ColorNames::beige, &Colors::beige }, + { ColorNames::bisque, &Colors::bisque }, + { ColorNames::black, &Colors::black }, + { ColorNames::blanchedalmond, &Colors::blanchedalmond }, + { ColorNames::blue, &Colors::blue }, + { ColorNames::blueviolet, &Colors::blueviolet }, + { ColorNames::brown, &Colors::brown }, + { ColorNames::burlywood, &Colors::burlywood }, + { ColorNames::cadetblue, &Colors::cadetblue }, + { ColorNames::chartreuse, &Colors::chartreuse }, + { ColorNames::chocolate, &Colors::chocolate }, + { ColorNames::coral, &Colors::coral }, + { ColorNames::cornflowerblue, &Colors::cornflowerblue }, + { ColorNames::cornsilk, &Colors::cornsilk }, + { ColorNames::crimson, &Colors::crimson }, + { ColorNames::darkblue, &Colors::darkblue }, + { ColorNames::darkcyan, &Colors::darkcyan }, + { ColorNames::darkgoldenrod, &Colors::darkgoldenrod }, + { ColorNames::darkgray, &Colors::darkgray }, + { ColorNames::darkgrey, &Colors::darkgrey }, + { ColorNames::darkgreen, &Colors::darkgreen }, + { ColorNames::darkkhaki, &Colors::darkkhaki }, + { ColorNames::darkmagenta, &Colors::darkmagenta }, + { ColorNames::darkolivegreen, &Colors::darkolivegreen }, + { ColorNames::darkorange, &Colors::darkorange }, + { ColorNames::darkorchid, &Colors::darkorchid }, + { ColorNames::darkred, &Colors::darkred }, + { ColorNames::darksalmon, &Colors::darksalmon }, + { ColorNames::darkseagreen, &Colors::darkseagreen }, + { ColorNames::darkslateblue, &Colors::darkslateblue }, + { ColorNames::darkslategray, &Colors::darkslategray }, + { ColorNames::darkslategrey, &Colors::darkslategrey }, + { ColorNames::darkturquoise, &Colors::darkturquoise }, + { ColorNames::darkviolet, &Colors::darkviolet }, + { ColorNames::deeppink, &Colors::deeppink }, + { ColorNames::deepskyblue, &Colors::deepskyblue }, + { ColorNames::dimgray, &Colors::dimgray }, + { ColorNames::dimgrey, &Colors::dimgrey }, + { ColorNames::dodgerblue, &Colors::dodgerblue }, + { ColorNames::firebrick, &Colors::firebrick }, + { ColorNames::floralwhite, &Colors::floralwhite }, + { ColorNames::forestgreen, &Colors::forestgreen }, + { ColorNames::magenta, &Colors::magenta }, + { ColorNames::fuchsia, &Colors::fuchsia }, + { ColorNames::gainsboro, &Colors::gainsboro }, + { ColorNames::ghostwhite, &Colors::ghostwhite }, + { ColorNames::gold, &Colors::gold }, + { ColorNames::goldenrod, &Colors::goldenrod }, + { ColorNames::gray, &Colors::gray }, + { ColorNames::grey, &Colors::grey }, + { ColorNames::green, &Colors::green }, + { ColorNames::greenyellow, &Colors::greenyellow }, + { ColorNames::honeydew, &Colors::honeydew }, + { ColorNames::hotpink, &Colors::hotpink }, + { ColorNames::indianred, &Colors::indianred }, + { ColorNames::indigo, &Colors::indigo }, + { ColorNames::ivory, &Colors::ivory }, + { ColorNames::khaki, &Colors::khaki }, + { ColorNames::lavender, &Colors::lavender }, + { ColorNames::lavenderblush, &Colors::lavenderblush }, + { ColorNames::lawngreen, &Colors::lawngreen }, + { ColorNames::lemonchiffon, &Colors::lemonchiffon }, + { ColorNames::lightblue, &Colors::lightblue }, + { ColorNames::lightcoral, &Colors::lightcoral }, + { ColorNames::lightcyan, &Colors::lightcyan }, + { ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow }, + { ColorNames::lightgray, &Colors::lightgray }, + { ColorNames::lightgrey, &Colors::lightgrey }, + { ColorNames::lightgreen, &Colors::lightgreen }, + { ColorNames::lightpink, &Colors::lightpink }, + { ColorNames::lightsalmon, &Colors::lightsalmon }, + { ColorNames::lightseagreen, &Colors::lightseagreen }, + { ColorNames::lightskyblue, &Colors::lightskyblue }, + { ColorNames::lightslategray, &Colors::lightslategray }, + { ColorNames::lightslategrey, &Colors::lightslategrey }, + { ColorNames::lightsteelblue, &Colors::lightsteelblue }, + { ColorNames::lightyellow, &Colors::lightyellow }, + { ColorNames::lime, &Colors::lime }, + { ColorNames::limegreen, &Colors::limegreen }, + { ColorNames::linen, &Colors::linen }, + { ColorNames::maroon, &Colors::maroon }, + { ColorNames::mediumaquamarine, &Colors::mediumaquamarine }, + { ColorNames::mediumblue, &Colors::mediumblue }, + { ColorNames::mediumorchid, &Colors::mediumorchid }, + { ColorNames::mediumpurple, &Colors::mediumpurple }, + { ColorNames::mediumseagreen, &Colors::mediumseagreen }, + { ColorNames::mediumslateblue, &Colors::mediumslateblue }, + { ColorNames::mediumspringgreen, &Colors::mediumspringgreen }, + { ColorNames::mediumturquoise, &Colors::mediumturquoise }, + { ColorNames::mediumvioletred, &Colors::mediumvioletred }, + { ColorNames::midnightblue, &Colors::midnightblue }, + { ColorNames::mintcream, &Colors::mintcream }, + { ColorNames::mistyrose, &Colors::mistyrose }, + { ColorNames::moccasin, &Colors::moccasin }, + { ColorNames::navajowhite, &Colors::navajowhite }, + { ColorNames::navy, &Colors::navy }, + { ColorNames::oldlace, &Colors::oldlace }, + { ColorNames::olive, &Colors::olive }, + { ColorNames::olivedrab, &Colors::olivedrab }, + { ColorNames::orange, &Colors::orange }, + { ColorNames::orangered, &Colors::orangered }, + { ColorNames::orchid, &Colors::orchid }, + { ColorNames::palegoldenrod, &Colors::palegoldenrod }, + { ColorNames::palegreen, &Colors::palegreen }, + { ColorNames::paleturquoise, &Colors::paleturquoise }, + { ColorNames::palevioletred, &Colors::palevioletred }, + { ColorNames::papayawhip, &Colors::papayawhip }, + { ColorNames::peachpuff, &Colors::peachpuff }, + { ColorNames::peru, &Colors::peru }, + { ColorNames::pink, &Colors::pink }, + { ColorNames::plum, &Colors::plum }, + { ColorNames::powderblue, &Colors::powderblue }, + { ColorNames::purple, &Colors::purple }, + { ColorNames::red, &Colors::red }, + { ColorNames::rosybrown, &Colors::rosybrown }, + { ColorNames::royalblue, &Colors::royalblue }, + { ColorNames::saddlebrown, &Colors::saddlebrown }, + { ColorNames::salmon, &Colors::salmon }, + { ColorNames::sandybrown, &Colors::sandybrown }, + { ColorNames::seagreen, &Colors::seagreen }, + { ColorNames::seashell, &Colors::seashell }, + { ColorNames::sienna, &Colors::sienna }, + { ColorNames::silver, &Colors::silver }, + { ColorNames::skyblue, &Colors::skyblue }, + { ColorNames::slateblue, &Colors::slateblue }, + { ColorNames::slategray, &Colors::slategray }, + { ColorNames::slategrey, &Colors::slategrey }, + { ColorNames::snow, &Colors::snow }, + { ColorNames::springgreen, &Colors::springgreen }, + { ColorNames::steelblue, &Colors::steelblue }, + { ColorNames::tan, &Colors::tan }, + { ColorNames::teal, &Colors::teal }, + { ColorNames::thistle, &Colors::thistle }, + { ColorNames::tomato, &Colors::tomato }, + { ColorNames::turquoise, &Colors::turquoise }, + { ColorNames::violet, &Colors::violet }, + { ColorNames::wheat, &Colors::wheat }, + { ColorNames::white, &Colors::white }, + { ColorNames::whitesmoke, &Colors::whitesmoke }, + { ColorNames::yellow, &Colors::yellow }, + { ColorNames::yellowgreen, &Colors::yellowgreen }, + { ColorNames::rebeccapurple, &Colors::rebeccapurple }, + { ColorNames::transparent, &Colors::transparent } + }; + + Color_Ptr_Const name_to_color(const char* key) + { + auto p = names_to_colors.find(key); + if (p != names_to_colors.end()) { + return p->second; + } + return 0; + } + + Color_Ptr_Const name_to_color(const std::string& key) + { + return name_to_color(key.c_str()); + } + + const char* color_to_name(const int key) + { + auto p = colors_to_names.find(key); + if (p != colors_to_names.end()) { + return p->second; + } + return 0; + } + + const char* color_to_name(const double key) + { + return color_to_name((int)key); + } + + const char* color_to_name(const Color& c) + { + double key = c.r() * 0x10000 + + c.g() * 0x100 + + c.b(); + return color_to_name(key); + } + +} diff --git a/src/libsass/src/color_maps.hpp b/src/libsass/src/color_maps.hpp new file mode 100755 index 000000000..a225f42d9 --- /dev/null +++ b/src/libsass/src/color_maps.hpp @@ -0,0 +1,333 @@ +#ifndef SASS_COLOR_MAPS_H +#define SASS_COLOR_MAPS_H + +#include +#include "ast.hpp" + +namespace Sass { + + struct map_cmp_str + { + bool operator()(char const *a, char const *b) const + { + return std::strcmp(a, b) < 0; + } + }; + + namespace ColorNames + { + extern const char aliceblue[]; + extern const char antiquewhite[]; + extern const char cyan[]; + extern const char aqua[]; + extern const char aquamarine[]; + extern const char azure[]; + extern const char beige[]; + extern const char bisque[]; + extern const char black[]; + extern const char blanchedalmond[]; + extern const char blue[]; + extern const char blueviolet[]; + extern const char brown[]; + extern const char burlywood[]; + extern const char cadetblue[]; + extern const char chartreuse[]; + extern const char chocolate[]; + extern const char coral[]; + extern const char cornflowerblue[]; + extern const char cornsilk[]; + extern const char crimson[]; + extern const char darkblue[]; + extern const char darkcyan[]; + extern const char darkgoldenrod[]; + extern const char darkgray[]; + extern const char darkgrey[]; + extern const char darkgreen[]; + extern const char darkkhaki[]; + extern const char darkmagenta[]; + extern const char darkolivegreen[]; + extern const char darkorange[]; + extern const char darkorchid[]; + extern const char darkred[]; + extern const char darksalmon[]; + extern const char darkseagreen[]; + extern const char darkslateblue[]; + extern const char darkslategray[]; + extern const char darkslategrey[]; + extern const char darkturquoise[]; + extern const char darkviolet[]; + extern const char deeppink[]; + extern const char deepskyblue[]; + extern const char dimgray[]; + extern const char dimgrey[]; + extern const char dodgerblue[]; + extern const char firebrick[]; + extern const char floralwhite[]; + extern const char forestgreen[]; + extern const char magenta[]; + extern const char fuchsia[]; + extern const char gainsboro[]; + extern const char ghostwhite[]; + extern const char gold[]; + extern const char goldenrod[]; + extern const char gray[]; + extern const char grey[]; + extern const char green[]; + extern const char greenyellow[]; + extern const char honeydew[]; + extern const char hotpink[]; + extern const char indianred[]; + extern const char indigo[]; + extern const char ivory[]; + extern const char khaki[]; + extern const char lavender[]; + extern const char lavenderblush[]; + extern const char lawngreen[]; + extern const char lemonchiffon[]; + extern const char lightblue[]; + extern const char lightcoral[]; + extern const char lightcyan[]; + extern const char lightgoldenrodyellow[]; + extern const char lightgray[]; + extern const char lightgrey[]; + extern const char lightgreen[]; + extern const char lightpink[]; + extern const char lightsalmon[]; + extern const char lightseagreen[]; + extern const char lightskyblue[]; + extern const char lightslategray[]; + extern const char lightslategrey[]; + extern const char lightsteelblue[]; + extern const char lightyellow[]; + extern const char lime[]; + extern const char limegreen[]; + extern const char linen[]; + extern const char maroon[]; + extern const char mediumaquamarine[]; + extern const char mediumblue[]; + extern const char mediumorchid[]; + extern const char mediumpurple[]; + extern const char mediumseagreen[]; + extern const char mediumslateblue[]; + extern const char mediumspringgreen[]; + extern const char mediumturquoise[]; + extern const char mediumvioletred[]; + extern const char midnightblue[]; + extern const char mintcream[]; + extern const char mistyrose[]; + extern const char moccasin[]; + extern const char navajowhite[]; + extern const char navy[]; + extern const char oldlace[]; + extern const char olive[]; + extern const char olivedrab[]; + extern const char orange[]; + extern const char orangered[]; + extern const char orchid[]; + extern const char palegoldenrod[]; + extern const char palegreen[]; + extern const char paleturquoise[]; + extern const char palevioletred[]; + extern const char papayawhip[]; + extern const char peachpuff[]; + extern const char peru[]; + extern const char pink[]; + extern const char plum[]; + extern const char powderblue[]; + extern const char purple[]; + extern const char red[]; + extern const char rosybrown[]; + extern const char royalblue[]; + extern const char saddlebrown[]; + extern const char salmon[]; + extern const char sandybrown[]; + extern const char seagreen[]; + extern const char seashell[]; + extern const char sienna[]; + extern const char silver[]; + extern const char skyblue[]; + extern const char slateblue[]; + extern const char slategray[]; + extern const char slategrey[]; + extern const char snow[]; + extern const char springgreen[]; + extern const char steelblue[]; + extern const char tan[]; + extern const char teal[]; + extern const char thistle[]; + extern const char tomato[]; + extern const char turquoise[]; + extern const char violet[]; + extern const char wheat[]; + extern const char white[]; + extern const char whitesmoke[]; + extern const char yellow[]; + extern const char yellowgreen[]; + extern const char rebeccapurple[]; + extern const char transparent[]; + } + + namespace Colors { + extern const Color aliceblue; + extern const Color antiquewhite; + extern const Color cyan; + extern const Color aqua; + extern const Color aquamarine; + extern const Color azure; + extern const Color beige; + extern const Color bisque; + extern const Color black; + extern const Color blanchedalmond; + extern const Color blue; + extern const Color blueviolet; + extern const Color brown; + extern const Color burlywood; + extern const Color cadetblue; + extern const Color chartreuse; + extern const Color chocolate; + extern const Color coral; + extern const Color cornflowerblue; + extern const Color cornsilk; + extern const Color crimson; + extern const Color darkblue; + extern const Color darkcyan; + extern const Color darkgoldenrod; + extern const Color darkgray; + extern const Color darkgrey; + extern const Color darkgreen; + extern const Color darkkhaki; + extern const Color darkmagenta; + extern const Color darkolivegreen; + extern const Color darkorange; + extern const Color darkorchid; + extern const Color darkred; + extern const Color darksalmon; + extern const Color darkseagreen; + extern const Color darkslateblue; + extern const Color darkslategray; + extern const Color darkslategrey; + extern const Color darkturquoise; + extern const Color darkviolet; + extern const Color deeppink; + extern const Color deepskyblue; + extern const Color dimgray; + extern const Color dimgrey; + extern const Color dodgerblue; + extern const Color firebrick; + extern const Color floralwhite; + extern const Color forestgreen; + extern const Color magenta; + extern const Color fuchsia; + extern const Color gainsboro; + extern const Color ghostwhite; + extern const Color gold; + extern const Color goldenrod; + extern const Color gray; + extern const Color grey; + extern const Color green; + extern const Color greenyellow; + extern const Color honeydew; + extern const Color hotpink; + extern const Color indianred; + extern const Color indigo; + extern const Color ivory; + extern const Color khaki; + extern const Color lavender; + extern const Color lavenderblush; + extern const Color lawngreen; + extern const Color lemonchiffon; + extern const Color lightblue; + extern const Color lightcoral; + extern const Color lightcyan; + extern const Color lightgoldenrodyellow; + extern const Color lightgray; + extern const Color lightgrey; + extern const Color lightgreen; + extern const Color lightpink; + extern const Color lightsalmon; + extern const Color lightseagreen; + extern const Color lightskyblue; + extern const Color lightslategray; + extern const Color lightslategrey; + extern const Color lightsteelblue; + extern const Color lightyellow; + extern const Color lime; + extern const Color limegreen; + extern const Color linen; + extern const Color maroon; + extern const Color mediumaquamarine; + extern const Color mediumblue; + extern const Color mediumorchid; + extern const Color mediumpurple; + extern const Color mediumseagreen; + extern const Color mediumslateblue; + extern const Color mediumspringgreen; + extern const Color mediumturquoise; + extern const Color mediumvioletred; + extern const Color midnightblue; + extern const Color mintcream; + extern const Color mistyrose; + extern const Color moccasin; + extern const Color navajowhite; + extern const Color navy; + extern const Color oldlace; + extern const Color olive; + extern const Color olivedrab; + extern const Color orange; + extern const Color orangered; + extern const Color orchid; + extern const Color palegoldenrod; + extern const Color palegreen; + extern const Color paleturquoise; + extern const Color palevioletred; + extern const Color papayawhip; + extern const Color peachpuff; + extern const Color peru; + extern const Color pink; + extern const Color plum; + extern const Color powderblue; + extern const Color purple; + extern const Color red; + extern const Color rosybrown; + extern const Color royalblue; + extern const Color saddlebrown; + extern const Color salmon; + extern const Color sandybrown; + extern const Color seagreen; + extern const Color seashell; + extern const Color sienna; + extern const Color silver; + extern const Color skyblue; + extern const Color slateblue; + extern const Color slategray; + extern const Color slategrey; + extern const Color snow; + extern const Color springgreen; + extern const Color steelblue; + extern const Color tan; + extern const Color teal; + extern const Color thistle; + extern const Color tomato; + extern const Color turquoise; + extern const Color violet; + extern const Color wheat; + extern const Color white; + extern const Color whitesmoke; + extern const Color yellow; + extern const Color yellowgreen; + extern const Color rebeccapurple; + extern const Color transparent; + } + + extern const std::map colors_to_names; + extern const std::map names_to_colors; + + extern Color_Ptr_Const name_to_color(const char*); + extern Color_Ptr_Const name_to_color(const std::string&); + extern const char* color_to_name(const int); + extern const char* color_to_name(const Color&); + extern const char* color_to_name(const double); + +} + +#endif diff --git a/src/libsass/src/constants.cpp b/src/libsass/src/constants.cpp new file mode 100755 index 000000000..4246b3e52 --- /dev/null +++ b/src/libsass/src/constants.cpp @@ -0,0 +1,178 @@ +#include "sass.hpp" +#include "constants.hpp" + +namespace Sass { + namespace Constants { + + extern const unsigned long MaxCallStack = 1024; + + // https://github.com/sass/libsass/issues/592 + // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity + // https://github.com/sass/sass/issues/1495#issuecomment-61189114 + extern const unsigned long Specificity_Star = 0; + extern const unsigned long Specificity_Universal = 0; + extern const unsigned long Specificity_Element = 1; + extern const unsigned long Specificity_Base = 1000; + extern const unsigned long Specificity_Class = 1000; + extern const unsigned long Specificity_Attr = 1000; + extern const unsigned long Specificity_Pseudo = 1000; + extern const unsigned long Specificity_ID = 1000000; + + // sass keywords + extern const char at_root_kwd[] = "@at-root"; + extern const char import_kwd[] = "@import"; + extern const char mixin_kwd[] = "@mixin"; + extern const char function_kwd[] = "@function"; + extern const char return_kwd[] = "@return"; + extern const char include_kwd[] = "@include"; + extern const char content_kwd[] = "@content"; + extern const char extend_kwd[] = "@extend"; + extern const char if_kwd[] = "@if"; + extern const char else_kwd[] = "@else"; + extern const char if_after_else_kwd[] = "if"; + extern const char for_kwd[] = "@for"; + extern const char from_kwd[] = "from"; + extern const char to_kwd[] = "to"; + extern const char through_kwd[] = "through"; + extern const char each_kwd[] = "@each"; + extern const char in_kwd[] = "in"; + extern const char while_kwd[] = "@while"; + extern const char warn_kwd[] = "@warn"; + extern const char error_kwd[] = "@error"; + extern const char debug_kwd[] = "@debug"; + extern const char default_kwd[] = "default"; + extern const char global_kwd[] = "global"; + extern const char null_kwd[] = "null"; + extern const char optional_kwd[] = "optional"; + extern const char with_kwd[] = "with"; + extern const char without_kwd[] = "without"; + extern const char all_kwd[] = "all"; + extern const char rule_kwd[] = "rule"; + + // css standard units + extern const char em_kwd[] = "em"; + extern const char ex_kwd[] = "ex"; + extern const char px_kwd[] = "px"; + extern const char cm_kwd[] = "cm"; + extern const char mm_kwd[] = "mm"; + extern const char pt_kwd[] = "pt"; + extern const char pc_kwd[] = "pc"; + extern const char deg_kwd[] = "deg"; + extern const char rad_kwd[] = "rad"; + extern const char grad_kwd[] = "grad"; + extern const char turn_kwd[] = "turn"; + extern const char ms_kwd[] = "ms"; + extern const char s_kwd[] = "s"; + extern const char Hz_kwd[] = "Hz"; + extern const char kHz_kwd[] = "kHz"; + + // vendor prefixes + extern const char vendor_opera_kwd[] = "-o-"; + extern const char vendor_webkit_kwd[] = "-webkit-"; + extern const char vendor_mozilla_kwd[] = "-moz-"; + extern const char vendor_ms_kwd[] = "-ms-"; + extern const char vendor_khtml_kwd[] = "-khtml-"; + + // css functions and keywords + extern const char charset_kwd[] = "@charset"; + extern const char media_kwd[] = "@media"; + extern const char supports_kwd[] = "@supports"; + extern const char keyframes_kwd[] = "keyframes"; + extern const char only_kwd[] = "only"; + extern const char rgb_kwd[] = "rgb("; + extern const char url_kwd[] = "url"; + // extern const char url_prefix_kwd[] = "url-prefix("; + extern const char important_kwd[] = "important"; + extern const char pseudo_not_kwd[] = ":not("; + extern const char even_kwd[] = "even"; + extern const char odd_kwd[] = "odd"; + extern const char progid_kwd[] = "progid"; + extern const char expression_kwd[] = "expression"; + extern const char calc_fn_kwd[] = "calc"; + + extern const char almost_any_value_class[] = "\"'#!;{}"; + + // css selector keywords + extern const char sel_deep_kwd[] = "/deep/"; + + // css attribute-matching operators + extern const char tilde_equal[] = "~="; + extern const char pipe_equal[] = "|="; + extern const char caret_equal[] = "^="; + extern const char dollar_equal[] = "$="; + extern const char star_equal[] = "*="; + + // relational & logical operators and constants + extern const char and_kwd[] = "and"; + extern const char or_kwd[] = "or"; + extern const char not_kwd[] = "not"; + extern const char gt[] = ">"; + extern const char gte[] = ">="; + extern const char lt[] = "<"; + extern const char lte[] = "<="; + extern const char eq[] = "=="; + extern const char neq[] = "!="; + extern const char true_kwd[] = "true"; + extern const char false_kwd[] = "false"; + + // miscellaneous punctuation and delimiters + extern const char percent_str[] = "%"; + extern const char empty_str[] = ""; + extern const char slash_slash[] = "//"; + extern const char slash_star[] = "/*"; + extern const char star_slash[] = "*/"; + extern const char hash_lbrace[] = "#{"; + extern const char rbrace[] = "}"; + extern const char rparen[] = ")"; + extern const char sign_chars[] = "-+"; + extern const char op_chars[] = "-+"; + extern const char hyphen[] = "-"; + extern const char ellipsis[] = "..."; + // extern const char url_space_chars[] = " \t\r\n\f"; + // type names + extern const char numeric_name[] = "numeric value"; + extern const char number_name[] = "number"; + extern const char percentage_name[] = "percentage"; + extern const char dimension_name[] = "numeric dimension"; + extern const char string_name[] = "string"; + extern const char bool_name[] = "bool"; + extern const char color_name[] = "color"; + extern const char list_name[] = "list"; + extern const char map_name[] = "map"; + extern const char arglist_name[] = "arglist"; + + // constants for uri parsing (RFC 3986 Appendix A.) + extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; + extern const char real_uri_chars[] = "#%&"; + + // some specific constant character classes + // they must be static to be useable by lexer + extern const char static_ops[] = "*/%"; + // some character classes for the parser + extern const char selector_list_delims[] = "){};!"; + extern const char complex_selector_delims[] = ",){};!"; + extern const char selector_combinator_ops[] = "+~>"; + // optional modifiers for alternative compare context + extern const char attribute_compare_modifiers[] = "~|^$*"; + extern const char selector_lookahead_ops[] = "*&%,()[]"; + + // byte order marks + // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) + extern const unsigned char utf_8_bom[] = { 0xEF, 0xBB, 0xBF }; + extern const unsigned char utf_16_bom_be[] = { 0xFE, 0xFF }; + extern const unsigned char utf_16_bom_le[] = { 0xFF, 0xFE }; + extern const unsigned char utf_32_bom_be[] = { 0x00, 0x00, 0xFE, 0xFF }; + extern const unsigned char utf_32_bom_le[] = { 0xFF, 0xFE, 0x00, 0x00 }; + extern const unsigned char utf_7_bom_1[] = { 0x2B, 0x2F, 0x76, 0x38 }; + extern const unsigned char utf_7_bom_2[] = { 0x2B, 0x2F, 0x76, 0x39 }; + extern const unsigned char utf_7_bom_3[] = { 0x2B, 0x2F, 0x76, 0x2B }; + extern const unsigned char utf_7_bom_4[] = { 0x2B, 0x2F, 0x76, 0x2F }; + extern const unsigned char utf_7_bom_5[] = { 0x2B, 0x2F, 0x76, 0x38, 0x2D }; + extern const unsigned char utf_1_bom[] = { 0xF7, 0x64, 0x4C }; + extern const unsigned char utf_ebcdic_bom[] = { 0xDD, 0x73, 0x66, 0x73 }; + extern const unsigned char scsu_bom[] = { 0x0E, 0xFE, 0xFF }; + extern const unsigned char bocu_1_bom[] = { 0xFB, 0xEE, 0x28 }; + extern const unsigned char gb_18030_bom[] = { 0x84, 0x31, 0x95, 0x33 }; + + } +} diff --git a/src/libsass/src/constants.hpp b/src/libsass/src/constants.hpp new file mode 100755 index 000000000..8470d5d6a --- /dev/null +++ b/src/libsass/src/constants.hpp @@ -0,0 +1,180 @@ +#ifndef SASS_CONSTANTS_H +#define SASS_CONSTANTS_H + +namespace Sass { + namespace Constants { + + // The maximum call stack that can be created + extern const unsigned long MaxCallStack; + + // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity + // The following list of selectors is by increasing specificity: + extern const unsigned long Specificity_Star; + extern const unsigned long Specificity_Universal; + extern const unsigned long Specificity_Element; + extern const unsigned long Specificity_Base; + extern const unsigned long Specificity_Class; + extern const unsigned long Specificity_Attr; + extern const unsigned long Specificity_Pseudo; + extern const unsigned long Specificity_ID; + + // sass keywords + extern const char at_root_kwd[]; + extern const char import_kwd[]; + extern const char mixin_kwd[]; + extern const char function_kwd[]; + extern const char return_kwd[]; + extern const char include_kwd[]; + extern const char content_kwd[]; + extern const char extend_kwd[]; + extern const char if_kwd[]; + extern const char else_kwd[]; + extern const char if_after_else_kwd[]; + extern const char for_kwd[]; + extern const char from_kwd[]; + extern const char to_kwd[]; + extern const char through_kwd[]; + extern const char each_kwd[]; + extern const char in_kwd[]; + extern const char while_kwd[]; + extern const char warn_kwd[]; + extern const char error_kwd[]; + extern const char debug_kwd[]; + extern const char default_kwd[]; + extern const char global_kwd[]; + extern const char null_kwd[]; + extern const char optional_kwd[]; + extern const char with_kwd[]; + extern const char without_kwd[]; + extern const char all_kwd[]; + extern const char rule_kwd[]; + + // css standard units + extern const char em_kwd[]; + extern const char ex_kwd[]; + extern const char px_kwd[]; + extern const char cm_kwd[]; + extern const char mm_kwd[]; + extern const char pt_kwd[]; + extern const char pc_kwd[]; + extern const char deg_kwd[]; + extern const char rad_kwd[]; + extern const char grad_kwd[]; + extern const char turn_kwd[]; + extern const char ms_kwd[]; + extern const char s_kwd[]; + extern const char Hz_kwd[]; + extern const char kHz_kwd[]; + + // vendor prefixes + extern const char vendor_opera_kwd[]; + extern const char vendor_webkit_kwd[]; + extern const char vendor_mozilla_kwd[]; + extern const char vendor_ms_kwd[]; + extern const char vendor_khtml_kwd[]; + + // css functions and keywords + extern const char charset_kwd[]; + extern const char media_kwd[]; + extern const char supports_kwd[]; + extern const char keyframes_kwd[]; + extern const char only_kwd[]; + extern const char rgb_kwd[]; + extern const char url_kwd[]; + // extern const char url_prefix_kwd[]; + extern const char important_kwd[]; + extern const char pseudo_not_kwd[]; + extern const char even_kwd[]; + extern const char odd_kwd[]; + extern const char progid_kwd[]; + extern const char expression_kwd[]; + extern const char calc_fn_kwd[]; + + // char classes for "regular expressions" + extern const char almost_any_value_class[]; + + // css selector keywords + extern const char sel_deep_kwd[]; + + // css attribute-matching operators + extern const char tilde_equal[]; + extern const char pipe_equal[]; + extern const char caret_equal[]; + extern const char dollar_equal[]; + extern const char star_equal[]; + + // relational & logical operators and constants + extern const char and_kwd[]; + extern const char or_kwd[]; + extern const char not_kwd[]; + extern const char gt[]; + extern const char gte[]; + extern const char lt[]; + extern const char lte[]; + extern const char eq[]; + extern const char neq[]; + extern const char true_kwd[]; + extern const char false_kwd[]; + + // miscellaneous punctuation and delimiters + extern const char percent_str[]; + extern const char empty_str[]; + extern const char slash_slash[]; + extern const char slash_star[]; + extern const char star_slash[]; + extern const char hash_lbrace[]; + extern const char rbrace[]; + extern const char rparen[]; + extern const char sign_chars[]; + extern const char op_chars[]; + extern const char hyphen[]; + extern const char ellipsis[]; + // extern const char url_space_chars[]; + + // type names + extern const char numeric_name[]; + extern const char number_name[]; + extern const char percentage_name[]; + extern const char dimension_name[]; + extern const char string_name[]; + extern const char bool_name[]; + extern const char color_name[]; + extern const char list_name[]; + extern const char map_name[]; + extern const char arglist_name[]; + + // constants for uri parsing (RFC 3986 Appendix A.) + extern const char uri_chars[]; + extern const char real_uri_chars[]; + + // some specific constant character classes + // they must be static to be useable by lexer + extern const char static_ops[]; + extern const char selector_list_delims[]; + extern const char complex_selector_delims[]; + extern const char selector_combinator_ops[]; + extern const char attribute_compare_modifiers[]; + extern const char selector_lookahead_ops[]; + + // byte order marks + // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) + extern const unsigned char utf_8_bom[]; + extern const unsigned char utf_16_bom_be[]; + extern const unsigned char utf_16_bom_le[]; + extern const unsigned char utf_32_bom_be[]; + extern const unsigned char utf_32_bom_le[]; + extern const unsigned char utf_7_bom_1[]; + extern const unsigned char utf_7_bom_2[]; + extern const unsigned char utf_7_bom_3[]; + extern const unsigned char utf_7_bom_4[]; + extern const unsigned char utf_7_bom_5[]; + extern const unsigned char utf_1_bom[]; + extern const unsigned char utf_ebcdic_bom[]; + extern const unsigned char scsu_bom[]; + extern const unsigned char bocu_1_bom[]; + extern const unsigned char gb_18030_bom[]; + + } +} + +#endif diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp new file mode 100755 index 000000000..91b69f310 --- /dev/null +++ b/src/libsass/src/context.cpp @@ -0,0 +1,863 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "util.hpp" +#include "sass.h" +#include "context.hpp" +#include "plugins.hpp" +#include "constants.hpp" +#include "parser.hpp" +#include "file.hpp" +#include "inspect.hpp" +#include "output.hpp" +#include "expand.hpp" +#include "eval.hpp" +#include "check_nesting.hpp" +#include "cssize.hpp" +#include "listize.hpp" +#include "extend.hpp" +#include "remove_placeholders.hpp" +#include "functions.hpp" +#include "sass_functions.hpp" +#include "backtrace.hpp" +#include "sass2scss.h" +#include "prelexer.hpp" +#include "emitter.hpp" + +namespace Sass { + using namespace Constants; + using namespace File; + using namespace Sass; + + inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) + { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } + + static std::string safe_input(const char* in_path) + { + // enforce some safe defaults + // used to create relative file links + std::string safe_path(in_path ? in_path : ""); + return safe_path == "" ? "stdin" : safe_path; + } + + static std::string safe_output(const char* out_path, const std::string& input_path = "") + { + std::string safe_path(out_path ? out_path : ""); + // maybe we can extract an output path from input path + if (safe_path == "" && input_path != "") { + int lastindex = static_cast(input_path.find_last_of(".")); + return (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css"; + } + // enforce some safe defaults + // used to create relative file links + return safe_path == "" ? "stdout" : safe_path; + } + + Context::Context(struct Sass_Context& c_ctx) + : CWD(File::get_cwd()), + c_options(c_ctx), + entry_path(""), + head_imports(0), + plugins(), + emitter(c_options), + + strings(), + resources(), + sheets(), + subset_map(), + import_stack(), + + c_headers (std::vector()), + c_importers (std::vector()), + c_functions (std::vector()), + + indent (safe_str(c_options.indent, " ")), + linefeed (safe_str(c_options.linefeed, "\n")), + + input_path (make_canonical_path(safe_input(c_options.input_path))), + output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), + source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), + source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) + + { + + // add cwd to include paths + include_paths.push_back(CWD); + + // collect more paths from different options + collect_include_paths(c_options.include_path); + collect_include_paths(c_options.include_paths); + collect_plugin_paths(c_options.plugin_path); + collect_plugin_paths(c_options.plugin_paths); + + // load plugins and register custom behaviors + for(auto plug : plugin_paths) plugins.load_plugins(plug); + for(auto fn : plugins.get_headers()) c_headers.push_back(fn); + for(auto fn : plugins.get_importers()) c_importers.push_back(fn); + for(auto fn : plugins.get_functions()) c_functions.push_back(fn); + + // sort the items by priority (lowest first) + sort (c_headers.begin(), c_headers.end(), sort_importers); + sort (c_importers.begin(), c_importers.end(), sort_importers); + + emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); + + } + + void Context::add_c_function(Sass_Function_Entry function) + { + c_functions.push_back(function); + } + void Context::add_c_header(Sass_Importer_Entry header) + { + c_headers.push_back(header); + // need to sort the array afterwards (no big deal) + sort (c_headers.begin(), c_headers.end(), sort_importers); + } + void Context::add_c_importer(Sass_Importer_Entry importer) + { + c_importers.push_back(importer); + // need to sort the array afterwards (no big deal) + sort (c_importers.begin(), c_importers.end(), sort_importers); + } + + Context::~Context() + { + // resources were allocated by strdup or malloc + for (size_t i = 0; i < resources.size(); ++i) { + free(resources[i].contents); + free(resources[i].srcmap); + } + // free all strings we kept alive during compiler execution + for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); + // everything that gets put into sources will be freed by us + // this shouldn't have anything in it anyway!? + for (size_t m = 0; m < import_stack.size(); ++m) { + sass_import_take_source(import_stack[m]); + sass_import_take_srcmap(import_stack[m]); + sass_delete_import(import_stack[m]); + } + // clear inner structures (vectors) and input source + resources.clear(); import_stack.clear(); + subset_map.clear(), sheets.clear(); + } + + Data_Context::~Data_Context() + { + // --> this will be freed by resources + // make sure we free the source even if not processed! + // if (resources.size() == 0 && source_c_str) free(source_c_str); + // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); + // source_c_str = 0; srcmap_c_str = 0; + } + + File_Context::~File_Context() + { + } + + void Context::collect_include_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + } + } + + void Context::collect_include_paths(string_list* paths_array) + { + while (paths_array) + { + collect_include_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + void Context::collect_plugin_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + } + } + + void Context::collect_plugin_paths(string_list* paths_array) + { + while (paths_array) + { + collect_plugin_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + // resolve the imp_path in base_path or include_paths + // looks for alternatives and returns a list from one directory + std::vector Context::find_includes(const Importer& import) + { + // make sure we resolve against an absolute path + std::string base_path(rel2abs(import.base_path)); + // first try to resolve the load path relative to the base path + std::vector vec(resolve_includes(base_path, import.imp_path)); + // then search in every include path (but only if nothing found yet) + for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) + { + // call resolve_includes and individual base path and append all results + std::vector resolved(resolve_includes(include_paths[i], import.imp_path)); + if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); + } + // return vector + return vec; + } + + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res, ParserState* prstate) + { + + // do not parse same resource twice + // maybe raise an error in this case + // if (sheets.count(inc.abs_path)) { + // free(res.contents); free(res.srcmap); + // throw std::runtime_error("duplicate resource registered"); + // return; + // } + + // get index for this resource + size_t idx = resources.size(); + + // tell emitter about new resource + emitter.add_source_index(idx); + + // put resources under our control + // the memory will be freed later + resources.push_back(res); + + // add a relative link to the working directory + included_files.push_back(inc.abs_path); + // add a relative link to the source map output file + srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); + + // get pointer to the loaded content + Sass_Import_Entry import = sass_make_import( + inc.imp_path.c_str(), + inc.abs_path.c_str(), + res.contents, + res.srcmap + ); + // add the entry to the stack + import_stack.push_back(import); + + // get pointer to the loaded content + const char* contents = resources[idx].contents; + // keep a copy of the path around (for parserstates) + // ToDo: we clean it, but still not very elegant!? + strings.push_back(sass_copy_c_string(inc.abs_path.c_str())); + // create the initial parser state from resource + ParserState pstate(strings.back(), contents, idx); + + // check existing import stack for possible recursion + for (size_t i = 0; i < import_stack.size() - 2; ++i) { + auto parent = import_stack[i]; + if (std::strcmp(parent->abs_path, import->abs_path) == 0) { + std::string stack("An @import loop has been found:"); + for (size_t n = 1; n < i + 2; ++n) { + stack += "\n " + std::string(import_stack[n]->imp_path) + + " imports " + std::string(import_stack[n+1]->imp_path); + } + // implement error throw directly until we + // decided how to handle full stack traces + ParserState state = prstate ? *prstate : pstate; + throw Exception::InvalidSyntax(state, stack, &import_stack); + // error(stack, prstate ? *prstate : pstate, import_stack); + } + } + + // create a parser instance from the given c_str buffer + Parser p(Parser::from_c_str(contents, *this, pstate)); + // do not yet dispose these buffers + sass_import_take_source(import); + sass_import_take_srcmap(import); + // then parse the root block + Block_Obj root = p.parse(); + // delete memory of current stack frame + sass_delete_import(import_stack.back()); + // remove current stack frame + import_stack.pop_back(); + // create key/value pair for ast node + std::pair + ast_pair(inc.abs_path, { res, root }); + // register resulting resource + sheets.insert(ast_pair); + + } + + // Add a new import to the context (called from `import_url`) + Include Context::load_import(const Importer& imp, ParserState pstate) + { + + // search for valid imports (ie. partials) on the filesystem + // this may return more than one valid result (ambiguous imp_path) + const std::vector resolved(find_includes(imp)); + + // error nicely on ambiguous imp_path + if (resolved.size() > 1) { + std::stringstream msg_stream; + msg_stream << "It's not clear which file to import for "; + msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; + msg_stream << "Candidates:" << "\n"; + for (size_t i = 0, L = resolved.size(); i < L; ++i) + { msg_stream << " " << resolved[i].imp_path << "\n"; } + msg_stream << "Please delete or rename all but one of these files." << "\n"; + error(msg_stream.str(), pstate); + } + + // process the resolved entry + else if (resolved.size() == 1) { + bool use_cache = c_importers.size() == 0; + // use cache for the resource loading + if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; + // try to read the content of the resolved file entry + // the memory buffer returned must be freed by us! + if (char* contents = read_file(resolved[0].abs_path)) { + // register the newly resolved file resource + register_resource(resolved[0], { contents, 0 }, &pstate); + // return resolved entry + return resolved[0]; + } + } + + // nothing found + return { imp, "" }; + + } + + void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { + + ParserState pstate(imp->pstate()); + std::string imp_path(unquote(load_path)); + std::string protocol("file"); + + using namespace Prelexer; + if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { + + protocol = std::string(imp_path.c_str(), proto - 3); + // if (protocol.compare("file") && true) { } + } + + // add urls (protocol other than file) and urls without procotol to `urls` member + // ToDo: if ctx_path is already a file resource, we should not add it here? + if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { + imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); + } + else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { + String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); + Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); + Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); + loc_args->append(&loc_arg); + Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", &loc_args); + imp->urls().push_back(new_url); + } + else { + const Importer importer(imp_path, ctx_path); + Include include(load_import(importer, pstate)); + if (include.abs_path.empty()) { + error("File to import not found or unreadable: " + imp_path + ".\nParent style sheet: " + ctx_path, pstate); + } + imp->incs().push_back(include); + } + + } + + + // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet + bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) + { + // unique counter + size_t count = 0; + // need one correct import + bool has_import = false; + // process all custom importers (or custom headers) + for (Sass_Importer_Entry& importer : importers) { + // int priority = sass_importer_get_priority(importer); + Sass_Importer_Fn fn = sass_importer_get_function(importer); + // skip importer if it returns NULL + if (Sass_Import_List includes = + fn(load_path.c_str(), importer, c_compiler) + ) { + // get c pointer copy to iterate over + Sass_Import_List it_includes = includes; + while (*it_includes) { ++count; + // create unique path to use as key + std::string uniq_path = load_path; + if (!only_one && count) { + std::stringstream path_strm; + path_strm << uniq_path << ":" << count; + uniq_path = path_strm.str(); + } + // create the importer struct + Importer importer(uniq_path, ctx_path); + // query data from the current include + Sass_Import_Entry include = *it_includes; + char* source = sass_import_take_source(include); + char* srcmap = sass_import_take_srcmap(include); + size_t line = sass_import_get_error_line(include); + size_t column = sass_import_get_error_column(include); + const char *abs_path = sass_import_get_abs_path(include); + // handle error message passed back from custom importer + // it may (or may not) override the line and column info + if (const char* err_message = sass_import_get_error_message(include)) { + if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, &pstate); + if (line == std::string::npos && column == std::string::npos) error(err_message, pstate); + else error(err_message, ParserState(ctx_path, source, Position(line, column))); + } + // content for import was set + else if (source) { + // resolved abs_path should be set by custom importer + // use the created uniq_path as fallback (maybe enforce) + std::string path_key(abs_path ? abs_path : uniq_path); + // create the importer struct + Include include(importer, path_key); + // attach information to AST node + imp->incs().push_back(include); + // register the resource buffers + register_resource(include, { source, srcmap }, &pstate); + } + // only a path was retuned + // try to load it like normal + else if(abs_path) { + // checks some urls to preserve + // `http://`, `https://` and `//` + // or dispatchs to `import_file` + // which will check for a `.css` extension + // or resolves the file on the filesystem + // added and resolved via `add_file` + // finally stores everything on `imp` + import_url(imp, abs_path, ctx_path); + } + // move to next + ++it_includes; + } + // deallocate the returned memory + sass_delete_import_list(includes); + // set success flag + has_import = true; + // break out of loop + if (only_one) break; + } + } + // return result + return has_import; + } + + void register_function(Context&, Signature sig, Native_Function f, Env* env); + void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); + void register_overload_stub(Context&, std::string name, Env* env); + void register_built_in_functions(Context&, Env* env); + void register_c_functions(Context&, Env* env, Sass_Function_List); + void register_c_function(Context&, Env* env, Sass_Function_Entry); + + char* Context::render(Block_Obj root) + { + // check for valid block + if (!root) return 0; + // start the render process + root->perform(&emitter); + // finish emitter stream + emitter.finalize(); + // get the resulting buffer from stream + OutputBuffer emitted = emitter.get_buffer(); + // should we append a source map url? + if (!c_options.omit_source_map_url) { + // generate an embeded source map + if (c_options.source_map_embed) { + emitted.buffer += linefeed; + emitted.buffer += format_embedded_source_map(); + } + // or just link the generated one + else if (source_map_file != "") { + emitted.buffer += linefeed; + emitted.buffer += format_source_mapping_url(source_map_file); + } + } + // create a copy of the resulting buffer string + // this must be freed or taken over by implementor + return sass_copy_c_string(emitted.buffer.c_str()); + } + + void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, ParserState pstate) + { + // create a custom import to resolve headers + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + // dispatch headers which will add custom functions + // custom headers are added to the import instance + call_headers(entry_path, ctx_path, pstate, &imp); + // increase head count to skip later + head_imports += resources.size() - 1; + // add the statement if we have urls + if (!imp->urls().empty()) root->append(&imp); + // process all other resources (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + Block_Obj File_Context::parse() + { + + // check if entry file is given + if (input_path.empty()) return 0; + + // create absolute path from input filename + // ToDo: this should be resolved via custom importers + std::string abs_path(rel2abs(input_path, CWD)); + + // try to load the entry file + char* contents = read_file(abs_path); + + // alternatively also look inside each include path folder + // I think this differs from ruby sass (IMO too late to remove) + for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { + // build absolute path for this include path entry + abs_path = rel2abs(input_path, include_paths[i]); + // try to load the resulting path + contents = read_file(abs_path); + } + + // abort early if no content could be loaded (various reasons) + if (!contents) throw std::runtime_error("File to read not found or unreadable: " + input_path); + + // store entry path + entry_path = abs_path; + + // create entry only for import stack + Sass_Import_Entry import = sass_make_import( + input_path.c_str(), + entry_path.c_str(), + contents, + 0 + ); + // add the entry to the stack + import_stack.push_back(import); + + // create the source entry for file entry + register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); + + // create root ast tree node + return compile(); + + } + + Block_Obj Data_Context::parse() + { + + // check if source string is given + if (!source_c_str) return 0; + + // convert indented sass syntax + if(c_options.is_indented_syntax_src) { + // call sass2scss to convert the string + char * converted = sass2scss(source_c_str, + // preserve the structure as much as possible + SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); + // replace old source_c_str with converted + free(source_c_str); source_c_str = converted; + } + + // remember entry path (defaults to stdin for string) + entry_path = input_path.empty() ? "stdin" : input_path; + + // ToDo: this may be resolved via custom importers + std::string abs_path(rel2abs(entry_path)); + char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); + strings.push_back(abs_path_c_str); + + // create entry only for the import stack + Sass_Import_Entry import = sass_make_import( + entry_path.c_str(), + abs_path_c_str, + source_c_str, + srcmap_c_str + ); + // add the entry to the stack + import_stack.push_back(import); + + // register a synthetic resource (path does not really exist, skip in includes) + register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); + + // create root ast tree node + return compile(); + } + + + + // parse root block from includes + Block_Obj Context::compile() + { + // abort if there is no data + if (resources.size() == 0) return 0; + // get root block from the first style sheet + Block_Obj root = sheets.at(entry_path).root; + // abort on invalid root + if (root.isNull()) return 0; + Env global; // create root environment + // register built-in functions on env + register_built_in_functions(*this, &global); + // register custom functions (defined via C-API) + for (size_t i = 0, S = c_functions.size(); i < S; ++i) + { register_c_function(*this, &global, c_functions[i]); } + // create initial backtrace entry + Backtrace backtrace(0, ParserState("", 0), ""); + // create crtp visitor objects + Expand expand(*this, &global, &backtrace); + Cssize cssize(*this, &backtrace); + CheckNesting check_nesting; + // check nesting + check_nesting(&root); + // expand and eval the tree + root = expand(&root); + // check nesting + check_nesting(&root); + // merge and bubble certain rules + root = cssize(&root); + // should we extend something? + if (!subset_map.empty()) { + // create crtp visitor object + Extend extend(*this, subset_map); + // extend tree nodes + extend(&root); + } + + // clean up by removing empty placeholders + // ToDo: maybe we can do this somewhere else? + Remove_Placeholders remove_placeholders(*this); + root->perform(&remove_placeholders); + // return processed tree + return root; + } + // EO compile + + std::string Context::format_embedded_source_map() + { + std::string map = emitter.render_srcmap(*this); + std::istringstream is( map ); + std::ostringstream buffer; + base64::encoder E; + E.encode(is, buffer); + std::string url = "data:application/json;base64," + buffer.str(); + url.erase(url.size() - 1); + return "/*# sourceMappingURL=" + url + " */"; + } + + std::string Context::format_source_mapping_url(const std::string& file) + { + std::string url = abs2rel(file, output_path, CWD); + return "/*# sourceMappingURL=" + url + " */"; + } + + char* Context::render_srcmap() + { + if (source_map_file == "") return 0; + char* result = 0; + std::string map = emitter.render_srcmap(*this); + result = sass_copy_c_string(map.c_str()); + return result; + } + + + // for data context we want to start after "stdin" + // we probably always want to skip the header includes? + std::vector Context::get_included_files(bool skip, size_t headers) + { + // create a copy of the vector for manipulations + std::vector includes = included_files; + if (includes.size() == 0) return includes; + if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } + else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } + includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); + std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); + return includes; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + std::stringstream ss; + ss << def->name() << "[f]" << arity; + def->environment(env); + (*env)[ss.str()] = def; + } + + void register_overload_stub(Context& ctx, std::string name, Env* env) + { + Definition_Ptr stub = SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + 0, + name, + 0, + 0, + true); + (*env)[name + "[f]"] = stub; + } + + + void register_built_in_functions(Context& ctx, Env* env) + { + using namespace Functions; + // RGB Functions + register_function(ctx, rgb_sig, rgb, env); + register_overload_stub(ctx, "rgba", env); + register_function(ctx, rgba_4_sig, rgba_4, 4, env); + register_function(ctx, rgba_2_sig, rgba_2, 2, env); + register_function(ctx, red_sig, red, env); + register_function(ctx, green_sig, green, env); + register_function(ctx, blue_sig, blue, env); + register_function(ctx, mix_sig, mix, env); + // HSL Functions + register_function(ctx, hsl_sig, hsl, env); + register_function(ctx, hsla_sig, hsla, env); + register_function(ctx, hue_sig, hue, env); + register_function(ctx, saturation_sig, saturation, env); + register_function(ctx, lightness_sig, lightness, env); + register_function(ctx, adjust_hue_sig, adjust_hue, env); + register_function(ctx, lighten_sig, lighten, env); + register_function(ctx, darken_sig, darken, env); + register_function(ctx, saturate_sig, saturate, env); + register_function(ctx, desaturate_sig, desaturate, env); + register_function(ctx, grayscale_sig, grayscale, env); + register_function(ctx, complement_sig, complement, env); + register_function(ctx, invert_sig, invert, env); + // Opacity Functions + register_function(ctx, alpha_sig, alpha, env); + register_function(ctx, opacity_sig, alpha, env); + register_function(ctx, opacify_sig, opacify, env); + register_function(ctx, fade_in_sig, opacify, env); + register_function(ctx, transparentize_sig, transparentize, env); + register_function(ctx, fade_out_sig, transparentize, env); + // Other Color Functions + register_function(ctx, adjust_color_sig, adjust_color, env); + register_function(ctx, scale_color_sig, scale_color, env); + register_function(ctx, change_color_sig, change_color, env); + register_function(ctx, ie_hex_str_sig, ie_hex_str, env); + // String Functions + register_function(ctx, unquote_sig, sass_unquote, env); + register_function(ctx, quote_sig, sass_quote, env); + register_function(ctx, str_length_sig, str_length, env); + register_function(ctx, str_insert_sig, str_insert, env); + register_function(ctx, str_index_sig, str_index, env); + register_function(ctx, str_slice_sig, str_slice, env); + register_function(ctx, to_upper_case_sig, to_upper_case, env); + register_function(ctx, to_lower_case_sig, to_lower_case, env); + // Number Functions + register_function(ctx, percentage_sig, percentage, env); + register_function(ctx, round_sig, round, env); + register_function(ctx, ceil_sig, ceil, env); + register_function(ctx, floor_sig, floor, env); + register_function(ctx, abs_sig, abs, env); + register_function(ctx, min_sig, min, env); + register_function(ctx, max_sig, max, env); + register_function(ctx, random_sig, random, env); + // List Functions + register_function(ctx, length_sig, length, env); + register_function(ctx, nth_sig, nth, env); + register_function(ctx, set_nth_sig, set_nth, env); + register_function(ctx, index_sig, index, env); + register_function(ctx, join_sig, join, env); + register_function(ctx, append_sig, append, env); + register_function(ctx, zip_sig, zip, env); + register_function(ctx, list_separator_sig, list_separator, env); + // Map Functions + register_function(ctx, map_get_sig, map_get, env); + register_function(ctx, map_merge_sig, map_merge, env); + register_function(ctx, map_remove_sig, map_remove, env); + register_function(ctx, map_keys_sig, map_keys, env); + register_function(ctx, map_values_sig, map_values, env); + register_function(ctx, map_has_key_sig, map_has_key, env); + register_function(ctx, keywords_sig, keywords, env); + // Introspection Functions + register_function(ctx, type_of_sig, type_of, env); + register_function(ctx, unit_sig, unit, env); + register_function(ctx, unitless_sig, unitless, env); + register_function(ctx, comparable_sig, comparable, env); + register_function(ctx, variable_exists_sig, variable_exists, env); + register_function(ctx, global_variable_exists_sig, global_variable_exists, env); + register_function(ctx, function_exists_sig, function_exists, env); + register_function(ctx, mixin_exists_sig, mixin_exists, env); + register_function(ctx, feature_exists_sig, feature_exists, env); + register_function(ctx, call_sig, call, env); + // Boolean Functions + register_function(ctx, not_sig, sass_not, env); + register_function(ctx, if_sig, sass_if, env); + // Misc Functions + register_function(ctx, inspect_sig, inspect, env); + register_function(ctx, unique_id_sig, unique_id, env); + // Selector functions + register_function(ctx, selector_nest_sig, selector_nest, env); + register_function(ctx, selector_append_sig, selector_append, env); + register_function(ctx, selector_extend_sig, selector_extend, env); + register_function(ctx, selector_replace_sig, selector_replace, env); + register_function(ctx, selector_unify_sig, selector_unify, env); + register_function(ctx, is_superselector_sig, is_superselector, env); + register_function(ctx, simple_selectors_sig, simple_selectors, env); + register_function(ctx, selector_parse_sig, selector_parse, env); + } + + void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) + { + while (descrs && *descrs) { + register_c_function(ctx, env, *descrs); + ++descrs; + } + } + void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) + { + Definition_Ptr def = make_c_function(descr, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + +} diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp new file mode 100755 index 000000000..86c2e87cb --- /dev/null +++ b/src/libsass/src/context.hpp @@ -0,0 +1,145 @@ +#ifndef SASS_CONTEXT_H +#define SASS_CONTEXT_H + +#include +#include +#include + +#define BUFFERSIZE 255 +#include "b64/encode.h" + +#include "ast_fwd_decl.hpp" +#include "kwd_arg_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "sass_context.hpp" +#include "environment.hpp" +#include "source_map.hpp" +#include "subset_map.hpp" +#include "output.hpp" +#include "plugins.hpp" +#include "file.hpp" + + +struct Sass_Function; + +namespace Sass { + + class Context { + public: + void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); + bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; + bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; + + private: + bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); + + public: + const std::string CWD; + struct Sass_Options& c_options; + std::string entry_path; + size_t head_imports; + Plugins plugins; + Output emitter; + + // resources add under our control + // these are guaranteed to be freed + std::vector strings; + std::vector resources; + std::map sheets; + Subset_Map subset_map; + std::vector import_stack; + + struct Sass_Compiler* c_compiler; + + // absolute paths to includes + std::vector included_files; + // relative includes for sourcemap + std::vector srcmap_links; + // vectors above have same size + + std::vector plugin_paths; // relative paths to load plugins + std::vector include_paths; // lookup paths for includes + + + + + + void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); + + std::vector c_headers; + std::vector c_importers; + std::vector c_functions; + + void add_c_header(Sass_Importer_Entry header); + void add_c_importer(Sass_Importer_Entry importer); + void add_c_function(Sass_Function_Entry function); + + const std::string indent; // String to be used for indentation + const std::string linefeed; // String to be used for line feeds + const std::string input_path; // for relative paths in src-map + const std::string output_path; // for relative paths to the output + const std::string source_map_file; // path to source map file (enables feature) + const std::string source_map_root; // path for sourceRoot property (pass-through) + + virtual ~Context(); + Context(struct Sass_Context&); + virtual Block_Obj parse() = 0; + virtual Block_Obj compile(); + virtual char* render(Block_Obj root); + virtual char* render_srcmap(); + + void register_resource(const Include&, const Resource&, ParserState* = 0); + std::vector find_includes(const Importer& import); + Include load_import(const Importer&, ParserState pstate); + + Sass_Output_Style output_style() { return c_options.output_style; }; + std::vector get_included_files(bool skip = false, size_t headers = 0); + + private: + void collect_plugin_paths(const char* paths_str); + void collect_plugin_paths(string_list* paths_array); + void collect_include_paths(const char* paths_str); + void collect_include_paths(string_list* paths_array); + std::string format_embedded_source_map(); + std::string format_source_mapping_url(const std::string& out_path); + + + // void register_built_in_functions(Env* env); + // void register_function(Signature sig, Native_Function f, Env* env); + // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); + // void register_overload_stub(std::string name, Env* env); + + public: + const std::string& cwd() { return CWD; }; + }; + + class File_Context : public Context { + public: + File_Context(struct Sass_File_Context& ctx) + : Context(ctx) + { } + virtual ~File_Context(); + virtual Block_Obj parse(); + }; + + class Data_Context : public Context { + public: + char* source_c_str; + char* srcmap_c_str; + Data_Context(struct Sass_Data_Context& ctx) + : Context(ctx) + { + source_c_str = ctx.source_string; + srcmap_c_str = ctx.srcmap_string; + ctx.source_string = 0; // passed away + ctx.srcmap_string = 0; // passed away + } + virtual ~Data_Context(); + virtual Block_Obj parse(); + }; + +} + +#endif diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp new file mode 100755 index 000000000..63e6a7d58 --- /dev/null +++ b/src/libsass/src/cssize.cpp @@ -0,0 +1,595 @@ +#include "sass.hpp" +#include +#include +#include + +#include "cssize.hpp" +#include "context.hpp" +#include "backtrace.hpp" + +namespace Sass { + + Cssize::Cssize(Context& ctx, Backtrace* bt) + : ctx(ctx), + block_stack(std::vector()), + p_stack(std::vector()), + backtrace(bt) + { } + + Statement_Ptr Cssize::parent() + { + return p_stack.size() ? p_stack.back() : block_stack.front(); + } + + Block_Ptr Cssize::operator()(Block_Ptr b) + { + Block_Ptr bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); + // bb->tabs(b->tabs()); + block_stack.push_back(bb); + append_block(b, bb); + block_stack.pop_back(); + return bb; + } + + Statement_Ptr Cssize::operator()(Trace_Ptr t) + { + return t->block()->perform(this); + } + + Statement_Ptr Cssize::operator()(Declaration_Ptr d) + { + String_Obj property = SASS_MEMORY_CAST(String, d->property()); + + if (Declaration_Ptr dd = dynamic_cast(parent())) { + String_Obj parent_property = SASS_MEMORY_CAST(String, dd->property()); + property = SASS_MEMORY_NEW(String_Constant, + d->property()->pstate(), + parent_property->to_string() + "-" + property->to_string()); + if (!dd->value()) { + d->tabs(dd->tabs() + 1); + } + } + + Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, + d->pstate(), + property, + d->value(), + d->is_important()); + dd->is_indented(d->is_indented()); + dd->tabs(d->tabs()); + + p_stack.push_back(&dd); + Block_Obj bb = d->block() ? operator()(&d->block()) : NULL; + p_stack.pop_back(); + + if (bb && bb->length()) { + if (dd->value() && !dd->value()->is_invisible()) { + bb->unshift(&dd); + } + return bb.detach(); + } + else if (dd->value() && !dd->value()->is_invisible()) { + return dd.detach(); + } + + return 0; + } + + Statement_Ptr Cssize::operator()(Directive_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + if (parent()->statement_type() == Statement::RULESET) + { + return (r->is_keyframes()) ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); + } + + p_stack.push_back(r); + Directive_Obj rr = SASS_MEMORY_NEW(Directive, + r->pstate(), + r->keyword(), + r->selector(), + r->block() ? operator()(&r->block()) : 0); + if (r->value()) rr->value(r->value()); + p_stack.pop_back(); + + bool directive_exists = false; + size_t L = rr->block() ? rr->block()->length() : 0; + for (size_t i = 0; i < L && !directive_exists; ++i) { + Statement_Obj s = r->block()->at(i); + if (s->statement_type() != Statement::BUBBLE) directive_exists = true; + else { + Bubble_Obj s_obj = SASS_MEMORY_CAST(Bubble, s); + s = s_obj->node(); + if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; + else directive_exists = (static_cast(&s)->keyword() == rr->keyword()); + } + + } + + Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); + if (!(directive_exists || rr->is_keyframes())) + { + Directive_Ptr empty_node = SASS_MEMORY_CAST(Directive, rr); + empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); + result->append(empty_node); + } + + Block_Obj ss = debubble(rr->block() ? &rr->block() : SASS_MEMORY_NEW(Block, rr->pstate()), &rr); + for (size_t i = 0, L = ss->length(); i < L; ++i) { + result->append(ss->at(i)); + } + + return result; + } + + Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, + r->pstate(), + operator()(&r->block())); + if (&r->name()) rr->name(r->name()); + + return debubble(&rr->block(), &rr); + } + + Statement_Ptr Cssize::operator()(Ruleset_Ptr r) + { + p_stack.push_back(r); + // this can return a string schema + // string schema is not a statement! + // r->block() is already a string schema + // and that is comming from propset expand + Block_Ptr bb = operator()(&r->block()); + // this should protect us (at least a bit) from our mess + // fixing this properly is harder that it should be ... + if (dynamic_cast(bb) == NULL) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); + } + Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + r->selector(), + bb); + + rr->is_root(r->is_root()); + // rr->tabs(r->block()->tabs()); + p_stack.pop_back(); + + if (!rr->block()) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); + } + + Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + for (size_t i = 0, L = rr->block()->length(); i < L; i++) + { + Statement_Ptr s = &rr->block()->at(i); + if (bubblable(s)) rules->append(s); + if (!bubblable(s)) props->append(s); + } + + if (props->length()) + { + Block_Obj bb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + bb->concat(&props); + rr->block(bb); + + for (size_t i = 0, L = rules->length(); i < L; i++) + { + Statement_Ptr stm = &rules->at(i); + stm->tabs(stm->tabs() + 1); + } + + rules->unshift(&rr); + } + + Block_Ptr ptr = rules; + rules = debubble(rules); + void* lp = ptr; + void* rp = rules; + if (lp != rp) { + Block_Obj obj = ptr; + } + + if (!(!rules->length() || + !bubblable(&rules->last()) || + parent()->statement_type() == Statement::RULESET)) + { + rules->last()->group_end(true); + } + return rules; + } + + Statement_Ptr Cssize::operator()(Null_Ptr m) + { + return 0; + } + + Statement_Ptr Cssize::operator()(Media_Block_Ptr m) + { + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + if (parent()->statement_type() == Statement::MEDIA) + { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } + + p_stack.push_back(m); + + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + &m->media_queries(), + operator()(&m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(&mm->block(), &mm); + } + + Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) + { + if (!m->block()->length()) + { return m; } + + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + p_stack.push_back(m); + + Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + &m->condition(), + operator()(&m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(&mm->block(), &mm); + } + + Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) + { + bool tmp = false; + for (size_t i = 0, L = p_stack.size(); i < L; ++i) { + Statement_Ptr s = p_stack[i]; + tmp |= m->exclude_node(s); + } + + if (!tmp) + { + Block_Ptr bb = operator()(&m->block()); + for (size_t i = 0, L = bb->length(); i < L; ++i) { + // (bb->elements())[i]->tabs(m->tabs()); + Statement_Obj stm = bb->at(i); + if (bubblable(&stm)) stm->tabs(stm->tabs() + m->tabs()); + } + if (bb->length() && bubblable(&bb->last())) bb->last()->group_end(m->group_end()); + return bb; + } + + if (m->exclude_node(parent())) + { + return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + } + + return bubble(m); + } + + Statement_Ptr Cssize::bubble(Directive_Ptr m) + { + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = static_cast(SASS_MEMORY_COPY(this->parent())); + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(&m->block()); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); + wrapper_block->append(&new_rule); + Directive_Obj mm = SASS_MEMORY_NEW(Directive, + m->pstate(), + m->keyword(), + m->selector(), + wrapper_block); + if (m->value()) mm->value(m->value()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), &mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) + { + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = static_cast(SASS_MEMORY_COPY(this->parent())); + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(&m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(&new_rule); + At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, + m->pstate(), + wrapper_block, + m->expression()); + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) + { + Ruleset_Obj parent = static_cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(&m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + &m->condition(), + wrapper_block); + + mm->tabs(m->tabs()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Media_Block_Ptr m) + { + Ruleset_Obj parent = static_cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(&m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + &m->media_queries(), + wrapper_block, + 0); + + mm->tabs(m->tabs()); + + return SASS_MEMORY_NEW(Bubble, mm->pstate(), &mm); + } + + bool Cssize::bubblable(Statement_Ptr s) + { + return dynamic_cast(s) || s->bubbles(); + } + + Block_Ptr Cssize::flatten(Block_Ptr b) + { + Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr ss = &b->at(i); + if (Block_Ptr bb = SASS_MEMORY_CAST_PTR(Block, ss)) { + Block_Obj bs = flatten(bb); + for (size_t j = 0, K = bs->length(); j < K; ++j) { + result->append(bs->at(j)); + } + } + else { + result->append(ss); + } + } + return result; + } + + std::vector> Cssize::slice_by_bubble(Block_Ptr b) + { + std::vector> results; + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj value = b->at(i); + bool key = dynamic_cast(&value) != NULL; + + if (!results.empty() && results.back().first == key) + { + Block_Obj wrapper_block = results.back().second; + wrapper_block->append(value); + } + else + { + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); + wrapper_block->append(value); + results.push_back(std::make_pair(key, wrapper_block)); + } + } + return results; + } + + Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) + { + Has_Block_Obj previous_parent = 0; + std::vector> baz = slice_by_bubble(children); + Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); + + for (size_t i = 0, L = baz.size(); i < L; ++i) { + bool is_bubble = baz[i].first; + Block_Obj slice = baz[i].second; + + if (!is_bubble) { + if (!parent) { + result->append(&slice); + } + else if (previous_parent) { + previous_parent->block()->concat(&slice); + } + else { + previous_parent = static_cast(SASS_MEMORY_COPY(parent)); + previous_parent->block(slice); + previous_parent->tabs(parent->tabs()); + + result->append(&previous_parent); + } + continue; + } + + for (size_t j = 0, K = slice->length(); j < K; ++j) + { + Statement_Ptr ss = NULL; + Statement_Obj stm = slice->at(j); + // this has to go now here (too bad) + Bubble_Obj node = SASS_MEMORY_CAST(Bubble, stm); + Media_Block_Ptr m1 = NULL; + Media_Block_Ptr m2 = NULL; + if (parent) m1 = SASS_MEMORY_CAST(Media_Block, *parent); + if (node) m2 = SASS_MEMORY_CAST(Media_Block, node->node()); + if (!parent || + parent->statement_type() != Statement::MEDIA || + node->node()->statement_type() != Statement::MEDIA || + (m1 && m2 && &m1->media_queries() == &m2->media_queries()) + ) + { + ss = &node->node(); + } + else + { + List_Obj mq = merge_media_queries(static_cast(&node->node()), static_cast(parent)); + if (!mq->length()) continue; + static_cast(&node->node())->media_queries(mq); + ss = &node->node(); + } + + if (!ss) continue; + + ss->tabs(ss->tabs() + node->tabs()); + ss->group_end(node->group_end()); + + if (!ss) continue; + + Block_Obj bb = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + bb->append(ss->perform(this)); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + + Block_Ptr wrapper = flatten(&bb); + wrapper_block->append(wrapper); + + if (wrapper->length()) { + previous_parent = NULL; + } + + if (wrapper_block) { + result->append(&wrapper_block); + } + } + } + + return flatten(&result); + } + + Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + void Cssize::append_block(Block_Ptr b, Block_Ptr cur) + { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj ith = b->at(i)->perform(this); + if (Block_Ptr bb = SASS_MEMORY_CAST(Block, ith)) { + for (size_t j = 0, K = bb->length(); j < K; ++j) { + cur->append(bb->at(j)); + } + } + else if (ith) { + cur->append(ith); + } + } + } + + List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) + { + List_Ptr qq = SASS_MEMORY_NEW(List, + m1->media_queries()->pstate(), + m1->media_queries()->length(), + SASS_COMMA); + + for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { + for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { + Expression_Obj l1 = m1->media_queries()->at(i); + Expression_Obj l2 = m2->media_queries()->at(j); + Media_Query_Ptr mq1 = SASS_MEMORY_CAST(Media_Query, l1); + Media_Query_Ptr mq2 = SASS_MEMORY_CAST(Media_Query, l2); + Media_Query_Ptr mq = merge_media_query(mq1, mq2); + if (mq) qq->append(mq); + } + } + + return qq; + } + + + Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) + { + + std::string type; + std::string mod; + + std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + std::string t1 = &mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; + std::string m2 = std::string(mq2->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + std::string t2 = &mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; + + + if (t1.empty()) t1 = t2; + if (t2.empty()) t2 = t1; + + if ((m1 == "not") ^ (m2 == "not")) { + if (t1 == t2) { + return 0; + } + type = m1 == "not" ? t2 : t1; + mod = m1 == "not" ? m2 : m1; + } + else if (m1 == "not" && m2 == "not") { + if (t1 != t2) { + return 0; + } + type = t1; + mod = "not"; + } + else if (t1 != t2) { + return 0; + } else { + type = t1; + mod = m1.empty() ? m2 : m1; + } + + Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, + +mq1->pstate(), 0, +mq1->length() + mq2->length(), mod == "not", mod == "only" +); + + if (!type.empty()) { + mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); + } + + mm->concat(mq2); + mm->concat(mq1); + + return mm; + } +} diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp new file mode 100755 index 000000000..20b79668f --- /dev/null +++ b/src/libsass/src/cssize.hpp @@ -0,0 +1,78 @@ +#ifndef SASS_CSSIZE_H +#define SASS_CSSIZE_H + +#include "ast.hpp" +#include "context.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + typedef Environment Env; + struct Backtrace; + + class Cssize : public Operation_CRTP { + + Context& ctx; + std::vector block_stack; + std::vector p_stack; + Backtrace* backtrace; + + Statement_Ptr fallback_impl(AST_Node_Ptr n); + + public: + Cssize(Context&, Backtrace*); + ~Cssize() { } + + Selector_List_Ptr selector(); + + Block_Ptr operator()(Block_Ptr); + Statement_Ptr operator()(Ruleset_Ptr); + // Statement_Ptr operator()(Bubble_Ptr); + Statement_Ptr operator()(Media_Block_Ptr); + Statement_Ptr operator()(Supports_Block_Ptr); + Statement_Ptr operator()(At_Root_Block_Ptr); + Statement_Ptr operator()(Directive_Ptr); + Statement_Ptr operator()(Keyframe_Rule_Ptr); + Statement_Ptr operator()(Trace_Ptr); + Statement_Ptr operator()(Declaration_Ptr); + // Statement_Ptr operator()(Assignment_Ptr); + // Statement_Ptr operator()(Import_Ptr); + // Statement_Ptr operator()(Import_Stub_Ptr); + // Statement_Ptr operator()(Warning_Ptr); + // Statement_Ptr operator()(Error_Ptr); + // Statement_Ptr operator()(Comment_Ptr); + // Statement_Ptr operator()(If_Ptr); + // Statement_Ptr operator()(For_Ptr); + // Statement_Ptr operator()(Each_Ptr); + // Statement_Ptr operator()(While_Ptr); + // Statement_Ptr operator()(Return_Ptr); + // Statement_Ptr operator()(Extension_Ptr); + // Statement_Ptr operator()(Definition_Ptr); + // Statement_Ptr operator()(Mixin_Call_Ptr); + // Statement_Ptr operator()(Content_Ptr); + Statement_Ptr operator()(Null_Ptr); + + Statement_Ptr parent(); + std::vector> slice_by_bubble(Block_Ptr); + Statement_Ptr bubble(Directive_Ptr); + Statement_Ptr bubble(At_Root_Block_Ptr); + Statement_Ptr bubble(Media_Block_Ptr); + Statement_Ptr bubble(Supports_Block_Ptr); + + Block_Ptr debubble(Block_Ptr children, Statement_Ptr parent = 0); + Block_Ptr flatten(Block_Ptr); + bool bubblable(Statement_Ptr); + + List_Ptr merge_media_queries(Media_Block_Ptr, Media_Block_Ptr); + Media_Query_Ptr merge_media_query(Media_Query_Ptr, Media_Query_Ptr); + + template + Statement_Ptr fallback(U x) { return fallback_impl(x); } + + void append_block(Block_Ptr, Block_Ptr); + }; + +} + +#endif diff --git a/src/libsass/src/debug.hpp b/src/libsass/src/debug.hpp new file mode 100755 index 000000000..43fe05e67 --- /dev/null +++ b/src/libsass/src/debug.hpp @@ -0,0 +1,43 @@ +#ifndef SASS_DEBUG_H +#define SASS_DEBUG_H + +#include + +#ifndef UINT32_MAX + #define UINT32_MAX 0xffffffffU +#endif + +enum dbg_lvl_t : uint32_t { + NONE = 0, + TRIM = 1, + CHUNKS = 2, + SUBWEAVE = 4, + WEAVE = 8, + EXTEND_COMPOUND = 16, + EXTEND_COMPLEX = 32, + LCS = 64, + EXTEND_OBJECT = 128, + ALL = UINT32_MAX +}; + +#ifdef DEBUG + +#ifndef DEBUG_LVL +const uint32_t debug_lvl = UINT32_MAX; +#else +const uint32_t debug_lvl = (DEBUG_LVL); +#endif // DEBUG_LVL + +#define DEBUG_PRINT(lvl, x) if((lvl) & debug_lvl) { std::cerr << x; } +#define DEBUG_PRINTLN(lvl, x) if((lvl) & debug_lvl) { std::cerr << x << std::endl; } +#define DEBUG_EXEC(lvl, x) if((lvl) & debug_lvl) { x; } + +#else // DEBUG + +#define DEBUG_PRINT(lvl, x) +#define DEBUG_PRINTLN(lvl, x) +#define DEBUG_EXEC(lvl, x) + +#endif // DEBUG + +#endif // SASS_DEBUG diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp new file mode 100755 index 000000000..9d38dd2ce --- /dev/null +++ b/src/libsass/src/debugger.hpp @@ -0,0 +1,798 @@ +#ifndef SASS_DEBUGGER_H +#define SASS_DEBUGGER_H + +#include +#include +#include "node.hpp" +#include "ast_fwd_decl.hpp" + +using namespace Sass; + +inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); + +inline void debug_sources_set(SourcesSet& set, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : set) { + debug_ast(&pair, ind + ""); + // debug_ast(set[pair], ind + "first: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) +{ + size_t pos = 0; + while((pos = str.find(oldStr, pos)) != std::string::npos) + { + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return str; +} + +inline std::string prettyprint(const std::string& str) { + std::string clean = str_replace(str, "\n", "\\n"); + clean = str_replace(clean, " ", "\\t"); + clean = str_replace(clean, "\r", "\\r"); + return clean; +} + +inline std::string longToHex(long long t) { + std::stringstream is; + is << std::hex << t; + return is.str(); +} + +inline std::string pstate_source_position(AST_Node_Ptr node) +{ + std::stringstream str; + Position start(node->pstate()); + Position end(start + node->pstate().offset); + str << (start.file == std::string::npos ? -1 : start.file) + << "@[" << start.line << ":" << start.column << "]" + << "-[" << end.line << ":" << end.column << "]"; +#ifdef DEBUG_SHARED_PTR + str << "x" << node->getRefCount() << "" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); +#endif + return str.str(); +} + +inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) +{ + if (node == 0) return; + if (ind == "") std::cerr << "####################################################################\n"; + if (dynamic_cast(node)) { + Bubble_Ptr bubble = dynamic_cast(node); + std::cerr << ind << "Bubble " << bubble; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << bubble->tabs(); + std::cerr << std::endl; + debug_ast(&bubble->node(), ind + " ", env); + } else if (dynamic_cast(node)) { + Trace_Ptr trace = dynamic_cast(node); + std::cerr << ind << "Trace " << trace; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [name:" << trace->name() << "]" + << std::endl; + debug_ast(&trace->block(), ind + " ", env); + } else if (dynamic_cast(node)) { + At_Root_Block_Ptr root_block = dynamic_cast(node); + std::cerr << ind << "At_Root_Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << root_block->tabs(); + std::cerr << std::endl; + debug_ast(&root_block->expression(), ind + ":", env); + debug_ast(&root_block->block(), ind + " ", env); + } else if (dynamic_cast(node)) { + Selector_List_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Selector_List " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); + std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + + for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(&i, ind + " ", env); } + +// } else if (dynamic_cast(node)) { +// Expression_Ptr expression = dynamic_cast(node); +// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; + + } else if (dynamic_cast(node)) { + Parent_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Parent_Selector " << selector; +// if (selector->not_selector()) cerr << " [in_declaration]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; +// debug_ast(selector->selector(), ind + "->", env); + + } else if (dynamic_cast(node)) { + Complex_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Complex_Selector " << selector + << " (" << pstate_source_position(node) << ")" + << " <" << selector->hash() << ">" + << " [length:" << longToHex(selector->length()) << "]" + << " [weight:" << longToHex(selector->specificity()) << "]" + << " [@media:" << selector->media_block() << "]" + << (selector->is_invisible() ? " [INVISIBLE]": " -") + << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_parent_ref() ? " [has parent]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << " -- "; + std::string del; + switch (selector->combinator()) { + case Complex_Selector::PARENT_OF: del = ">"; break; + case Complex_Selector::PRECEDES: del = "~"; break; + case Complex_Selector::ADJACENT_TO: del = "+"; break; + case Complex_Selector::ANCESTOR_OF: del = " "; break; + case Complex_Selector::REFERENCE: del = "//"; break; + } + // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(&selector->head(), ind + " " /* + "[" + del + "]" */, env); + if (selector->tail()) { + debug_ast(&selector->tail(), ind + "{" + del + "}", env); + } else if(del != " ") { + std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; + } + SourcesSet set = selector->sources(); + // debug_sources_set(set, ind + " @--> "); + } else if (dynamic_cast(node)) { + Compound_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Compound_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->extended() ? " [extended]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Wrapped_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Wrapped_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(&selector->selector(), ind + " () ", env); + } else if (dynamic_cast(node)) { + Pseudo_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Pseudo_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(&selector->expression(), ind + " <= ", env); + } else if (dynamic_cast(node)) { + Attribute_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Attribute_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(&selector->value(), ind + "[" + selector->matcher() + "] ", env); + } else if (dynamic_cast(node)) { + Class_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Class_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (dynamic_cast(node)) { + Id_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Id_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (dynamic_cast(node)) { + Element_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Element_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; + std::cerr << std::endl; + } else if (dynamic_cast(node)) { + + Placeholder_Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; + std::cerr << " (" << pstate_source_position(selector) << ")" + << " <" << selector->hash() << ">" + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (dynamic_cast(node)) { + Simple_Selector* selector = dynamic_cast(node); + std::cerr << ind << "Simple_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; + + } else if (dynamic_cast(node)) { + Selector_Schema_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Selector_Schema " << selector; + std::cerr << " (" << pstate_source_position(node) << ")" + << (selector->at_root() && selector->at_root() ? " [@ROOT]" : "") + << " [@media:" << selector->media_block() << "]" + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + debug_ast(&selector->contents(), ind + " "); + // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (dynamic_cast(node)) { + Selector_Ptr selector = dynamic_cast(node); + std::cerr << ind << "Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (dynamic_cast(node)) { + Media_Query_Expression_Ptr block = dynamic_cast(node); + std::cerr << ind << "Media_Query_Expression " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") + << std::endl; + debug_ast(&block->feature(), ind + " feature) "); + debug_ast(&block->value(), ind + " value) "); + + } else if (dynamic_cast(node)) { + Media_Query_Ptr block = dynamic_cast(node); + std::cerr << ind << "Media_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_negated() ? " [is_negated]": " -") + << (block->is_restricted() ? " [is_restricted]": " -") + << std::endl; + debug_ast(&block->media_type(), ind + " "); + for(const auto& i : block->elements()) { debug_ast(&i, ind + " ", env); } + + } else if (dynamic_cast(node)) { + Media_Block_Ptr block = dynamic_cast(node); + std::cerr << ind << "Media_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->media_queries(), ind + " =@ "); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Supports_Block_Ptr block = dynamic_cast(node); + std::cerr << ind << "Supports_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->condition(), ind + " =@ "); + debug_ast(&block->block(), ind + " <>"); + } else if (dynamic_cast(node)) { + Supports_Operator_Ptr block = dynamic_cast(node); + std::cerr << ind << "Supports_Operator " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(&block->left(), ind + " left) "); + debug_ast(&block->right(), ind + " right) "); + } else if (dynamic_cast(node)) { + Supports_Negation_Ptr block = dynamic_cast(node); + std::cerr << ind << "Supports_Negation " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(&block->condition(), ind + " condition) "); + } else if (dynamic_cast(node)) { + At_Root_Query_Ptr block = dynamic_cast(node); + std::cerr << ind << "At_Root_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(&block->feature(), ind + " feature) "); + debug_ast(&block->value(), ind + " value) "); + } else if (dynamic_cast(node)) { + Supports_Declaration_Ptr block = dynamic_cast(node); + std::cerr << ind << "Supports_Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(&block->feature(), ind + " feature) "); + debug_ast(&block->value(), ind + " value) "); + } else if (dynamic_cast(node)) { + Block_Ptr root_block = dynamic_cast(node); + std::cerr << ind << "Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (root_block->is_root()) std::cerr << " [root]"; + std::cerr << " " << root_block->tabs() << std::endl; + for(const Statement_Obj& i : root_block->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Warning_Ptr block = dynamic_cast(node); + std::cerr << ind << "Warning " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->message(), ind + " : "); + } else if (dynamic_cast(node)) { + Error_Ptr block = dynamic_cast(node); + std::cerr << ind << "Error " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (dynamic_cast(node)) { + Debug_Ptr block = dynamic_cast(node); + std::cerr << ind << "Debug " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->value(), ind + " "); + } else if (dynamic_cast(node)) { + Comment_Ptr block = dynamic_cast(node); + std::cerr << ind << "Comment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << + " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(&block->text(), ind + "// ", env); + } else if (dynamic_cast(node)) { + If_Ptr block = dynamic_cast(node); + std::cerr << ind << "If " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->predicate(), ind + " = "); + debug_ast(&block->block(), ind + " <>"); + debug_ast(&block->alternative(), ind + " ><"); + } else if (dynamic_cast(node)) { + Return_Ptr block = dynamic_cast(node); + std::cerr << ind << "Return " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (dynamic_cast(node)) { + Extension_Ptr block = dynamic_cast(node); + std::cerr << ind << "Extension " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->selector(), ind + "-> ", env); + } else if (dynamic_cast(node)) { + Content_Ptr block = dynamic_cast(node); + std::cerr << ind << "Content " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << block->media_block() << "]"; + std::cerr << " " << block->tabs() << std::endl; + } else if (dynamic_cast(node)) { + Import_Stub_Ptr block = dynamic_cast(node); + std::cerr << ind << "Import_Stub " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->imp_path() << "] "; + std::cerr << " " << block->tabs() << std::endl; + } else if (dynamic_cast(node)) { + Import_Ptr block = dynamic_cast(node); + std::cerr << ind << "Import " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + // std::vector files_; + for (auto imp : block->urls()) debug_ast(&imp, ind + "@: ", env); + debug_ast(&block->import_queries(), ind + "@@ "); + } else if (dynamic_cast(node)) { + Assignment_Ptr block = dynamic_cast(node); + std::cerr << ind << "Assignment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; + debug_ast(&block->value(), ind + "=", env); + } else if (dynamic_cast(node)) { + Declaration_Ptr block = dynamic_cast(node); + std::cerr << ind << "Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->property(), ind + " prop: ", env); + debug_ast(&block->value(), ind + " value: ", env); + debug_ast(&block->block(), ind + " ", env); + } else if (dynamic_cast(node)) { + Keyframe_Rule_Ptr has_block = dynamic_cast(node); + std::cerr << ind << "Keyframe_Rule " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->name()) debug_ast(&has_block->name(), ind + "@"); + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Directive_Ptr block = dynamic_cast(node); + std::cerr << ind << "Directive " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; + debug_ast(&block->selector(), ind + "~", env); + debug_ast(&block->value(), ind + "+", env); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Each_Ptr block = dynamic_cast(node); + std::cerr << ind << "Each " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + For_Ptr block = dynamic_cast(node); + std::cerr << ind << "For " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + While_Ptr block = dynamic_cast(node); + std::cerr << ind << "While " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Definition_Ptr block = dynamic_cast(node); + std::cerr << ind << "Definition " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << block->name() << "] "; + std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; + // this seems to lead to segfaults some times? + // std::cerr << " [signature: " << block->signature() << "] "; + std::cerr << " [native: " << block->native_function() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(&block->parameters(), ind + " params: ", env); + if (block->block()) debug_ast(&block->block(), ind + " ", env); + } else if (dynamic_cast(node)) { + Mixin_Call_Ptr block = dynamic_cast(node); + std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); + std::cerr << " (" << pstate_source_position(block) << ")"; + std::cerr << " [" << block->name() << "]"; + std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; + debug_ast(&block->arguments(), ind + " args: "); + if (block->block()) debug_ast(&block->block(), ind + " ", env); + } else if (Ruleset_Ptr ruleset = dynamic_cast(node)) { + std::cerr << ind << "Ruleset " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << (ruleset->at_root() ? " [@ROOT]" : ""); + std::cerr << (ruleset->is_root() ? " [root]" : ""); + std::cerr << std::endl; + debug_ast(&ruleset->selector(), ind + ">"); + debug_ast(&ruleset->block(), ind + " "); + } else if (dynamic_cast(node)) { + Block_Ptr block = dynamic_cast(node); + std::cerr << ind << "Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << " [indent: " << block->tabs() << "]" << std::endl; + for(const Statement_Obj& i : block->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Textual_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Textual " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->type() == Textual::NUMBER) std::cerr << " [NUMBER]"; + else if (expression->type() == Textual::PERCENTAGE) std::cerr << " [PERCENTAGE]"; + else if (expression->type() == Textual::DIMENSION) std::cerr << " [DIMENSION]"; + else if (expression->type() == Textual::HEX) std::cerr << " [HEX]"; + std::cerr << " [" << expression->value() << "]"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + if (expression->is_delayed()) std::cerr << " [delayed]"; + std::cerr << std::endl; + } else if (dynamic_cast(node)) { + Variable_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Variable " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]" << std::endl; + std::string name(expression->name()); + if (env && env->has(name)) debug_ast(SASS_MEMORY_CAST(Expression, (*env)[name]), ind + " -> ", env); + } else if (dynamic_cast(node)) { + Function_Call_Schema_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Function_Call_Schema " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << "" << std::endl; + debug_ast(&expression->name(), ind + "name: ", env); + debug_ast(&expression->arguments(), ind + " args: ", env); + } else if (dynamic_cast(node)) { + Function_Call_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Function_Call " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << std::endl; + debug_ast(&expression->arguments(), ind + " args: ", env); + } else if (dynamic_cast(node)) { + Arguments_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Arguments " << expression; + if (expression->is_delayed()) std::cerr << " [delayed]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; + if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; + if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; + std::cerr << std::endl; + for(const Argument_Obj& i : expression->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Argument_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Argument " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->value() << "]"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [rest: " << expression->is_rest_argument() << "] "; + std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; + debug_ast(&expression->value(), ind + " value: ", env); + } else if (dynamic_cast(node)) { + Parameters_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Parameters " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; + std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; + std::cerr << std::endl; + for(const Parameter_Obj& i : expression->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Parameter_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Parameter " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [default: " << expression->default_value() << "] "; + std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; + } else if (dynamic_cast(node)) { + Unary_Expression_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Unary_Expression " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type() << "]" << std::endl; + debug_ast(&expression->operand(), ind + " operand: ", env); + } else if (dynamic_cast(node)) { + Binary_Expression_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Binary_Expression " << expression; + if (expression->is_interpolant()) std::cerr << " [is interpolant] "; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [ws_before: " << expression->op().ws_before << "] "; + std::cerr << " [ws_after: " << expression->op().ws_after << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type_name() << "]" << std::endl; + debug_ast(&expression->left(), ind + " left: ", env); + debug_ast(&expression->right(), ind + " right: ", env); + } else if (dynamic_cast(node)) { + Map_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Map " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [Hashed]" << std::endl; + for (const auto& i : expression->elements()) { + debug_ast(&i.first, ind + " key: "); + debug_ast(&i.second, ind + " val: "); + } + } else if (dynamic_cast(node)) { + List_Ptr expression = dynamic_cast(node); + std::cerr << ind << "List " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->length() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map" : "Space ") << + " [delayed: " << expression->is_delayed() << "] " << + " [interpolant: " << expression->is_interpolant() << "] " << + " [listized: " << expression->from_selector() << "] " << + " [arglist: " << expression->is_arglist() << "] " << + " [hash: " << expression->hash() << "] " << + std::endl; + for(const auto& i : expression->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Content_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Content " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << expression->media_block() << "]"; + std::cerr << " [Statement]" << std::endl; + } else if (dynamic_cast(node)) { + Boolean_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Boolean " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << "]" << std::endl; + } else if (dynamic_cast(node)) { + Color_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Color " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } else if (dynamic_cast(node)) { + Number_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Number " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << expression->unit() << "]" << + " [hash: " << expression->hash() << "] " << + std::endl; + } else if (dynamic_cast(node)) { + Null_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Null " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] " + // " [hash: " << expression->hash() << "] " + << std::endl; + } else if (dynamic_cast(node)) { + String_Quoted_Ptr expression = dynamic_cast(node); + std::cerr << ind << "String_Quoted " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (dynamic_cast(node)) { + String_Constant_Ptr expression = dynamic_cast(node); + std::cerr << ind << "String_Constant " << expression; + if (expression->concrete_type()) { + std::cerr << " " << expression->concrete_type(); + } + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (dynamic_cast(node)) { + String_Schema_Ptr expression = dynamic_cast(node); + std::cerr << ind << "String_Schema " << expression; + std::cerr << " (" << pstate_source_position(expression) << ")"; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [is interpolant]"; + if (expression->has_interpolant()) std::cerr << " [has interpolant]"; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + for(const auto& i : expression->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + String_Ptr expression = dynamic_cast(node); + std::cerr << ind << "String " << expression; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (dynamic_cast(node)) { + Expression_Ptr expression = dynamic_cast(node); + std::cerr << ind << "Expression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + switch (expression->concrete_type()) { + case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; + case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; + case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; + case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; + case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; + case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; + case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; + case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; + case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; + case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; + case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; + case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; + case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; + } + std::cerr << std::endl; + } else if (dynamic_cast(node)) { + Has_Block_Ptr has_block = dynamic_cast(node); + std::cerr << ind << "Has_Block " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(&i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Statement_Ptr statement = dynamic_cast(node); + std::cerr << ind << "Statement " << statement; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << statement->tabs() << std::endl; + } + + if (ind == "") std::cerr << "####################################################################\n"; +} + +inline void debug_node(Node* node, std::string ind = "") +{ + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; + if (node->isCombinator()) { + std::cerr << ind; + std::cerr << "Combinator "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + switch (node->combinator()) { + case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; + case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; + case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; + case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; + case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; + } + std::cerr << std::endl; + // debug_ast(node->combinator(), ind + " "); + } else if (node->isSelector()) { + std::cerr << ind; + std::cerr << "Selector "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + debug_ast(&node->selector(), ind + " "); + } else if (node->isCollection()) { + std::cerr << ind; + std::cerr << "Collection "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + for(auto n : (*node->collection())) { + debug_node(&n, ind + " "); + } + } else if (node->isNil()) { + std::cerr << ind; + std::cerr << "Nil "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } else { + std::cerr << ind; + std::cerr << "OTHER "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; +} + +/* +inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) +{ + debug_ast(const_cast(node), ind, env); +} +*/ +inline void debug_node(const Node* node, std::string ind = "") +{ + debug_node(const_cast(node), ind); +} + +inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &it : map.values()) { + debug_ast(&it.first, ind + "first: "); + debug_ast(&it.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +typedef std::pair ExtensionPair; +typedef std::vector SubsetMapEntries; + +inline void debug_subset_entries(SubsetMapEntries* entries, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : *entries) { + debug_ast(&pair.first, ind + "first: "); + debug_ast(&pair.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +#endif // SASS_DEBUGGER diff --git a/src/libsass/src/emitter.cpp b/src/libsass/src/emitter.cpp new file mode 100755 index 000000000..f315b840b --- /dev/null +++ b/src/libsass/src/emitter.cpp @@ -0,0 +1,273 @@ +#include "sass.hpp" +#include "util.hpp" +#include "context.hpp" +#include "output.hpp" +#include "emitter.hpp" +#include "utf8_string.hpp" + +namespace Sass { + + Emitter::Emitter(struct Sass_Output_Options& opt) + : wbuf(), + opt(opt), + indentation(0), + scheduled_space(0), + scheduled_linefeed(0), + scheduled_delimiter(false), + scheduled_mapping(0), + in_comment(false), + in_wrapped(false), + in_media_block(false), + in_declaration(false), + in_space_array(false), + in_comma_array(false) + { } + + // return buffer as string + std::string Emitter::get_buffer(void) + { + return wbuf.buffer; + } + + Sass_Output_Style Emitter::output_style(void) const + { + return opt.output_style; + } + + // PROXY METHODS FOR SOURCE MAPS + + void Emitter::add_source_index(size_t idx) + { wbuf.smap.source_index.push_back(idx); } + + std::string Emitter::render_srcmap(Context &ctx) + { return wbuf.smap.render_srcmap(ctx); } + + void Emitter::set_filename(const std::string& str) + { wbuf.smap.file = str; } + + void Emitter::schedule_mapping(const AST_Node_Ptr node) + { scheduled_mapping = node; } + void Emitter::add_open_mapping(const AST_Node_Ptr node) + { wbuf.smap.add_open_mapping(node); } + void Emitter::add_close_mapping(const AST_Node_Ptr node) + { wbuf.smap.add_close_mapping(node); } + ParserState Emitter::remap(const ParserState& pstate) + { return wbuf.smap.remap(pstate); } + + // MAIN BUFFER MANIPULATION + + // add outstanding delimiter + void Emitter::finalize(bool final) + { + scheduled_space = 0; + if (output_style() == SASS_STYLE_COMPRESSED) + if (final) scheduled_delimiter = false; + if (scheduled_linefeed) + scheduled_linefeed = 1; + flush_schedules(); + } + + // flush scheduled space/linefeed + void Emitter::flush_schedules(void) + { + // check the schedule + if (scheduled_linefeed) { + std::string linefeeds = ""; + + for (size_t i = 0; i < scheduled_linefeed; i++) + linefeeds += opt.linefeed; + scheduled_space = 0; + scheduled_linefeed = 0; + append_string(linefeeds); + + } else if (scheduled_space) { + std::string spaces(scheduled_space, ' '); + scheduled_space = 0; + append_string(spaces); + } + if (scheduled_delimiter) { + scheduled_delimiter = false; + append_string(";"); + } + } + + // prepend some text or token to the buffer + void Emitter::prepend_output(const OutputBuffer& output) + { + wbuf.smap.prepend(output); + wbuf.buffer = output.buffer + wbuf.buffer; + } + + // prepend some text or token to the buffer + void Emitter::prepend_string(const std::string& text) + { + wbuf.smap.prepend(Offset(text)); + wbuf.buffer = text + wbuf.buffer; + } + + // append some text or token to the buffer + void Emitter::append_string(const std::string& text) + { + + // write space/lf + flush_schedules(); + + if (in_comment && output_style() == COMPACT) { + // unescape comment nodes + std::string out = comment_to_string(text); + // add to buffer + wbuf.buffer += out; + // account for data in source-maps + wbuf.smap.append(Offset(out)); + } else { + // add to buffer + wbuf.buffer += text; + // account for data in source-maps + wbuf.smap.append(Offset(text)); + } + } + + // append some white-space only text + void Emitter::append_wspace(const std::string& text) + { + if (text.empty()) return; + if (peek_linefeed(text.c_str())) { + scheduled_space = 0; + append_mandatory_linefeed(); + } + } + + // append some text or token to the buffer + // this adds source-mappings for node start and end + void Emitter::append_token(const std::string& text, const AST_Node_Ptr node) + { + flush_schedules(); + add_open_mapping(node); + // hotfix for browser issues + // this is pretty ugly indeed + if (scheduled_mapping) { + add_open_mapping(scheduled_mapping); + scheduled_mapping = 0; + } + append_string(text); + add_close_mapping(node); + } + + // HELPER METHODS + + void Emitter::append_indentation() + { + if (output_style() == COMPRESSED) return; + if (output_style() == COMPACT) return; + if (in_declaration && in_comma_array) return; + if (scheduled_linefeed && indentation) + scheduled_linefeed = 1; + std::string indent = ""; + for (size_t i = 0; i < indentation; i++) + indent += opt.indent; + append_string(indent); + } + + void Emitter::append_delimiter() + { + scheduled_delimiter = true; + if (output_style() == COMPACT) { + if (indentation == 0) { + append_mandatory_linefeed(); + } else { + append_mandatory_space(); + } + } else if (output_style() != COMPRESSED) { + append_optional_linefeed(); + } + } + + void Emitter::append_comma_separator() + { + // scheduled_space = 0; + append_string(","); + append_optional_space(); + } + + void Emitter::append_colon_separator() + { + scheduled_space = 0; + append_string(":"); + append_optional_space(); + } + + void Emitter::append_mandatory_space() + { + scheduled_space = 1; + } + + void Emitter::append_optional_space() + { + if ((output_style() != COMPRESSED) && buffer().size()) { + unsigned char lst = buffer().at(buffer().length() - 1); + if (!isspace(lst) || scheduled_delimiter) { + append_mandatory_space(); + } + } + } + + void Emitter::append_special_linefeed() + { + if (output_style() == COMPACT) { + append_mandatory_linefeed(); + for (size_t p = 0; p < indentation; p++) + append_string(opt.indent); + } + } + + void Emitter::append_optional_linefeed() + { + if (in_declaration && in_comma_array) return; + if (output_style() == COMPACT) { + append_mandatory_space(); + } else { + append_mandatory_linefeed(); + } + } + + void Emitter::append_mandatory_linefeed() + { + if (output_style() != COMPRESSED) { + scheduled_linefeed = 1; + scheduled_space = 0; + // flush_schedules(); + } + } + + void Emitter::append_scope_opener(AST_Node_Ptr node) + { + scheduled_linefeed = 0; + append_optional_space(); + flush_schedules(); + if (node) add_open_mapping(node); + append_string("{"); + append_optional_linefeed(); + // append_optional_space(); + ++ indentation; + } + void Emitter::append_scope_closer(AST_Node_Ptr node) + { + -- indentation; + scheduled_linefeed = 0; + if (output_style() == COMPRESSED) + scheduled_delimiter = false; + if (output_style() == EXPANDED) { + append_optional_linefeed(); + append_indentation(); + } else { + append_optional_space(); + } + append_string("}"); + if (node) add_close_mapping(node); + append_optional_linefeed(); + if (indentation != 0) return; + if (output_style() != COMPRESSED) + scheduled_linefeed = 2; + } + +} diff --git a/src/libsass/src/emitter.hpp b/src/libsass/src/emitter.hpp new file mode 100755 index 000000000..94b2a08bd --- /dev/null +++ b/src/libsass/src/emitter.hpp @@ -0,0 +1,92 @@ +#ifndef SASS_EMITTER_H +#define SASS_EMITTER_H + +#include +#include "sass.hpp" +#include "sass/base.h" +#include "source_map.hpp" +#include "ast_fwd_decl.hpp" + +namespace Sass { + class Context; + + class Emitter { + + public: + Emitter(struct Sass_Output_Options& opt); + virtual ~Emitter() { } + + protected: + OutputBuffer wbuf; + public: + const std::string& buffer(void) { return wbuf.buffer; } + const SourceMap smap(void) { return wbuf.smap; } + const OutputBuffer output(void) { return wbuf; } + // proxy methods for source maps + void add_source_index(size_t idx); + void set_filename(const std::string& str); + void add_open_mapping(const AST_Node_Ptr node); + void add_close_mapping(const AST_Node_Ptr node); + void schedule_mapping(const AST_Node_Ptr node); + std::string render_srcmap(Context &ctx); + ParserState remap(const ParserState& pstate); + + public: + struct Sass_Output_Options& opt; + size_t indentation; + size_t scheduled_space; + size_t scheduled_linefeed; + bool scheduled_delimiter; + AST_Node_Ptr scheduled_mapping; + + public: + // output strings different in comments + bool in_comment; + // selector list does not get linefeeds + bool in_wrapped; + // lists always get a space after delimiter + bool in_media_block; + // nested list must not have parentheses + bool in_declaration; + // nested lists need parentheses + bool in_space_array; + bool in_comma_array; + + public: + // return buffer as std::string + std::string get_buffer(void); + // flush scheduled space/linefeed + Sass_Output_Style output_style(void) const; + // add outstanding linefeed + void finalize(bool final = true); + // flush scheduled space/linefeed + void flush_schedules(void); + // prepend some text or token to the buffer + void prepend_string(const std::string& text); + void prepend_output(const OutputBuffer& out); + // append some text or token to the buffer + void append_string(const std::string& text); + // append some white-space only text + void append_wspace(const std::string& text); + // append some text or token to the buffer + // this adds source-mappings for node start and end + void append_token(const std::string& text, const AST_Node_Ptr node); + + public: // syntax sugar + void append_indentation(); + void append_optional_space(void); + void append_mandatory_space(void); + void append_special_linefeed(void); + void append_optional_linefeed(void); + void append_mandatory_linefeed(void); + void append_scope_opener(AST_Node_Ptr node = 0); + void append_scope_closer(AST_Node_Ptr node = 0); + void append_comma_separator(void); + void append_colon_separator(void); + void append_delimiter(void); + + }; + +} + +#endif diff --git a/src/libsass/src/environment.cpp b/src/libsass/src/environment.cpp new file mode 100755 index 000000000..083a53cc6 --- /dev/null +++ b/src/libsass/src/environment.cpp @@ -0,0 +1,195 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "environment.hpp" + +namespace Sass { + + template + Environment::Environment(bool is_shadow) + : local_frame_(std::map()), + parent_(0), is_shadow_(false) + { } + template + Environment::Environment(Environment* env, bool is_shadow) + : local_frame_(std::map()), + parent_(env), is_shadow_(is_shadow) + { } + template + Environment::Environment(Environment& env, bool is_shadow) + : local_frame_(std::map()), + parent_(&env), is_shadow_(is_shadow) + { } + + // link parent to create a stack + template + void Environment::link(Environment& env) { parent_ = &env; } + template + void Environment::link(Environment* env) { parent_ = env; } + + // this is used to find the global frame + // which is the second last on the stack + template + bool Environment::is_lexical() const + { + return !! parent_ && parent_->parent_; + } + + // only match the real root scope + // there is still a parent around + // not sure what it is actually use for + // I guess we store functions etc. there + template + bool Environment::is_global() const + { + return parent_ && ! parent_->parent_; + } + + template + std::map& Environment::local_frame() { + return local_frame_; + } + + template + bool Environment::has_local(const std::string& key) const + { return local_frame_.find(key) != local_frame_.end(); } + + template + T& Environment::get_local(const std::string& key) + { return local_frame_[key]; } + + template + void Environment::set_local(const std::string& key, T val) + { + local_frame_[key] = val; + } + + template + void Environment::del_local(const std::string& key) + { local_frame_.erase(key); } + + template + Environment* Environment::global_env() + { + Environment* cur = this; + while (cur->is_lexical()) { + cur = cur->parent_; + } + return cur; + } + + template + bool Environment::has_global(const std::string& key) + { return global_env()->has(key); } + + template + T& Environment::get_global(const std::string& key) + { return (*global_env())[key]; } + + template + void Environment::set_global(const std::string& key, T val) + { + global_env()->local_frame_[key] = val; + } + + template + void Environment::del_global(const std::string& key) + { global_env()->local_frame_.erase(key); } + + template + Environment* Environment::lexical_env(const std::string& key) + { + Environment* cur = this; + while (cur) { + if (cur->has_local(key)) { + return cur; + } + cur = cur->parent_; + } + return this; + } + + // see if we have a lexical variable + // move down the stack but stop before we + // reach the global frame (is not included) + template + bool Environment::has_lexical(const std::string& key) const + { + auto cur = this; + while (cur->is_lexical()) { + if (cur->has_local(key)) return true; + cur = cur->parent_; + } + return false; + } + + // see if we have a lexical we could update + // either update already existing lexical value + // or if flag is set, we create one if no lexical found + template + void Environment::set_lexical(const std::string& key, T val) + { + auto cur = this; bool shadow = false; + while (cur->is_lexical() || shadow) { + if (cur->has_local(key)) { + cur->set_local(key, val); + return; + } + shadow = cur->is_shadow(); + cur = cur->parent_; + } + set_local(key, val); + } + + // look on the full stack for key + // include all scopes available + template + bool Environment::has(const std::string& key) const + { + auto cur = this; + while (cur) { + if (cur->has_local(key)) { + return true; + } + cur = cur->parent_; + } + return false; + } + + // use array access for getter and setter functions + template + T& Environment::operator[](const std::string& key) + { + auto cur = this; + while (cur) { + if (cur->has_local(key)) { + return cur->get_local(key); + } + cur = cur->parent_; + } + return get_local(key); + } + + #ifdef DEBUG + template + size_t Environment::print(std::string prefix) + { + size_t indent = 0; + if (parent_) indent = parent_->print(prefix) + 1; + std::cerr << prefix << std::string(indent, ' ') << "== " << this << std::endl; + for (typename std::map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { + if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { + std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second; + if (Value_Ptr val = SASS_MEMORY_CAST_PTR(Value, i->second)) + { std::cerr << " : " << val->to_string(); } + std::cerr << std::endl; + } + } + return indent ; + } + #endif + + // compile implementation for AST_Node + template class Environment; + +} + diff --git a/src/libsass/src/environment.hpp b/src/libsass/src/environment.hpp new file mode 100755 index 000000000..199b690db --- /dev/null +++ b/src/libsass/src/environment.hpp @@ -0,0 +1,92 @@ +#ifndef SASS_ENVIRONMENT_H +#define SASS_ENVIRONMENT_H + +#include +#include + +#include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" + +namespace Sass { + + template + class Environment { + // TODO: test with map + std::map local_frame_; + ADD_PROPERTY(Environment*, parent) + ADD_PROPERTY(bool, is_shadow) + + public: + Environment(bool is_shadow = false); + Environment(Environment* env, bool is_shadow = false); + Environment(Environment& env, bool is_shadow = false); + + // link parent to create a stack + void link(Environment& env); + void link(Environment* env); + + // this is used to find the global frame + // which is the second last on the stack + bool is_lexical() const; + + // only match the real root scope + // there is still a parent around + // not sure what it is actually use for + // I guess we store functions etc. there + bool is_global() const; + + // scope operates on the current frame + + std::map& local_frame(); + + bool has_local(const std::string& key) const; + + T& get_local(const std::string& key); + + // set variable on the current frame + void set_local(const std::string& key, T val); + + void del_local(const std::string& key); + + // global operates on the global frame + // which is the second last on the stack + Environment* global_env(); + // get the env where the variable already exists + // if it does not yet exist, we return current env + Environment* lexical_env(const std::string& key); + + bool has_global(const std::string& key); + + T& get_global(const std::string& key); + + // set a variable on the global frame + void set_global(const std::string& key, T val); + + void del_global(const std::string& key); + + // see if we have a lexical variable + // move down the stack but stop before we + // reach the global frame (is not included) + bool has_lexical(const std::string& key) const; + + // see if we have a lexical we could update + // either update already existing lexical value + // or we create a new one on the current frame + void set_lexical(const std::string& key, T val); + + // look on the full stack for key + // include all scopes available + bool has(const std::string& key) const; + + // use array access for getter and setter functions + T& operator[](const std::string& key); + + #ifdef DEBUG + size_t print(std::string prefix = ""); + #endif + + }; + +} + +#endif diff --git a/src/libsass/src/error_handling.cpp b/src/libsass/src/error_handling.cpp new file mode 100755 index 000000000..81e016fb0 --- /dev/null +++ b/src/libsass/src/error_handling.cpp @@ -0,0 +1,207 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "prelexer.hpp" +#include "backtrace.hpp" +#include "error_handling.hpp" + +#include + +namespace Sass { + + namespace Exception { + + Base::Base(ParserState pstate, std::string msg, std::vector* import_stack) + : std::runtime_error(msg), msg(msg), + prefix("Error"), pstate(pstate), + import_stack(import_stack) + { } + + InvalidSass::InvalidSass(ParserState pstate, std::string msg) + : Base(pstate, msg) + { } + + + InvalidParent::InvalidParent(Selector_Ptr parent, Selector_Ptr selector) + : Base(selector->pstate()), parent(parent), selector(selector) + { + msg = "Invalid parent selector for \""; + msg += selector->to_string(Sass_Inspect_Options()); + msg += "\": \""; + msg += parent->to_string(Sass_Inspect_Options()); + msg += "\""; + } + + InvalidArgumentType::InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value) + : Base(pstate), fn(fn), arg(arg), type(type), value(value) + { + msg = arg + ": \""; + if (value) msg += value->to_string(Sass_Inspect_Options()); + msg += "\" is not a " + type; + msg += " for `" + fn + "'"; + } + + MissingArgument::MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype) + : Base(pstate), fn(fn), arg(arg), fntype(fntype) + { + msg = fntype + " " + fn; + msg += " is missing argument "; + msg += arg + "."; + } + + InvalidSyntax::InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack) + : Base(pstate, msg, import_stack) + { } + + UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) + : lhs(lhs), rhs(rhs), op(op) + { + msg = def_op_msg + ": \""; + msg += lhs->to_string({ NESTED, 5 }); + msg += " " + op + " "; + msg += rhs->to_string({ TO_SASS, 5 }); + msg += "\"."; + } + + InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) + : UndefinedOperation(lhs, rhs, op) + { + msg = def_op_null_msg + ": \""; + msg += lhs->inspect(); + msg += " " + op + " "; + msg += rhs->inspect(); + msg += "\"."; + } + + ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) + : lhs(lhs), rhs(rhs) + { + msg = "divided by 0"; + } + + DuplicateKeyError::DuplicateKeyError(const Map& dup, const Expression& org) + : Base(org.pstate()), dup(dup), org(org) + { + msg = "Duplicate key "; + msg += dup.get_duplicate_key()->inspect(); + msg += " in map ("; + msg += org.inspect(); + msg += ")."; + } + + TypeMismatch::TypeMismatch(const Expression& var, const std::string type) + : Base(var.pstate()), var(var), type(type) + { + msg = var.to_string(); + msg += " is not an "; + msg += type; + msg += "."; + } + + InvalidValue::InvalidValue(const Expression& val) + : Base(val.pstate()), val(val) + { + msg = val.to_string(); + msg += " isn't a valid CSS value."; + } + + StackError::StackError(const AST_Node& node) + : Base(node.pstate()), node(node) + { + msg = "stack level too deep"; + } + + IncompatibleUnits::IncompatibleUnits(const Number& lhs, const Number& rhs) + : lhs(lhs), rhs(rhs) + { + msg = "Incompatible units: '"; + msg += rhs.unit(); + msg += "' and '"; + msg += lhs.unit(); + msg += "'."; + } + + AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) + : lhs(lhs), rhs(rhs), op(op) + { + msg = "Alpha channels must be equal: "; + msg += lhs->to_string({ NESTED, 5 }); + msg += " " + op + " "; + msg += rhs->to_string({ NESTED, 5 }); + msg += "."; + } + + + SassValueError::SassValueError(ParserState pstate, OperationError& err) + : Base(pstate, err.what()) + { + msg = err.what(); + prefix = err.errtype(); + } + + } + + + void warn(std::string msg, ParserState pstate) + { + std::cerr << "Warning: " << msg<< std::endl; + } + + void warn(std::string msg, ParserState pstate, Backtrace* bt) + { + Backtrace top(bt, pstate, ""); + msg += top.to_string(); + warn(msg, pstate); + } + + void deprecated_function(std::string msg, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); + + std::cerr << "DEPRECATION WARNING: " << msg << std::endl; + std::cerr << "will be an error in future versions of Sass." << std::endl; + std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; + } + + void deprecated(std::string msg, std::string msg2, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, pstate.path, pstate.path)); + + std::cerr << "DEPRECATION WARNING on line " << pstate.line + 1; + if (output_path.length()) std::cerr << " of " << output_path; + std::cerr << ":" << std::endl; + std::cerr << msg << " and will be an error in future versions of Sass." << std::endl; + if (msg2.length()) std::cerr << msg2 << std::endl; + std::cerr << std::endl; + } + + void deprecated_bind(std::string msg, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); + + std::cerr << "WARNING: " << msg << std::endl; + std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; + std::cerr << "This will be an error in future versions of Sass." << std::endl; + } + + void error(std::string msg, ParserState pstate) + { + throw Exception::InvalidSyntax(pstate, msg); + } + + void error(std::string msg, ParserState pstate, Backtrace* bt) + { + Backtrace top(bt, pstate, ""); + msg += "\n" + top.to_string(); + error(msg, pstate); + } + +} diff --git a/src/libsass/src/error_handling.hpp b/src/libsass/src/error_handling.hpp new file mode 100755 index 000000000..174d91cab --- /dev/null +++ b/src/libsass/src/error_handling.hpp @@ -0,0 +1,195 @@ +#ifndef SASS_ERROR_HANDLING_H +#define SASS_ERROR_HANDLING_H + +#include +#include +#include +#include "position.hpp" + +namespace Sass { + + struct Backtrace; + + namespace Exception { + + const std::string def_msg = "Invalid sass detected"; + const std::string def_op_msg = "Undefined operation"; + const std::string def_op_null_msg = "Invalid null operation"; + + class Base : public std::runtime_error { + protected: + std::string msg; + std::string prefix; + public: + ParserState pstate; + std::vector* import_stack; + public: + Base(ParserState pstate, std::string msg = def_msg, std::vector* import_stack = 0); + virtual const char* errtype() const { return prefix.c_str(); } + virtual const char* what() const throw() { return msg.c_str(); } + virtual ~Base() throw() {}; + }; + + class InvalidSass : public Base { + public: + InvalidSass(ParserState pstate, std::string msg); + virtual ~InvalidSass() throw() {}; + }; + + class InvalidParent : public Base { + protected: + Selector_Ptr parent; + Selector_Ptr selector; + public: + InvalidParent(Selector_Ptr parent, Selector_Ptr selector); + virtual ~InvalidParent() throw() {}; + }; + + class MissingArgument : public Base { + protected: + std::string fn; + std::string arg; + std::string fntype; + public: + MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype); + virtual ~MissingArgument() throw() {}; + }; + + class InvalidArgumentType : public Base { + protected: + std::string fn; + std::string arg; + std::string type; + const Value_Ptr value; + public: + InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); + virtual ~InvalidArgumentType() throw() {}; + }; + + class InvalidSyntax : public Base { + public: + InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack = 0); + virtual ~InvalidSyntax() throw() {}; + }; + + /* common virtual base class (has no pstate) */ + class OperationError : public std::runtime_error { + protected: + std::string msg; + public: + OperationError(std::string msg = def_op_msg) + : std::runtime_error(msg), msg(msg) + {}; + public: + virtual const char* errtype() const { return "Error"; } + virtual const char* what() const throw() { return msg.c_str(); } + virtual ~OperationError() throw() {}; + }; + + class ZeroDivisionError : public OperationError { + protected: + const Expression& lhs; + const Expression& rhs; + public: + ZeroDivisionError(const Expression& lhs, const Expression& rhs); + virtual const char* errtype() const { return "ZeroDivisionError"; } + virtual ~ZeroDivisionError() throw() {}; + }; + + class DuplicateKeyError : public Base { + protected: + const Map& dup; + const Expression& org; + public: + DuplicateKeyError(const Map& dup, const Expression& org); + virtual const char* errtype() const { return "Error"; } + virtual ~DuplicateKeyError() throw() {}; + }; + + class TypeMismatch : public Base { + protected: + const Expression& var; + const std::string type; + public: + TypeMismatch(const Expression& var, const std::string type); + virtual const char* errtype() const { return "Error"; } + virtual ~TypeMismatch() throw() {}; + }; + + class InvalidValue : public Base { + protected: + const Expression& val; + public: + InvalidValue(const Expression& val); + virtual const char* errtype() const { return "Error"; } + virtual ~InvalidValue() throw() {}; + }; + + class StackError : public Base { + protected: + const AST_Node& node; + public: + StackError(const AST_Node& node); + virtual const char* errtype() const { return "SystemStackError"; } + virtual ~StackError() throw() {}; + }; + + class IncompatibleUnits : public OperationError { + protected: + const Number& lhs; + const Number& rhs; + public: + IncompatibleUnits(const Number& lhs, const Number& rhs); + virtual ~IncompatibleUnits() throw() {}; + }; + + class UndefinedOperation : public OperationError { + protected: + Expression_Ptr_Const lhs; + Expression_Ptr_Const rhs; + const std::string op; + public: + UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + // virtual const char* errtype() const { return "Error"; } + virtual ~UndefinedOperation() throw() {}; + }; + + class InvalidNullOperation : public UndefinedOperation { + public: + InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + virtual ~InvalidNullOperation() throw() {}; + }; + + class AlphaChannelsNotEqual : public OperationError { + protected: + Expression_Ptr_Const lhs; + Expression_Ptr_Const rhs; + const std::string op; + public: + AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + // virtual const char* errtype() const { return "Error"; } + virtual ~AlphaChannelsNotEqual() throw() {}; + }; + + class SassValueError : public Base { + public: + SassValueError(ParserState pstate, OperationError& err); + virtual ~SassValueError() throw() {}; + }; + + } + + void warn(std::string msg, ParserState pstate); + void warn(std::string msg, ParserState pstate, Backtrace* bt); + + void deprecated_function(std::string msg, ParserState pstate); + void deprecated(std::string msg, std::string msg2, ParserState pstate); + void deprecated_bind(std::string msg, ParserState pstate); + // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); + + void error(std::string msg, ParserState pstate); + void error(std::string msg, ParserState pstate, Backtrace* bt); + +} + +#endif diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp new file mode 100755 index 000000000..2ab20c90e --- /dev/null +++ b/src/libsass/src/eval.cpp @@ -0,0 +1,1713 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "file.hpp" +#include "eval.hpp" +#include "ast.hpp" +#include "bind.hpp" +#include "util.hpp" +#include "inspect.hpp" +#include "environment.hpp" +#include "position.hpp" +#include "sass/values.h" +#include "to_value.hpp" +#include "to_c.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "lexer.hpp" +#include "prelexer.hpp" +#include "parser.hpp" +#include "expand.hpp" +#include "color_maps.hpp" + +namespace Sass { + + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } // x/0 checked by caller + inline double mod(double x, double y) { // x/0 checked by caller + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } else { + return std::fmod(x, y); + } + } + typedef double (*bop)(double, double); + bop ops[Sass_OP::NUM_OPS] = { + 0, 0, // and, or + 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte + add, sub, mul, div, mod + }; + + Eval::Eval(Expand& exp) + : exp(exp), + ctx(exp.ctx), + force(false), + is_in_comment(false) + { } + Eval::~Eval() { } + + Env* Eval::environment() + { + return exp.environment(); + } + + Selector_List_Obj Eval::selector() + { + return exp.selector(); + } + + Backtrace* Eval::backtrace() + { + return exp.backtrace(); + } + + Expression_Ptr Eval::operator()(Block_Ptr b) + { + Expression_Ptr val = 0; + for (size_t i = 0, L = b->length(); i < L; ++i) { + val = b->at(i)->perform(this); + if (val) return val; + } + return val; + } + + Expression_Ptr Eval::operator()(Assignment_Ptr a) + { + Env* env = exp.environment(); + std::string var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Ptr e = SASS_MEMORY_CAST(Expression, env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Ptr e = SASS_MEMORY_CAST(Expression, node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(this)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Ptr e = SASS_MEMORY_CAST(Expression, node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(this)); + } + else { + env->set_local(var, a->value()->perform(this)); + } + } + else { + env->set_lexical(var, a->value()->perform(this)); + } + return 0; + } + + Expression_Ptr Eval::operator()(If_Ptr i) + { + Expression_Obj rv = 0; + Env env(exp.environment()); + exp.env_stack.push_back(&env); + Expression_Obj cond = i->predicate()->perform(this); + if (!cond->is_false()) { + rv = i->block()->perform(this); + } + else { + Block_Obj alt = i->alternative(); + if (alt) rv = alt->perform(this); + } + exp.env_stack.pop_back(); + return rv.detach(); + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(this); + if (low->concrete_type() != Expression::NUMBER) { + throw Exception::TypeMismatch(*low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(this); + if (high->concrete_type() != Expression::NUMBER) { + throw Exception::TypeMismatch(*high, "integer"); + } + Number_Obj sass_start = SASS_MEMORY_CAST(Number, low); + Number_Obj sass_end = SASS_MEMORY_CAST(Number, high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_end->unit() << "' and '" + << sass_start->unit() << "'."; + error(msg.str(), low->pstate(), backtrace()); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + exp.env_stack.push_back(&env); + Number_Ptr it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit()); + env.set_local(variable, it); + Block_Obj body = f->block(); + Expression_Ptr val = 0; + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + it->value(i); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + it->value(i); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(this); + Env env(environment(), true); + exp.env_stack.push_back(&env); + List_Obj list = 0; + Map_Ptr map = 0; + if (expr->concrete_type() == Expression::MAP) { + map = SASS_MEMORY_CAST(Map, expr); + } + else if (Selector_List_Ptr ls = SASS_MEMORY_CAST(Selector_List, expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = dynamic_cast(&rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = SASS_MEMORY_CAST(List, expr); + } + + Block_Obj body = e->block(); + Expression_Obj val = 0; + + if (map) { + for (Expression_Obj key : map->keys()) { + Expression_Obj value = map->at(key); + + if (variables.size() == 1) { + List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(key); + variable->append(value); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], &key); + env.set_local(variables[1], &value); + } + + val = body->perform(this); + if (val) break; + } + } + else { + if (list->length() == 1 && SASS_MEMORY_CAST(Selector_List, list)) { + list = SASS_MEMORY_CAST(List, list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Ptr e = &list->at(i); + // unwrap value if the expression is an argument + if (Argument_Ptr arg = SASS_MEMORY_CAST_PTR(Argument, e)) e = &arg->value(); + // check if we got passed a list of args (investigate) + if (List_Ptr scalars = SASS_MEMORY_CAST_PTR(List, e)) { + if (variables.size() == 1) { + Expression_Ptr var = scalars; + env.set_local(variables[0], var); + } else { + // XXX: this is never hit via spec tests + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Ptr res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : &scalars->at(j); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), e); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + // XXX: this is never hit via spec tests + Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val.detach(); + } + + Expression_Ptr Eval::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Obj body = w->block(); + Env env(environment(), true); + exp.env_stack.push_back(&env); + Expression_Obj cond = pred->perform(this); + while (!cond->is_false()) { + Expression_Obj val = body->perform(this); + if (val) { + exp.env_stack.pop_back(); + return val.detach(); + } + cond = pred->perform(this); + } + exp.env_stack.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Return_Ptr r) + { + return r->value()->perform(this); + } + + Expression_Ptr Eval::operator()(Warning_Ptr w) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = w->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@warn[f]")) { + + Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@warn[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + Backtrace top(backtrace(), w->pstate(), ""); + std::cerr << "WARNING: " << result; + std::cerr << top.to_string(); + std::cerr << std::endl << std::endl; + ctx.c_options.output_style = outstyle; + return 0; + } + + Expression_Ptr Eval::operator()(Error_Ptr e) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = e->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@error[f]")) { + + Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@error[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + ctx.c_options.output_style = outstyle; + error(result, e->pstate()); + return 0; + } + + Expression_Ptr Eval::operator()(Debug_Ptr d) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = d->value()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@debug[f]")) { + + Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@debug[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string cwd(ctx.cwd()); + std::string result(unquote(message->to_sass())); + std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); + ctx.c_options.output_style = outstyle; + + std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; + std::cerr << std::endl; + return 0; + } + + Expression_Ptr Eval::operator()(List_Ptr l) + { + // special case for unevaluated map + if (l->separator() == SASS_HASH) { + Map_Obj lm = SASS_MEMORY_NEW(Map, + l->pstate(), + l->length() / 2); + for (size_t i = 0, L = l->length(); i < L; i += 2) + { + Expression_Ptr key = (*l)[i+0]->perform(this); + Expression_Ptr val = (*l)[i+1]->perform(this); + // make sure the color key never displays its real name + key->is_delayed(true); // verified + *lm << std::make_pair(key, val); + } + if (lm->has_duplicate_key()) { + throw Exception::DuplicateKeyError(*lm, *l); + } + + lm->is_interpolant(l->is_interpolant()); + return lm->perform(this); + } + // check if we should expand it + if (l->is_expanded()) return l; + // regular case for unevaluated lists + List_Obj ll = SASS_MEMORY_NEW(List, + l->pstate(), + l->length(), + l->separator(), + l->is_arglist()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + ll->append((*l)[i]->perform(this)); + } + ll->is_interpolant(l->is_interpolant()); + ll->from_selector(l->from_selector()); + ll->is_expanded(true); + return ll.detach(); + } + + Expression_Ptr Eval::operator()(Map_Ptr m) + { + if (m->is_expanded()) return m; + + // make sure we're not starting with duplicate keys. + // the duplicate key state will have been set in the parser phase. + if (m->has_duplicate_key()) { + throw Exception::DuplicateKeyError(*m, *m); + } + + Map_Obj mm = SASS_MEMORY_NEW(Map, + m->pstate(), + m->length()); + for (auto key : m->keys()) { + Expression_Ptr ex_key = key->perform(this); + Expression_Ptr ex_val = m->at(key)->perform(this); + *mm << std::make_pair(ex_key, ex_val); + } + + // check the evaluated keys aren't duplicates. + if (mm->has_duplicate_key()) { + throw Exception::DuplicateKeyError(*mm, *m); + } + + mm->is_expanded(true); + return mm.detach(); + } + + Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) + { + + String_Schema_Obj ret_schema; + Binary_Expression_Obj b = b_in; + enum Sass_OP op_type = b->type(); + + // only the last item will be used to eval the binary expression + if (String_Schema_Ptr s_l = SASS_MEMORY_CAST(String_Schema, b->left())) { + if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), &s_l->last(), b->right()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified + for (size_t i = 0; i < s_l->length() - 1; ++i) { + ret_schema->append(s_l->at(i)->perform(this)); + } + ret_schema->append(bin_ex->perform(this)); + return ret_schema->perform(this); + } + } + if (String_Schema_Ptr s_r = SASS_MEMORY_CAST(String_Schema, b->right())) { + + if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), b->left(), &s_r->first()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified + ret_schema->append(bin_ex->perform(this)); + for (size_t i = 1; i < s_r->length(); ++i) { + ret_schema->append(s_r->at(i)->perform(this)); + } + return ret_schema->perform(this); + } + } + + // don't eval delayed expressions (the '/' when used as a separator) + if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { + b->right(b->right()->perform(this)); + b->left(b->left()->perform(this)); + return b.detach(); + } + + Expression_Obj lhs = b->left(); + Expression_Obj rhs = b->right(); + + // fully evaluate their values + if (op_type == Sass_OP::EQ || + op_type == Sass_OP::NEQ || + op_type == Sass_OP::GT || + op_type == Sass_OP::GTE || + op_type == Sass_OP::LT || + op_type == Sass_OP::LTE) + { + LOCAL_FLAG(force, true); + lhs->is_expanded(false); + lhs->set_delayed(false); + lhs = lhs->perform(this); + rhs->is_expanded(false); + rhs->set_delayed(false); + rhs = rhs->perform(this); + } + else { + lhs = lhs->perform(this); + } + + Binary_Expression_Obj u3 = b; + switch (op_type) { + case Sass_OP::AND: { + return *lhs ? b->right()->perform(this) : lhs.detach(); + } break; + + case Sass_OP::OR: { + return *lhs ? lhs.detach() : b->right()->perform(this); + } break; + + default: + break; + } + // not a logical connective, so go ahead and eval the rhs + rhs = rhs->perform(this); + AST_Node_Obj lu = &lhs; + AST_Node_Obj ru = &rhs; + + Expression::Concrete_Type l_type = lhs->concrete_type(); + Expression::Concrete_Type r_type = rhs->concrete_type(); + + // Is one of the operands an interpolant? + String_Schema_Obj s1 = SASS_MEMORY_CAST(String_Schema, b->left()); + String_Schema_Obj s2 = SASS_MEMORY_CAST(String_Schema, b->right()); + Binary_Expression_Obj b1 = SASS_MEMORY_CAST(Binary_Expression, b->left()); + Binary_Expression_Obj b2 = SASS_MEMORY_CAST(Binary_Expression, b->right()); + + bool schema_op = false; + + bool force_delay = (s2 && s2->is_left_interpolant()) || + (s1 && s1->is_right_interpolant()) || + (b1 && b1->is_right_interpolant()) || + (b2 && b2->is_left_interpolant()); + + if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) + { + if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || + op_type == Sass_OP::EQ) { + // If possible upgrade LHS to a number (for number to string compare) + if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, lhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { + Textual_Obj l = SASS_MEMORY_NEW(Textual, b->pstate(), Textual::DIMENSION, str->value()); + lhs = l->perform(this); + } + } + // If possible upgrade RHS to a number (for string to number compare) + if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, rhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { + Textual_Obj r = SASS_MEMORY_NEW(Textual, b->pstate(), Textual::DIMENSION, str->value()); + rhs = r->perform(this); + } + } + } + + To_Value to_value(ctx); + Value_Obj v_l = dynamic_cast(lhs->perform(&to_value)); + Value_Obj v_r = dynamic_cast(rhs->perform(&to_value)); + l_type = lhs->concrete_type(); + r_type = rhs->concrete_type(); + + if (s2 && s2->has_interpolants() && s2->length()) { + Textual_Obj front = SASS_MEMORY_CAST(Textual, s2->elements().front()); + if (front && !front->is_interpolant()) + { + // XXX: this is never hit via spec tests + schema_op = true; + rhs = front->perform(this); + } + } + + if (force_delay) { + std::string str(""); + str += v_l->to_string(ctx.c_options); + if (b->op().ws_before) str += " "; + str += b->separator(); + if (b->op().ws_after) str += " "; + str += v_r->to_string(ctx.c_options); + String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); + val->is_interpolant(b->left()->has_interpolant()); + return val; + } + } + + // see if it's a relational expression + try { + switch(op_type) { + case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), eq(lhs, rhs)); + case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), !eq(lhs, rhs)); + case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gt") && !eq(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gte")); + case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lt")); + case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lte") || eq(lhs, rhs)); + default: break; + } + } + catch (Exception::OperationError& err) + { + // throw Exception::Base(b->pstate(), err.what()); + throw Exception::SassValueError(b->pstate(), err); + } + + l_type = lhs->concrete_type(); + r_type = rhs->concrete_type(); + + // ToDo: throw error in op functions + // ToDo: then catch and re-throw them + Expression_Obj rv = 0; + try { + ParserState pstate(b->pstate()); + if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { + Number_Ptr l_n = SASS_MEMORY_CAST(Number, lhs); + Number_Ptr r_n = SASS_MEMORY_CAST(Number, rhs); + rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, &pstate); + } + else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { + Number_Ptr l_n = SASS_MEMORY_CAST(Number, lhs); + Color_Ptr r_c = SASS_MEMORY_CAST(Color, rhs); + rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, &pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { + Color_Ptr l_c = SASS_MEMORY_CAST(Color, lhs); + Number_Ptr r_n = SASS_MEMORY_CAST(Number, rhs); + rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, &pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { + Color_Ptr l_c = SASS_MEMORY_CAST(Color, lhs); + Color_Ptr r_c = SASS_MEMORY_CAST(Color, rhs); + rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, &pstate); + } + else { + To_Value to_value(ctx); + // this will leak if perform does not return a value! + Value_Obj v_l = SASS_MEMORY_CAST_PTR(Value, lhs->perform(&to_value)); + Value_Obj v_r = SASS_MEMORY_CAST_PTR(Value, rhs->perform(&to_value)); + bool interpolant = b->is_right_interpolant() || + b->is_left_interpolant() || + b->is_interpolant(); + if (op_type == Sass_OP::SUB) interpolant = false; + // if (op_type == Sass_OP::DIV) interpolant = true; + // check for type violations + if (l_type == Expression::MAP) { + throw Exception::InvalidValue(*v_l); + } + if (r_type == Expression::MAP) { + throw Exception::InvalidValue(*v_r); + } + Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, &pstate, !interpolant); // pass true to compress + if (String_Constant_Ptr str = dynamic_cast(ex)) + { + if (str->concrete_type() == Expression::STRING) + { + String_Constant_Ptr lstr = SASS_MEMORY_CAST(String_Constant, lhs); + String_Constant_Ptr rstr = SASS_MEMORY_CAST(String_Constant, rhs); + if (op_type != Sass_OP::SUB) { + if (String_Constant_Ptr org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } + } + } + ex->is_interpolant(b->is_interpolant()); + rv = ex; + } + } + catch (Exception::OperationError& err) + { + // throw Exception::Base(b->pstate(), err.what()); + throw Exception::SassValueError(b->pstate(), err); + } + + if (rv) { + if (schema_op) { + // XXX: this is never hit via spec tests + (*s2)[0] = rv; + rv = s2->perform(this); + } + } + + return rv.detach(); + + } + + Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) + { + Expression_Obj operand = u->operand()->perform(this); + if (u->type() == Unary_Expression::NOT) { + Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); + result->value(!result->value()); + return result; + } + else if (operand->concrete_type() == Expression::NUMBER) { + Number_Ptr result = SASS_MEMORY_NEW(Number, static_cast(&operand)); + result->value(u->type() == Unary_Expression::MINUS + ? -result->value() + : result->value()); + return result; + } + else { + // Special cases: +/- variables which evaluate to null ouput just +/-, + // but +/- null itself outputs the string + if (operand->concrete_type() == Expression::NULL_VAL && SASS_MEMORY_CAST(Variable, u->operand())) { + u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); + } + // Never apply unary opertions on colors @see #2140 + else if (operand->concrete_type() == Expression::COLOR) { + Color_Ptr c = dynamic_cast(&operand); + + // Use the color name if this was eval with one + if (c->disp().length() > 0) { + operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), c->disp()); + u->operand(operand); + } + } + else { + u->operand(operand); + } + + String_Constant_Ptr result = SASS_MEMORY_NEW(String_Quoted, + u->pstate(), + u->inspect()); + return result; + } + // unreachable + return u; + } + + Expression_Ptr Eval::operator()(Function_Call_Ptr c) + { + if (backtrace()->parent != NULL && backtrace()->depth() > Constants::MaxCallStack) { + // XXX: this is never hit via spec tests + std::ostringstream stm; + stm << "Stack depth exceeded max of " << Constants::MaxCallStack; + error(stm.str(), c->pstate(), backtrace()); + } + std::string name(Util::normalize_underscores(c->name())); + std::string full_name(name + "[f]"); + // we make a clone here, need to implement that further + Arguments_Obj args = c->arguments(); + + Env* env = environment(); + if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { + if (!env->has("*[f]")) { + for (Argument_Obj arg : args->elements()) { + if (List_Obj ls = SASS_MEMORY_CAST(List, arg->value())) { + if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate()); + } + } + args = SASS_MEMORY_CAST_PTR(Arguments, args->perform(this)); + Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, + c->pstate(), + c->name(), + &args); + if (args->has_named_arguments()) { + error("Function " + c->name() + " doesn't support keyword arguments", c->pstate()); + } + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, + c->pstate(), + lit->to_string(ctx.c_options)); + str->is_interpolant(c->is_interpolant()); + return str; + } else { + // call generic function + full_name = "*[f]"; + } + } + + // further delay for calls + if (full_name != "call[f]") { + args->set_delayed(false); // verified + } + if (full_name != "if[f]") { + args = SASS_MEMORY_CAST_PTR(Arguments, args->perform(this)); + } + Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)[full_name]); + + if (def->is_overload_stub()) { + std::stringstream ss; + size_t L = args->length(); + // account for rest arguments + if (args->has_rest_argument() && args->length() > 0) { + // get the rest arguments list + List_Ptr rest = SASS_MEMORY_CAST(List, args->last()->value()); + // arguments before rest argument plus rest + if (rest) L += rest->length() - 1; + } + ss << full_name << L; + full_name = ss.str(); + std::string resolved_name(full_name); + if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate()); + def = SASS_MEMORY_CAST(Definition, (*env)[resolved_name]); + } + + Expression_Obj result = c; + Block_Obj body = def->block(); + Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + + Parameters_Obj params = def->parameters(); + Env fn_env(def->environment()); + exp.env_stack.push_back(&fn_env); + + if (func || body) { + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); + exp.backtrace_stack.push_back(&here); + // eval the body if user-defined or special, invoke underlying CPP function if native + if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { + result = body->perform(this); + } + else if (func) { + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace(), exp.selector_stack); + } + if (!result) { + error(std::string("Function ") + c->name() + " did not return a value", c->pstate()); + } + exp.backtrace_stack.pop_back(); + } + + // else if it's a user-defined c function + // convert call into C-API compatible form + else if (c_function) { + Sass_Function_Fn c_func = sass_function_get_function(c_function); + if (full_name == "*[f]") { + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); + Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); + new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), &str)); + new_args->concat(&args); + args = new_args; + } + + // populates env with default values for params + std::string ff(c->name()); + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + + Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); + exp.backtrace_stack.push_back(&here); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA); + for(size_t i = 0; i < params->length(); i++) { + Parameter_Obj param = params->at(i); + std::string key = param->name(); + AST_Node_Obj node = fn_env.get_local(key); + Expression_Obj arg = SASS_MEMORY_CAST(Expression, node); + sass_list_set_value(c_args, i, arg->perform(&to_c)); + } + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + if (sass_value_get_tag(c_val) == SASS_ERROR) { + error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), backtrace()); + } else if (sass_value_get_tag(c_val) == SASS_WARNING) { + error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), backtrace()); + } + result = cval_to_astnode(c_val, backtrace(), c->pstate()); + + exp.backtrace_stack.pop_back(); + sass_delete_value(c_args); + if (c_val != c_args) + sass_delete_value(c_val); + } + + // link back to function definition + // only do this for custom functions + if (result->pstate().file == std::string::npos) + result->pstate(c->pstate()); + + result = result->perform(this); + result->is_interpolant(c->is_interpolant()); + exp.env_stack.pop_back(); + return result.detach(); + } + + Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) + { + Expression_Ptr evaluated_name = s->name()->perform(this); + Expression_Ptr evaluated_args = s->arguments()->perform(this); + String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); + ss->append(evaluated_name); + ss->append(evaluated_args); + return ss->perform(this); + } + + Expression_Ptr Eval::operator()(Variable_Ptr v) + { + std::string name(v->name()); + Expression_Obj value = 0; + Env* env = environment(); + if (env->has(name)) { + value = SASS_MEMORY_CAST(Expression, (*env)[name]); + } + else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); + if (typeid(*value) == typeid(Argument)) { + value = SASS_MEMORY_CAST(Argument, value)->value(); + } + + // behave according to as ruby sass (add leading zero) + if (Number_Ptr nr = SASS_MEMORY_CAST(Number, value)) { + nr->zero(true); + } + + value->is_interpolant(v->is_interpolant()); + if (force) value->is_expanded(false); + value->set_delayed(false); // verified + value = value->perform(this); + if(!force) (*env)[name] = &value; + return value.detach(); + } + + Expression_Ptr Eval::operator()(Textual_Ptr t) + { + using Prelexer::number; + Expression_Obj result = 0; + size_t L = t->value().length(); + bool zero = !( (L > 0 && t->value().substr(0, 1) == ".") || + (L > 1 && t->value().substr(0, 2) == "0.") || + (L > 1 && t->value().substr(0, 2) == "-.") || + (L > 2 && t->value().substr(0, 3) == "-0.") + ); + + const std::string& text = t->value(); + size_t num_pos = text.find_first_not_of(" \n\r\t"); + if (num_pos == std::string::npos) num_pos = text.length(); + size_t unit_pos = text.find_first_not_of("-+0123456789.", num_pos); + if (unit_pos == std::string::npos) unit_pos = text.length(); + const std::string& num = text.substr(num_pos, unit_pos - num_pos); + + switch (t->type()) + { + case Textual::NUMBER: + result = SASS_MEMORY_NEW(Number, + t->pstate(), + sass_atof(num.c_str()), + "", + zero); + break; + case Textual::PERCENTAGE: + result = SASS_MEMORY_NEW(Number, + t->pstate(), + sass_atof(num.c_str()), + "%", + true); + break; + case Textual::DIMENSION: + result = SASS_MEMORY_NEW(Number, + t->pstate(), + sass_atof(num.c_str()), + Token(number(text.c_str())), + zero); + break; + case Textual::HEX: { + if (t->value().substr(0, 1) != "#") { + result = SASS_MEMORY_NEW(String_Quoted, t->pstate(), t->value()); + break; + } + std::string hext(t->value().substr(1)); // chop off the '#' + if (hext.length() == 6) { + std::string r(hext.substr(0,2)); + std::string g(hext.substr(2,2)); + std::string b(hext.substr(4,2)); + result = SASS_MEMORY_NEW(Color, + t->pstate(), + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + t->value()); + } + else { + result = SASS_MEMORY_NEW(Color, + t->pstate(), + static_cast(strtol(std::string(2,hext[0]).c_str(), NULL, 16)), + static_cast(strtol(std::string(2,hext[1]).c_str(), NULL, 16)), + static_cast(strtol(std::string(2,hext[2]).c_str(), NULL, 16)), + 1, // alpha channel + t->value()); + } + } break; + } + result->is_interpolant(t->is_interpolant()); + return result.detach(); + } + + Expression_Ptr Eval::operator()(Color_Ptr c) + { + return c; + } + + Expression_Ptr Eval::operator()(Number_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Boolean_Ptr b) + { + return b; + } + + void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { + + bool needs_closing_brace = false; + + if (Arguments_Ptr args = SASS_MEMORY_CAST(Arguments, ex)) { + List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); + for(auto arg : args->elements()) { + ll->append(arg->value()); + } + ll->is_interpolant(args->is_interpolant()); + needs_closing_brace = true; + res += "("; + ex = ll; + } + if (Number_Ptr nr = SASS_MEMORY_CAST(Number, ex)) { + if (!nr->is_valid_css_unit()) { + throw Exception::InvalidValue(*nr); + } + } + if (Argument_Ptr arg = SASS_MEMORY_CAST(Argument, ex)) { + ex = &arg->value(); + } + if (String_Quoted_Ptr sq = SASS_MEMORY_CAST(String_Quoted, ex)) { + if (was_itpl) { + bool was_interpolant = ex->is_interpolant(); + ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); + ex->is_interpolant(was_interpolant); + } + } + + if (SASS_MEMORY_CAST(Null, ex)) { return; } + + // parent selector needs another go + if (SASS_MEMORY_CAST(Parent_Selector, ex)) { + // XXX: this is never hit via spec tests + ex = ex->perform(this); + } + + if (List_Ptr l = SASS_MEMORY_CAST(List, ex)) { + List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); + // this fixes an issue with bourbon sample, not really sure why + // if (l->size() && dynamic_cast((*l)[0])) { res += ""; } + for(Expression_Obj item : *l) { + item->is_interpolant(l->is_interpolant()); + std::string rl(""); interpolation(ctx, rl, &item, into_quotes, l->is_interpolant()); + bool is_null = dynamic_cast(&item) != 0; // rl != "" + if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); + } + // Check indicates that we probably should not get a list + // here. Normally single list items are already unwrapped. + if (l->size() > 1) { + // string_to_output would fail "#{'_\a' '_\a'}"; + std::string str(ll->to_string(ctx.c_options)); + newline_to_space(str); // replace directly + res += str; // append to result string + } else { + res += (ll->to_string(ctx.c_options)); + } + ll->is_interpolant(l->is_interpolant()); + } + + // Value + // Textual + // Function_Call + // Selector_List + // String_Quoted + // String_Constant + // Parent_Selector + // Binary_Expression + else { + // ex = ex->perform(this); + if (into_quotes && ex->is_interpolant()) { + res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); + } else { + res += ex ? ex->to_string(ctx.c_options) : ""; + } + } + + if (needs_closing_brace) res += ")"; + + } + + Expression_Ptr Eval::operator()(String_Schema_Ptr s) + { + size_t L = s->length(); + bool into_quotes = false; + if (L > 1) { + if (!SASS_MEMORY_CAST(String_Quoted, (*s)[0]) && !SASS_MEMORY_CAST(String_Quoted, (*s)[L - 1])) { + if (String_Constant_Ptr l = SASS_MEMORY_CAST(String_Constant, (*s)[0])) { + if (String_Constant_Ptr r = SASS_MEMORY_CAST(String_Constant, (*s)[L - 1])) { + if (r->value().size() > 0) { + if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; + if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; + } + } + } + } + } + bool was_quoted = false; + bool was_interpolant = false; + std::string res(""); + for (size_t i = 0; i < L; ++i) { + bool is_quoted = SASS_MEMORY_CAST(String_Quoted, (*s)[i]) != NULL; + if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + Expression_Obj ex = (*s)[i]->perform(this); + interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); + was_quoted = SASS_MEMORY_CAST(String_Quoted, (*s)[i]) != NULL; + was_interpolant = (*s)[i]->is_interpolant(); + + } + if (!s->is_interpolant()) { + if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); + return SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + } + // string schema seems to have a special unquoting behavior (also handles "nested" quotes) + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false); + // if (s->is_interpolant()) str->quote_mark(0); + // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + if (str->quote_mark()) str->quote_mark('*'); + else if (!is_in_comment) str->value(string_to_output(str->value())); + str->is_interpolant(s->is_interpolant()); + return str.detach(); + } + + + Expression_Ptr Eval::operator()(String_Constant_Ptr s) + { + if (!s->is_delayed() && name_to_color(s->value())) { + Color_Ptr c = SASS_MEMORY_COPY(name_to_color(s->value())); // copy + c->pstate(s->pstate()); + c->disp(s->value()); + c->is_delayed(true); + return c; + } + return s; + } + + Expression_Ptr Eval::operator()(String_Quoted_Ptr s) + { + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); + str->value(s->value()); + str->quote_mark(s->quote_mark()); + str->is_interpolant(s->is_interpolant()); + return str; + } + + Expression_Ptr Eval::operator()(Supports_Operator_Ptr c) + { + Expression_Ptr left = c->left()->perform(this); + Expression_Ptr right = c->right()->perform(this); + Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, + c->pstate(), + dynamic_cast(left), + dynamic_cast(right), + c->operand()); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) + { + Expression_Ptr condition = c->condition()->perform(this); + Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, + c->pstate(), + dynamic_cast(condition)); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) + { + Expression_Ptr feature = c->feature()->perform(this); + Expression_Ptr value = c->value()->perform(this); + Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, + c->pstate(), + feature, + value); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) + { + Expression_Ptr value = c->value()->perform(this); + Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, + c->pstate(), + value); + return cc; + } + + Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, + e->pstate(), + dynamic_cast(&feature), + value); + return ee; + } + + Expression_Ptr Eval::operator()(Media_Query_Ptr q) + { + String_Obj t = q->media_type(); + t = static_cast(&t ? t->perform(this) : 0); + Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, + q->pstate(), + &t, + q->length(), + q->is_negated(), + q->is_restricted()); + for (size_t i = 0, L = q->length(); i < L; ++i) { + qq->append(static_cast((*q)[i]->perform(this))); + } + return qq.detach(); + } + + Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + if (feature && SASS_MEMORY_CAST(String_Quoted, feature)) { + feature = SASS_MEMORY_NEW(String_Quoted, + feature->pstate(), + SASS_MEMORY_CAST(String_Quoted, feature)->value()); + } + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + if (value && SASS_MEMORY_CAST(String_Quoted, value)) { + // XXX: this is never hit via spec tests + value = SASS_MEMORY_NEW(String_Quoted, + value->pstate(), + SASS_MEMORY_CAST(String_Quoted, value)->value()); + } + return SASS_MEMORY_NEW(Media_Query_Expression, + e->pstate(), + feature, + value, + e->is_interpolated()); + } + + Expression_Ptr Eval::operator()(Null_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Argument_Ptr a) + { + Expression_Obj val = a->value()->perform(this); + bool is_rest_argument = a->is_rest_argument(); + bool is_keyword_argument = a->is_keyword_argument(); + + if (a->is_rest_argument()) { + if (val->concrete_type() == Expression::MAP) { + is_rest_argument = false; + is_keyword_argument = true; + } + else if(val->concrete_type() != Expression::LIST) { + List_Obj wrapper = SASS_MEMORY_NEW(List, + val->pstate(), + 0, + SASS_COMMA, + true); + wrapper->append(val); + val = &wrapper; + } + } + return SASS_MEMORY_NEW(Argument, + a->pstate(), + val, + a->name(), + is_rest_argument, + is_keyword_argument); + } + + Expression_Ptr Eval::operator()(Arguments_Ptr a) + { + Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); + if (a->length() == 0) return aa.detach(); + for (size_t i = 0, L = a->length(); i < L; ++i) { + Expression_Obj rv = (*a)[i]->perform(this); + Argument_Ptr arg = SASS_MEMORY_CAST(Argument, rv); + if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { + aa->append(arg); + } + } + + if (a->has_rest_argument()) { + Expression_Obj rest = a->get_rest_argument()->perform(this); + Expression_Obj splat = SASS_MEMORY_CAST(Argument, rest)->value()->perform(this); + + Sass_Separator separator = SASS_COMMA; + List_Ptr ls = SASS_MEMORY_CAST(List, splat); + Map_Ptr ms = SASS_MEMORY_CAST(Map, splat); + + List_Obj arglist = SASS_MEMORY_NEW(List, + splat->pstate(), + 0, + ls ? ls->separator() : separator, + true); + + if (ls && ls->is_arglist()) { + arglist->concat(ls); + } else if (ms) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); + } else if (ls) { + arglist->concat(ls); + } else { + arglist->append(splat); + } + if (arglist->length()) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), &arglist, "", true)); + } + } + + if (a->has_keyword_argument()) { + Expression_Obj rv = a->get_keyword_argument()->perform(this); + Argument_Ptr rvarg = SASS_MEMORY_CAST(Argument, rv); + Expression_Obj kwarg = rvarg->value()->perform(this); + + aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); + } + return aa.detach(); + } + + Expression_Ptr Eval::operator()(Comment_Ptr c) + { + return 0; + } + + inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + // All the binary helpers. + + bool Eval::eq(Expression_Obj lhs, Expression_Obj rhs) + { + // use compare operator from ast node + return lhs && rhs && *lhs == *rhs; + } + + bool Eval::lt(Expression_Obj lhs, Expression_Obj rhs, std::string op) + { + Number_Obj l = SASS_MEMORY_CAST(Number, lhs); + Number_Obj r = SASS_MEMORY_CAST(Number, rhs); + // use compare operator from ast node + if (!l || !r) throw Exception::UndefinedOperation(&lhs, &rhs, op); + // use compare operator from ast node + return *l < *r; + } + + Value_Ptr Eval::op_numbers(enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate) + { + double lv = l.value(); + double rv = r.value(); + if (op == Sass_OP::DIV && rv == 0) { + // XXX: this is never hit via spec tests + return SASS_MEMORY_NEW(String_Quoted, pstate ? *pstate : l.pstate(), lv ? "Infinity" : "NaN"); + } + if (op == Sass_OP::MOD && !rv) { + // XXX: this is never hit via spec tests + throw Exception::ZeroDivisionError(l, r); + } + + Number tmp(&r); // copy + bool strict = op != Sass_OP::MUL && op != Sass_OP::DIV; + tmp.normalize(l.find_convertible_unit(), strict); + std::string l_unit(l.unit()); + std::string r_unit(tmp.unit()); + Number_Obj v = SASS_MEMORY_COPY(&l); // copy + v->pstate(pstate ? *pstate : l.pstate()); + if (l_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerator_units() = r.numerator_units(); + v->denominator_units() = r.denominator_units(); + } + + if (op == Sass_OP::MUL) { + v->value(ops[op](lv, rv)); + for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) { + v->numerator_units().push_back(r.numerator_units()[i]); + } + for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) { + v->denominator_units().push_back(r.denominator_units()[i]); + } + } + else if (op == Sass_OP::DIV) { + v->value(ops[op](lv, rv)); + for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) { + v->denominator_units().push_back(r.numerator_units()[i]); + } + for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) { + v->numerator_units().push_back(r.denominator_units()[i]); + } + } else { + v->value(ops[op](lv, r.value() * r.convert_factor(l))); + // v->normalize(); + return v.detach(); + + v->value(ops[op](lv, tmp.value())); + } + v->normalize(); + return v.detach(); + } + + Value_Ptr Eval::op_number_color(enum Sass_OP op, const Number& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate) + { + double lv = l.value(); + switch (op) { + case Sass_OP::ADD: + case Sass_OP::MUL: { + return SASS_MEMORY_NEW(Color, + pstate ? *pstate : l.pstate(), + ops[op](lv, r.r()), + ops[op](lv, r.g()), + ops[op](lv, r.b()), + r.a()); + } break; + case Sass_OP::SUB: + case Sass_OP::DIV: { + std::string sep(op == Sass_OP::SUB ? "-" : "/"); + std::string color(r.to_string(opt)); + return SASS_MEMORY_NEW(String_Quoted, + pstate ? *pstate : l.pstate(), + l.to_string(opt) + + sep + + color); + } break; + case Sass_OP::MOD: { + throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op)); + } break; + default: break; // caller should ensure that we don't get here + } + // unreachable + return NULL; + } + + Value_Ptr Eval::op_color_number(enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate) + { + double rv = r.value(); + if (op == Sass_OP::DIV && !rv) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(l, r); + } + return SASS_MEMORY_NEW(Color, + pstate ? *pstate : l.pstate(), + ops[op](l.r(), rv), + ops[op](l.g(), rv), + ops[op](l.b(), rv), + l.a()); + } + + Value_Ptr Eval::op_colors(enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate) + { + if (l.a() != r.a()) { + throw Exception::AlphaChannelsNotEqual(&l, &r, "+"); + } + if (op == Sass_OP::DIV && (!r.r() || !r.g() ||!r.b())) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(l, r); + } + return SASS_MEMORY_NEW(Color, + pstate ? *pstate : l.pstate(), + ops[op](l.r(), r.r()), + ops[op](l.g(), r.g()), + ops[op](l.b(), r.b()), + l.a()); + } + + Value_Ptr Eval::op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, ParserState* pstate, bool delayed) + { + Expression::Concrete_Type ltype = lhs.concrete_type(); + Expression::Concrete_Type rtype = rhs.concrete_type(); + enum Sass_OP op = operand.operand; + + String_Quoted_Ptr lqstr = dynamic_cast(&lhs); + String_Quoted_Ptr rqstr = dynamic_cast(&rhs); + + std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); + std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); + + if (ltype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); + if (rtype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); + if (op == Sass_OP::MOD) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); + if (op == Sass_OP::MUL) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); + std::string sep; + switch (op) { + case Sass_OP::SUB: sep = "-"; break; + case Sass_OP::DIV: sep = "/"; break; + case Sass_OP::MUL: sep = "*"; break; + case Sass_OP::MOD: sep = "%"; break; + case Sass_OP::EQ: sep = "=="; break; + case Sass_OP::NEQ: sep = "!="; break; + case Sass_OP::LT: sep = "<"; break; + case Sass_OP::GT: sep = ">"; break; + case Sass_OP::LTE: sep = "<="; break; + case Sass_OP::GTE: sep = ">="; break; + default: break; + } + + if ( (sep == "") /* && + (sep != "/" || !rqstr || !rqstr->quote_mark()) */ + ) { + // create a new string that might be quoted on output (but do not unquote what we pass) + return SASS_MEMORY_NEW(String_Quoted, pstate ? *pstate : lhs.pstate(), lstr + rstr, 0, false, true); + } + + if (sep != "" && !delayed) { + if (operand.ws_before) sep = " " + sep; + if (operand.ws_after) sep = sep + " "; + } + + if (op == Sass_OP::SUB || op == Sass_OP::DIV) { + if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); + if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); + } + + return SASS_MEMORY_NEW(String_Constant, pstate ? *pstate : lhs.pstate(), lstr + sep + rstr); + } + + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate) + { + using std::strlen; + using std::strcpy; + Expression_Ptr e = NULL; + switch (sass_value_get_tag(v)) { + case SASS_BOOLEAN: { + e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); + } break; + case SASS_NUMBER: { + e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); + } break; + case SASS_COLOR: { + e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); + } break; + case SASS_STRING: { + if (sass_string_is_quoted(v)) + e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); + else { + e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); + } + } break; + case SASS_LIST: { + List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); + for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { + l->append(cval_to_astnode(sass_list_get_value(v, i), backtrace, pstate)); + } + e = l; + } break; + case SASS_MAP: { + Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); + for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { + *m << std::make_pair( + cval_to_astnode(sass_map_get_key(v, i), backtrace, pstate), + cval_to_astnode(sass_map_get_value(v, i), backtrace, pstate)); + } + e = m; + } break; + case SASS_NULL: { + e = SASS_MEMORY_NEW(Null, pstate); + } break; + case SASS_ERROR: { + error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, backtrace); + } break; + case SASS_WARNING: { + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, backtrace); + } break; + } + return e; + } + + Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) + { + std::vector rv; + Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); + sl->is_optional(s->is_optional()); + sl->media_block(s->media_block()); + sl->is_optional(s->is_optional()); + for (size_t i = 0, iL = s->length(); i < iL; ++i) { + rv.push_back(operator()(&(*s)[i])); + } + + // we should actually permutate parent first + // but here we have permutated the selector first + size_t round = 0; + while (round != std::string::npos) { + bool abort = true; + for (size_t i = 0, iL = rv.size(); i < iL; ++i) { + if (rv[i]->length() > round) { + sl->append((*rv[i])[round]); + abort = false; + } + } + if (abort) { + round = std::string::npos; + } else { + ++ round; + } + + } + return sl.detach(); + } + + + Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) + { + bool implicit_parent = !exp.old_at_root_without_rule; + return s->resolve_parent_refs(ctx, exp.selector_stack, implicit_parent); + } + + // XXX: this is never hit via spec tests + Attribute_Selector_Ptr Eval::operator()(Attribute_Selector_Ptr s) + { + String_Obj attr = s->value(); + if (attr) { attr = static_cast(attr->perform(this)); } + Attribute_Selector_Ptr ss = SASS_MEMORY_COPY(s); + ss->value(attr); + return ss; + } + + Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) + { + // the parser will look for a brace to end the selector + Expression_Obj sel = s->contents()->perform(this); + std::string result_str(sel->to_string(ctx.c_options)); + result_str = unquote(Util::rtrim(result_str)) + "\n{"; + Parser p = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()); + p.last_media_block = s->media_block(); + Selector_List_Obj sl = p.parse_selector_list(exp.block_stack.back()->is_root()); + if (s->has_parent_ref()) sl->remove_parent_selectors(); + return operator()(&sl); + } + + Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) + { + if (Selector_List_Obj pr = selector()) { + exp.selector_stack.pop_back(); + Selector_List_Obj rv = operator()(&pr); + exp.selector_stack.push_back(rv); + return rv.detach(); + } else { + return SASS_MEMORY_NEW(Null, p->pstate()); + } + } + +} diff --git a/src/libsass/src/eval.hpp b/src/libsass/src/eval.hpp new file mode 100755 index 000000000..ca89d7fc2 --- /dev/null +++ b/src/libsass/src/eval.hpp @@ -0,0 +1,109 @@ +#ifndef SASS_EVAL_H +#define SASS_EVAL_H + +#include "ast.hpp" +#include "context.hpp" +#include "listize.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + class Expand; + class Context; + + class Eval : public Operation_CRTP { + + private: + Expression_Ptr fallback_impl(AST_Node_Ptr n); + + public: + Expand& exp; + Context& ctx; + Eval(Expand& exp); + ~Eval(); + + bool force; + bool is_in_comment; + + Env* environment(); + Backtrace* backtrace(); + Selector_List_Obj selector(); + + // for evaluating function bodies + Expression_Ptr operator()(Block_Ptr); + Expression_Ptr operator()(Assignment_Ptr); + Expression_Ptr operator()(If_Ptr); + Expression_Ptr operator()(For_Ptr); + Expression_Ptr operator()(Each_Ptr); + Expression_Ptr operator()(While_Ptr); + Expression_Ptr operator()(Return_Ptr); + Expression_Ptr operator()(Warning_Ptr); + Expression_Ptr operator()(Error_Ptr); + Expression_Ptr operator()(Debug_Ptr); + + Expression_Ptr operator()(List_Ptr); + Expression_Ptr operator()(Map_Ptr); + Expression_Ptr operator()(Binary_Expression_Ptr); + Expression_Ptr operator()(Unary_Expression_Ptr); + Expression_Ptr operator()(Function_Call_Ptr); + Expression_Ptr operator()(Function_Call_Schema_Ptr); + Expression_Ptr operator()(Variable_Ptr); + Expression_Ptr operator()(Textual_Ptr); + Expression_Ptr operator()(Number_Ptr); + Expression_Ptr operator()(Color_Ptr); + Expression_Ptr operator()(Boolean_Ptr); + Expression_Ptr operator()(String_Schema_Ptr); + Expression_Ptr operator()(String_Quoted_Ptr); + Expression_Ptr operator()(String_Constant_Ptr); + // Expression_Ptr operator()(Selector_List_Ptr); + Expression_Ptr operator()(Media_Query_Ptr); + Expression_Ptr operator()(Media_Query_Expression_Ptr); + Expression_Ptr operator()(At_Root_Query_Ptr); + Expression_Ptr operator()(Supports_Operator_Ptr); + Expression_Ptr operator()(Supports_Negation_Ptr); + Expression_Ptr operator()(Supports_Declaration_Ptr); + Expression_Ptr operator()(Supports_Interpolation_Ptr); + Expression_Ptr operator()(Null_Ptr); + Expression_Ptr operator()(Argument_Ptr); + Expression_Ptr operator()(Arguments_Ptr); + Expression_Ptr operator()(Comment_Ptr); + + // these will return selectors + Selector_List_Ptr operator()(Selector_List_Ptr); + Selector_List_Ptr operator()(Complex_Selector_Ptr); + Attribute_Selector_Ptr operator()(Attribute_Selector_Ptr); + // they don't have any specific implementatio (yet) + Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; }; + Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; }; + Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s) { return s; }; + Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; }; + Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; }; + Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; }; + // actual evaluated selectors + Selector_List_Ptr operator()(Selector_Schema_Ptr); + Expression_Ptr operator()(Parent_Selector_Ptr); + + template + Expression_Ptr fallback(U x) { return fallback_impl(x); } + + // -- only need to define two comparisons, and the rest can be implemented in terms of them + static bool eq(Expression_Obj, Expression_Obj); + static bool lt(Expression_Obj, Expression_Obj, std::string op); + // -- arithmetic on the combinations that matter + static Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); + static Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); + static Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); + static Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); + static Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, ParserState* pstate = 0, bool interpolant = false); + + private: + void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); + + }; + + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate = ParserState("[AST]")); + +} + +#endif diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp new file mode 100755 index 000000000..d38a01296 --- /dev/null +++ b/src/libsass/src/expand.cpp @@ -0,0 +1,789 @@ +#include "sass.hpp" +#include +#include + +#include "ast.hpp" +#include "expand.hpp" +#include "bind.hpp" +#include "eval.hpp" +#include "backtrace.hpp" +#include "context.hpp" +#include "parser.hpp" + +namespace Sass { + + // simple endless recursion protection + const size_t maxRecursion = 500; + + Expand::Expand(Context& ctx, Env* env, Backtrace* bt, std::vector* stack) + : ctx(ctx), + eval(Eval(*this)), + recursions(0), + in_keyframes(false), + at_root_without_rule(false), + old_at_root_without_rule(false), + env_stack(std::vector()), + block_stack(std::vector()), + call_stack(std::vector()), + selector_stack(std::vector()), + media_block_stack(std::vector()), + backtrace_stack(std::vector()) + { + env_stack.push_back(0); + env_stack.push_back(env); + block_stack.push_back(0); + call_stack.push_back(0); + if (stack == NULL) { selector_stack.push_back(0); } + else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } + media_block_stack.push_back(0); + backtrace_stack.push_back(0); + backtrace_stack.push_back(bt); + } + + Context& Expand::context() + { + return ctx; + } + + Env* Expand::environment() + { + if (env_stack.size() > 0) + return env_stack.back(); + return 0; + } + + Selector_List_Obj Expand::selector() + { + if (selector_stack.size() > 0) + return selector_stack.back(); + return 0; + } + + Backtrace* Expand::backtrace() + { + if (backtrace_stack.size() > 0) + return backtrace_stack.back(); + return 0; + } + + // blocks create new variable scopes + Block_Ptr Expand::operator()(Block_Ptr b) + { + // create new local environment + // set the current env as parent + Env env(environment()); + // copy the block object (add items later) + Block_Obj bb = SASS_MEMORY_NEW(Block, + b->pstate(), + b->length(), + b->is_root()); + // setup block and env stack + this->block_stack.push_back(&bb); + this->env_stack.push_back(&env); + // operate on block + // this may throw up! + this->append_block(b); + // revert block and env stack + this->block_stack.pop_back(); + this->env_stack.pop_back(); + // return copy + return bb.detach(); + } + + Statement_Ptr Expand::operator()(Ruleset_Ptr r) + { + LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); + + if (in_keyframes) { + Block_Ptr bb = operator()(&r->block()); + Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); + if (r->selector()) { + selector_stack.push_back(0); + k->name(SASS_MEMORY_CAST_PTR(Selector_List, r->selector()->perform(&eval))); + selector_stack.pop_back(); + } + return k.detach(); + } + + // reset when leaving scope + LOCAL_FLAG(at_root_without_rule, false); + + // do some special checks for the base level rules + if (r->is_root()) { + if (Selector_List_Ptr selector_list = SASS_MEMORY_CAST(Selector_List, r->selector())) { + for (Complex_Selector_Obj complex_selector : selector_list->elements()) { + Complex_Selector_Ptr tail = &complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + if (SASS_MEMORY_CAST(Parent_Selector, header) == NULL) continue; // skip all others + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace()); + } + tail = &tail->tail(); + } + } + } + } + + Expression_Obj ex = 0; + if (r->selector()) ex = r->selector()->perform(&eval); + Selector_List_Obj sel = SASS_MEMORY_CAST(Selector_List, ex); + if (sel == 0) throw std::runtime_error("Expanded null selector"); + + if (sel->length() == 0 || sel->has_parent_ref()) { + bool has_parent_selector = false; + for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { + Selector_List_Obj ll = selector_stack.at(i); + has_parent_selector = ll != 0 && ll->length() > 0; + } + if (sel->has_real_parent_ref() && !has_parent_selector) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), backtrace()); + } + } + + selector_stack.push_back(&sel); + Env env(environment()); + if (block_stack.back()->is_root()) { + env_stack.push_back(&env); + } + sel->set_media_block(media_block_stack.back()); + Block_Obj blk = 0; + if (r->block()) blk = operator()(&r->block()); + Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + &sel, + blk); + selector_stack.pop_back(); + if (block_stack.back()->is_root()) { + env_stack.pop_back(); + } + + rr->is_root(r->is_root()); + rr->tabs(r->tabs()); + + return rr; + } + + Statement_Ptr Expand::operator()(Supports_Block_Ptr f) + { + Expression_Obj condition = f->condition()->perform(&eval); + Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, + f->pstate(), + SASS_MEMORY_CAST(Supports_Condition, condition), + operator()(&f->block())); + return ff.detach(); + } + + Statement_Ptr Expand::operator()(Media_Block_Ptr m) + { + media_block_stack.push_back(m); + Expression_Obj mq = m->media_queries()->perform(&eval); + std::string str_mq(mq->to_string(ctx.c_options)); + char* str = sass_copy_c_string(str_mq.c_str()); + ctx.strings.push_back(str); + Parser p(Parser::from_c_str(str, ctx, mq->pstate())); + mq = &p.parse_media_queries(); // re-assign now + List_Obj ls = SASS_MEMORY_CAST_PTR(List, mq->perform(&eval)); + Block_Obj blk = operator()(&m->block()); + Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + ls, + blk, + 0); + media_block_stack.pop_back(); + mm->tabs(m->tabs()); + return mm; + } + + Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) + { + Block_Obj ab = a->block(); + Expression_Obj ae = &a->expression(); + + if (ae) ae = ae->perform(&eval); + else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); + + LOCAL_FLAG(at_root_without_rule, true); + LOCAL_FLAG(in_keyframes, false); + + ; + + Block_Obj bb = ab ? operator()(&ab) : NULL; + At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, + a->pstate(), + bb, + SASS_MEMORY_CAST(At_Root_Query, ae)); + return aa.detach(); + } + + Statement_Ptr Expand::operator()(Directive_Ptr a) + { + LOCAL_FLAG(in_keyframes, a->is_keyframes()); + Block_Ptr ab = &a->block(); + Selector_Ptr as = &a->selector(); + Expression_Ptr av = &a->value(); + selector_stack.push_back(0); + if (av) av = av->perform(&eval); + if (as) as = SASS_MEMORY_CAST_PTR(Selector, as->perform(&eval)); + selector_stack.pop_back(); + Block_Ptr bb = ab ? operator()(ab) : NULL; + Directive_Ptr aa = SASS_MEMORY_NEW(Directive, + a->pstate(), + a->keyword(), + as, + bb, + av); + return aa; + } + + Statement_Ptr Expand::operator()(Declaration_Ptr d) + { + Block_Obj ab = d->block(); + String_Obj old_p = d->property(); + Expression_Obj prop = old_p->perform(&eval); + String_Obj new_p = SASS_MEMORY_CAST(String, prop); + // we might get a color back + if (!new_p) { + std::string str(prop->to_string(ctx.c_options)); + new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); + } + Expression_Obj value = d->value()->perform(&eval); + Block_Obj bb = ab ? operator()(&ab) : NULL; + if (!bb) { + if (!value || (value->is_invisible() && !d->is_important())) return 0; + } + Declaration_Ptr decl = SASS_MEMORY_NEW(Declaration, + d->pstate(), + new_p, + value, + d->is_important(), + bb); + decl->tabs(d->tabs()); + return decl; + } + + Statement_Ptr Expand::operator()(Assignment_Ptr a) + { + Env* env = environment(); + std::string var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Obj e = SASS_MEMORY_CAST(Expression, env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Obj e = SASS_MEMORY_CAST(Expression, node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(&eval)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Obj e = SASS_MEMORY_CAST(Expression, node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(&eval)); + } + else { + env->set_local(var, a->value()->perform(&eval)); + } + } + else { + env->set_lexical(var, a->value()->perform(&eval)); + } + return 0; + } + + Statement_Ptr Expand::operator()(Import_Ptr imp) + { + Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); + if (imp->import_queries() && imp->import_queries()->size()) { + Expression_Obj ex = imp->import_queries()->perform(&eval); + result->import_queries(SASS_MEMORY_CAST(List, ex)); + } + for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { + result->urls().push_back(imp->urls()[i]->perform(&eval)); + } + // all resources have been dropped for Input_Stubs + // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} + return result.detach(); + } + + Statement_Ptr Expand::operator()(Import_Stub_Ptr i) + { + // get parent node from call stack + AST_Node_Obj parent = call_stack.back(); + if (SASS_MEMORY_CAST(Block, parent) == NULL) { + error("Import directives may not be used within control directives or mixins.", i->pstate()); + } + // we don't seem to need that actually afterall + Sass_Import_Entry import = sass_make_import( + i->imp_path().c_str(), + i->abs_path().c_str(), + 0, 0 + ); + ctx.import_stack.push_back(import); + const std::string& abs_path(i->resource().abs_path); + append_block(&ctx.sheets.at(abs_path).root); + sass_delete_import(ctx.import_stack.back()); + ctx.import_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Warning_Ptr w) + { + // eval handles this too, because warnings may occur in functions + w->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Error_Ptr e) + { + // eval handles this too, because errors may occur in functions + e->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Debug_Ptr d) + { + // eval handles this too, because warnings may occur in functions + d->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Comment_Ptr c) + { + eval.is_in_comment = true; + Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), SASS_MEMORY_CAST_PTR(String, c->text()->perform(&eval)), c->is_important()); + eval.is_in_comment = false; + // TODO: eval the text, once we're parsing/storing it as a String_Schema + return rv; + } + + Statement_Ptr Expand::operator()(If_Ptr i) + { + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(i); + Expression_Obj rv = i->predicate()->perform(&eval); + if (*rv) { + append_block(&i->block()); + } + else { + Block_Ptr alt = &i->alternative(); + if (alt) append_block(alt); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(&eval); + if (low->concrete_type() != Expression::NUMBER) { + throw Exception::TypeMismatch(*low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(&eval); + if (high->concrete_type() != Expression::NUMBER) { + throw Exception::TypeMismatch(*high, "integer"); + } + Number_Obj sass_start = SASS_MEMORY_CAST(Number, low); + Number_Obj sass_end = SASS_MEMORY_CAST(Number, high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_start->unit() << "' and '" + << sass_end->unit() << "'."; + error(msg.str(), low->pstate(), backtrace()); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(f); + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit()); + env.set_local(variable, &it); + Block_Ptr body = &f->block(); + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + it = SASS_MEMORY_COPY(it); + it->value(i); + env.set_local(variable, &it); + append_block(body); + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + it = SASS_MEMORY_COPY(it); + it->value(i); + env.set_local(variable, &it); + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(&eval); + List_Obj list = 0; + Map_Obj map; + if (expr->concrete_type() == Expression::MAP) { + map = SASS_MEMORY_CAST(Map, expr); + } + else if (Selector_List_Ptr ls = SASS_MEMORY_CAST(Selector_List, expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = SASS_MEMORY_CAST(List, rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = SASS_MEMORY_CAST(List, expr); + } + // remember variables and then reset them + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(e); + Block_Ptr body = &e->block(); + + if (map) { + for (auto key : map->keys()) { + Expression_Obj k = key->perform(&eval); + Expression_Obj v = map->at(key)->perform(&eval); + + if (variables.size() == 1) { + List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(k); + variable->append(v); + env.set_local(variables[0], &variable); + } else { + env.set_local(variables[0], &k); + env.set_local(variables[1], &v); + } + append_block(body); + } + } + else { + // bool arglist = list->is_arglist(); + if (list->length() == 1 && SASS_MEMORY_CAST(Selector_List, list)) { + list = SASS_MEMORY_CAST(List, list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Obj e = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, e)) e = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Obj scalars = SASS_MEMORY_CAST(List, e)) { + if (variables.size() == 1) { + List_Obj var = scalars; + // if (arglist) var = (*scalars)[0]; + env.set_local(variables[0], &var); + } else { + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Obj res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : (*scalars)[j]->perform(&eval); + env.set_local(variables[j], &res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), &e); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], &res); + } + } + } + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Ptr body = &w->block(); + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(w); + Expression_Obj cond = pred->perform(&eval); + while (*&cond) { + append_block(body); + cond = pred->perform(&eval); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Return_Ptr r) + { + error("@return may only be used within a function", r->pstate(), backtrace()); + return 0; + } + + + void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { + + if (Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, s)) { + for (Complex_Selector_Obj complex_selector : sl->elements()) { + Complex_Selector_Obj tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + if (SASS_MEMORY_CAST(Parent_Selector, header) == NULL) continue; // skip all others + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace()); + } + tail = tail->tail(); + } + } + } + + + Selector_List_Obj contextualized = SASS_MEMORY_CAST_PTR(Selector_List, s->perform(&eval)); + if (contextualized == false) return; + for (auto complex_sel : contextualized->elements()) { + Complex_Selector_Obj c = complex_sel; + if (!c->head() || c->tail()) { + std::string sel_str(contextualized->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace()); + } + Compound_Selector_Obj placeholder = c->head(); + if (contextualized->is_optional()) placeholder->is_optional(true); + for (size_t i = 0, L = extender->length(); i < L; ++i) { + Complex_Selector_Obj sel = (*extender)[i]; + if (!(sel->head() && sel->head()->length() > 0 && + SASS_MEMORY_CAST(Parent_Selector, (*sel->head())[0]))) + { + Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); + hh->media_block((*extender)[i]->media_block()); + Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); + ssel->media_block((*extender)[i]->media_block()); + if (sel->has_line_feed()) ssel->has_line_feed(true); + Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); + ps->media_block((*extender)[i]->media_block()); + hh->append(&ps); + ssel->tail(sel); + ssel->head(hh); + sel = ssel; + } + // if (c->has_line_feed()) sel->has_line_feed(true); + ctx.subset_map.put(placeholder, std::make_pair(sel, placeholder)); + } + } + + } + + Statement* Expand::operator()(Extension_Ptr e) + { + if (Selector_List_Obj extender = SASS_MEMORY_CAST(Selector_List, selector())) { + Selector_Obj s = e->selector(); + Selector_List_Obj sl = NULL; + // check if we already have a valid selector list + if ((sl = SASS_MEMORY_CAST(Selector_List, s))) {} + // convert selector schema to a selector list + else if (Selector_Schema_Obj schema = SASS_MEMORY_CAST(Selector_Schema, s)) { + if (schema->has_real_parent_ref()) { + sl = eval(&schema); + } else { + selector_stack.push_back(0); + sl = eval(&schema); + sl->remove_parent_selectors(); + selector_stack.pop_back(); + } + } + // abort on invalid selector + if (sl.isNull()) return NULL; + for (Complex_Selector_Obj cs : sl->elements()) { + if (!cs.isNull() && !cs->head().isNull()) { + cs->head()->media_block(media_block_stack.back()); + } + } + selector_stack.push_back(0); + expand_selector_list(&sl, extender); + selector_stack.pop_back(); + } + return 0; + } + + Statement_Ptr Expand::operator()(Definition_Ptr d) + { + Env* env = environment(); + Definition_Obj dd = SASS_MEMORY_COPY(d); + env->local_frame()[d->name() + + (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = ⅆ + + if (d->type() == Definition::FUNCTION && ( + Prelexer::calc_fn_call(d->name().c_str()) || + d->name() == "element" || + d->name() == "expression" || + d->name() == "url" + )) { + deprecated( + "Naming a function \"" + d->name() + "\" is disallowed", + "This name conflicts with an existing CSS function with special parse rules.", + d->pstate() + ); + } + + // set the static link so we can have lexical scoping + dd->environment(env); + return 0; + } + + Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) + { + + if (recursions > maxRecursion) { + throw Exception::StackError(*c); + } + + recursions ++; + + Env* env = environment(); + std::string full_name(c->name() + "[m]"); + if (!env->has(full_name)) { + error("no mixin named " + c->name(), c->pstate(), backtrace()); + } + Definition_Obj def = SASS_MEMORY_CAST(Definition, (*env)[full_name]); + Block_Obj body = def->block(); + Parameters_Obj params = def->parameters(); + + if (c->block() && c->name() != "@content" && !body->has_content()) { + error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), backtrace()); + } + Expression_Obj rv = c->arguments()->perform(&eval); + Arguments_Obj args = SASS_MEMORY_CAST(Arguments, rv); + Backtrace new_bt(backtrace(), c->pstate(), ", in mixin `" + c->name() + "`"); + backtrace_stack.push_back(&new_bt); + Env new_env(def->environment()); + env_stack.push_back(&new_env); + if (c->block()) { + // represent mixin content blocks as thunks/closures + Definition_Obj thunk = SASS_MEMORY_NEW(Definition, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Parameters, c->pstate()), + c->block(), + Definition::MIXIN); + thunk->environment(env); + new_env.local_frame()["@content[m]"] = &thunk; + } + + bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); + + + block_stack.push_back(&trace_block); + for (auto bb : body->elements()) { + Statement_Obj ith = bb->perform(this); + if (ith) trace->block()->append(ith); + } + block_stack.pop_back(); + + env_stack.pop_back(); + backtrace_stack.pop_back(); + + recursions --; + return trace.detach(); + } + + Statement_Ptr Expand::operator()(Content_Ptr c) + { + Env* env = environment(); + // convert @content directives into mixin calls to the underlying thunk + if (!env->has("@content[m]")) return 0; + + if (block_stack.back()->is_root()) { + selector_stack.push_back(0); + } + + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Arguments, c->pstate())); + + Trace_Obj trace = SASS_MEMORY_CAST_PTR(Trace, call->perform(this)); + + if (block_stack.back()->is_root()) { + selector_stack.pop_back(); + } + + return trace.detach(); + } + + // produce an error if something is not implemented + inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) + { + std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); + String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); + error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace()); + return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), &msg); + } + + // process and add to last block on stack + inline void Expand::append_block(Block_Ptr b) + { + if (b->is_root()) call_stack.push_back(b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr stm = &b->at(i); + Statement_Obj ith = stm->perform(this); + if (ith) block_stack.back()->append(ith); + } + if (b->is_root()) call_stack.pop_back(); + } + +} diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp new file mode 100755 index 000000000..1f93d2acb --- /dev/null +++ b/src/libsass/src/expand.hpp @@ -0,0 +1,83 @@ +#ifndef SASS_EXPAND_H +#define SASS_EXPAND_H + +#include + +#include "ast.hpp" +#include "eval.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + class Listize; + class Context; + class Eval; + typedef Environment Env; + struct Backtrace; + + class Expand : public Operation_CRTP { + public: + + Env* environment(); + Context& context(); + Selector_List_Obj selector(); + Backtrace* backtrace(); + + Context& ctx; + Eval eval; + size_t recursions; + bool in_keyframes; + bool at_root_without_rule; + bool old_at_root_without_rule; + + // it's easier to work with vectors + std::vector env_stack; + std::vector block_stack; + std::vector call_stack; + std::vector selector_stack; + std::vector media_block_stack; + std::vector backtrace_stack; + + Statement_Ptr fallback_impl(AST_Node_Ptr n); + + private: + void expand_selector_list(Selector_Obj, Selector_List_Obj extender); + + public: + Expand(Context&, Env*, Backtrace*, std::vector* stack = NULL); + ~Expand() { } + + Block_Ptr operator()(Block_Ptr); + Statement_Ptr operator()(Ruleset_Ptr); + Statement_Ptr operator()(Media_Block_Ptr); + Statement_Ptr operator()(Supports_Block_Ptr); + Statement_Ptr operator()(At_Root_Block_Ptr); + Statement_Ptr operator()(Directive_Ptr); + Statement_Ptr operator()(Declaration_Ptr); + Statement_Ptr operator()(Assignment_Ptr); + Statement_Ptr operator()(Import_Ptr); + Statement_Ptr operator()(Import_Stub_Ptr); + Statement_Ptr operator()(Warning_Ptr); + Statement_Ptr operator()(Error_Ptr); + Statement_Ptr operator()(Debug_Ptr); + Statement_Ptr operator()(Comment_Ptr); + Statement_Ptr operator()(If_Ptr); + Statement_Ptr operator()(For_Ptr); + Statement_Ptr operator()(Each_Ptr); + Statement_Ptr operator()(While_Ptr); + Statement_Ptr operator()(Return_Ptr); + Statement_Ptr operator()(Extension_Ptr); + Statement_Ptr operator()(Definition_Ptr); + Statement_Ptr operator()(Mixin_Call_Ptr); + Statement_Ptr operator()(Content_Ptr); + + template + Statement_Ptr fallback(U x) { return fallback_impl(x); } + + void append_block(Block_Ptr); + }; + +} + +#endif diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp new file mode 100755 index 000000000..8582de8a9 --- /dev/null +++ b/src/libsass/src/extend.cpp @@ -0,0 +1,2122 @@ +#include "sass.hpp" +#include "extend.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "paths.hpp" +#include "parser.hpp" +#include "node.hpp" +#include "sass_util.hpp" +#include "remove_placeholders.hpp" +#include "debug.hpp" +#include +#include +#include + +/* + NOTES: + + - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which + is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what + ruby already outputs (to make comparisons easier). + + - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing. + Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass. + The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually + used for the porting. + + - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby + output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to + the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see + something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment. + + - Coding conventions in this file (these may need to be changed before merging back into master) + - Very basic hungarian notation: + p prefix for pointers (pSelector) + no prefix for value types and references (selector) + - Use STL iterators where possible + - prefer verbose naming over terse naming + - use typedefs for STL container types for make maintenance easier + + - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators + in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby + sass causes this to be necessary. + + + GLOBAL TODOS: + + - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode. + + - consider making the extend* functions member functions to avoid passing around ctx and subset_map map around. This has the + drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and + can cause additional compile time dependencies. + + - mark the helper methods in this file static to given them compilation unit linkage. + + - implement parent directive matching + + - fix compilation warnings for unused Extend members if we really don't need those references anymore. + */ + + +namespace Sass { + + + typedef std::pair ExtensionPair; + typedef std::vector SubsetMapEntries; + +#ifdef DEBUG + + // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp + std::ostream& operator<<(std::ostream& os, const Complex_Selector::Combinator combinator) { + switch (combinator) { + case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; + case Complex_Selector::PARENT_OF: os << "\">\""; break; + case Complex_Selector::PRECEDES: os << "\"~\""; break; + case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; + case Complex_Selector::REFERENCE: os << "\"/\""; break; + } + + return os; + } + + + std::ostream& operator<<(std::ostream& os, Compound_Selector& compoundSelector) { + for (size_t i = 0, L = compoundSelector.length(); i < L; ++i) { + if (i > 0) os << ", "; + os << compoundSelector[i]->to_string(); + } + return os; + } + + std::ostream& operator<<(std::ostream& os, Simple_Selector& simpleSelector) { + os << simpleSelector.to_string(); + return os; + } + + // Print a string representation of a Compound_Selector + static void printSimpleSelector(Simple_Selector* pSimpleSelector, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + if (pSimpleSelector) { + std::cerr << "[" << *pSimpleSelector << "]"; + } else { + std::cerr << "NULL"; + } + + if (newline) { + std::cerr << std::endl; + } + } + + // Print a string representation of a Compound_Selector + typedef std::pair SelsNewSeqPair; + typedef std::vector SelsNewSeqPairCollection; + + + // Print a string representation of a Compound_Selector + static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + if (pCompoundSelector) { + std::cerr << "[" << *pCompoundSelector << "]"; + } else { + std::cerr << "NULL"; + } + + if (newline) { + std::cerr << std::endl; + } + } + + + std::ostream& operator<<(std::ostream& os, Complex_Selector& complexSelector) { + + os << "["; + Complex_Selector_Ptr pIter = &complexSelector; + bool first = true; + while (pIter) { + if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) { + if (!first) { + os << ", "; + } + first = false; + os << pIter->combinator(); + } + + if (!first) { + os << ", "; + } + first = false; + + if (pIter->head()) { + os << pIter->head()->to_string(); + } else { + os << "NULL_HEAD"; + } + + pIter = pIter->tail(); + } + os << "]"; + + return os; + } + + + // Print a string representation of a Complex_Selector + static void printComplexSelector(Complex_Selector_Ptr pComplexSelector, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + if (pComplexSelector) { + std::cerr << *pComplexSelector; + } else { + std::cerr << "NULL"; + } + + if (newline) { + std::cerr << std::endl; + } + } + + static void printSelsNewSeqPairCollection(SelsNewSeqPairCollection& collection, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + bool first = true; + std::cerr << "["; + for(SelsNewSeqPair& pair : collection) { + if (first) { + first = false; + } else { + std::cerr << ", "; + } + std::cerr << "["; + Compound_Selector_Ptr pSels = pair.first; + Complex_Selector_Ptr pNewSelector = pair.second; + std::cerr << "[" << *pSels << "], "; + printComplexSelector(pNewSelector, NULL, false); + } + std::cerr << "]"; + + if (newline) { + std::cerr << std::endl; + } + } + + // Print a string representation of a SourcesSet + static void printSourcesSet(SourcesSet& sources, Context& ctx, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on + // the differences we see when debug printing. + typedef std::deque SourceStrings; + SourceStrings sourceStrings; + for (SourcesSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { + Complex_Selector_Ptr pSource = *iterator; + std::stringstream sstream; + sstream << complexSelectorToNode(pSource, ctx); + sourceStrings.push_back(sstream.str()); + } + + // Sort to get consistent output + std::sort(sourceStrings.begin(), sourceStrings.end()); + + std::cerr << "SourcesSet["; + for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) { + std::string source = *iterator; + if (iterator != sourceStrings.begin()) { + std::cerr << ", "; + } + std::cerr << source; + } + std::cerr << "]"; + + if (newline) { + std::cerr << std::endl; + } + } + + + std::ostream& operator<<(std::ostream& os, SubsetMapEntries& entries) { + os << "SUBSET_MAP_ENTRIES["; + + for (SubsetMapEntries::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { + Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge) + Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend + + if (iterator != entries.begin()) { + os << ", "; + } + + os << "("; + + if (pExtComplexSelector) { + std::cerr << *pExtComplexSelector; + } else { + std::cerr << "NULL"; + } + + os << " -> "; + + if (pExtCompoundSelector) { + std::cerr << *pExtCompoundSelector; + } else { + std::cerr << "NULL"; + } + + os << ")"; + + } + + os << "]"; + + return os; + } +#endif + + static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo, Context& ctx) { + // TODO: figure out a better way to create a Complex_Selector from scratch + // TODO: There's got to be a better way. This got ugly quick... + Position noPosition(-1, -1, -1); + Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); + Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); + fakeHead->elements().push_back(&fakeParent); + Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/); + + pOne->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + pTwo->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + + bool isSuperselector = pOne->is_superselector_of(pTwo); + + pOne->clear_innermost(); + pTwo->clear_innermost(); + + return isSuperselector; + } + + void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out, Context& ctx) { + for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { + Node& child = *iter; + out.push_back(nodeToComplexSelector(child, ctx)); + } + } + + Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque, Context& ctx) { + Node result = Node::createCollection(); + + for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { + Complex_Selector_Obj pChild = *iter; + result.collection()->push_back(complexSelectorToNode(&pChild, ctx)); + } + + return result; + } + + class LcsCollectionComparator { + public: + LcsCollectionComparator(Context& ctx) : mCtx(ctx) {} + + Context& mCtx; + + bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const { + /* + This code is based on the following block from ruby sass' subweave + do |s1, s2| + next s1 if s1 == s2 + next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) + next s2 if parent_superselector?(s1, s2) + next s1 if parent_superselector?(s2, s1) + end + */ + + if (selectors_equal(*pOne, *pTwo, true /*simpleSelectorOrderDependent*/)) { + pOut = pOne; + return true; + } + + if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) { + return false; + } + + if (parentSuperselector(&pOne, &pTwo, mCtx)) { + pOut = pTwo; + return true; + } + + if (parentSuperselector(&pTwo, &pOne, mCtx)) { + pOut = pOne; + return true; + } + + return false; + } + }; + + + /* + This is the equivalent of ruby's Sass::Util.lcs_backtrace. + + # Computes a single longest common subsequence for arrays x and y. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS + */ + void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { + //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) + // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output + + if (i == 0 || j == 0) { + DEBUG_PRINTLN(LCS, "RETURNING EMPTY") + return; + } + + + Complex_Selector_Obj pCompareOut; + if (comparator(x[i], y[j], pCompareOut)) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") + lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out); + out.push_back(pCompareOut); + return; + } + + if (c[i][j - 1] > c[i - 1][j]) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") + lcs_backtrace(c, x, y, i, j - 1, comparator, out); + return; + } + + DEBUG_PRINTLN(LCS, "FINAL RETURN") + lcs_backtrace(c, x, y, i - 1, j, comparator, out); + return; + } + + /* + This is the equivalent of ruby's Sass::Util.lcs_table. + + # Calculates the memoization table for the Least Common Subsequence algorithm. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS + */ + void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) { + //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) + // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output + + LCSTable c(x.size(), std::vector(y.size())); + + // These shouldn't be necessary since the vector will be initialized to 0 already. + // x.size.times {|i| c[i][0] = 0} + // y.size.times {|j| c[0][j] = 0} + + for (size_t i = 1; i < x.size(); i++) { + for (size_t j = 1; j < y.size(); j++) { + Complex_Selector_Obj pCompareOut; + + if (comparator(&x[i], &y[j], pCompareOut)) { + c[i][j] = c[i - 1][j - 1] + 1; + } else { + c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); + } + } + } + + out = c; + } + + /* + This is the equivalent of ruby's Sass::Util.lcs. + + # Computes a single longest common subsequence for `x` and `y`. + # If there are more than one longest common subsequences, + # the one returned is that which starts first in `x`. + + # @param x [NodeCollection] + # @param y [NodeCollection] + # @comparator An equality check between elements of `x` and `y`. + # @return [NodeCollection] The LCS + + http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + */ + void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, Context& ctx, ComplexSelectorDeque& out) { + //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) + // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output + + x.push_front(NULL); + y.push_front(NULL); + + LCSTable table; + lcs_table(x, y, comparator, table); + + return lcs_backtrace(table, x, y, static_cast(x.size()) - 1, static_cast(y.size()) - 1, comparator, out); + } + + + /* + This is the equivalent of ruby's Sequence.trim. + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + # Avoid truly horrific quadratic behavior. TODO: I think there + # may be a way to get perfect trimming without going quadratic. + return seqses if seqses.size > 100 + + # Keep the results in a separate array so we can be sure we aren't + # comparing against an already-trimmed selector. This ensures that two + # identical selectors don't mutually trim one another. + result = seqses.dup + + # This is n^2 on the sequences, but only comparing between + # separate sequences should limit the quadratic behavior. + seqses.each_with_index do |seqs1, i| + tempResult = [] + + for seq1 in seqs1 do + max_spec = 0 + for seq in _sources(seq1) do + max_spec = [max_spec, seq.specificity].max + end + + + isMoreSpecificOuter = false + for seqs2 in result do + if seqs1.equal?(seqs2) then + next + end + + # Second Law of Extend: the specificity of a generated selector + # should never be less than the specificity of the extending + # selector. + # + # See https://github.com/nex3/sass/issues/324. + isMoreSpecificInner = false + for seq2 in seqs2 do + isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1) + if isMoreSpecificInner then + break + end + end + + if isMoreSpecificInner then + isMoreSpecificOuter = true + break + end + end + + if !isMoreSpecificOuter then + tempResult.push(seq1) + end + end + + result[i] = tempResult + + end + + result + */ + /* + - IMPROVEMENT: We could probably work directly in the output trimmed deque. + */ + static Node trim(Node& seqses, Context& ctx, bool isReplace) { + // See the comments in the above ruby code before embarking on understanding this function. + + // Avoid poor performance in extreme cases. + if (seqses.collection()->size() > 100) { + return seqses; + } + + + DEBUG_PRINTLN(TRIM, "TRIM: " << seqses) + + + Node result = Node::createCollection(); + result.plus(seqses); + + DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result) + + // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're + // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track + // of the index manually. + int toTrimIndex = 0; + + for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) { + Node& seqs1 = *seqsesIter; + + DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex) + + Node tempResult = Node::createCollection(); + tempResult.got_line_feed = seqs1.got_line_feed; + + for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) { + Node& seq1 = *seqs1Iter; + + Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1, ctx); + + // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code + // for a good description of sources. + // + // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test. + // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We + // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My + // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely + // a guess though. + unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0; + SourcesSet sources = pSeq1->sources(); + + DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) + DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIM SOURCES: ")) + + for (SourcesSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { + const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; + maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity()); + } + + DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity) + + bool isMoreSpecificOuter = false; + + int resultIndex = 0; + + for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) { + Node& seqs2 = *resultIter; + + DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1) + DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2) + + // Do not compare the same sequence to itself. The ruby call we're trying to + // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision. + // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is + // derived from seqses and seqs2 is derived from result. + if (seqs1.collection() == seqs2.collection()) { + DEBUG_PRINTLN(TRIM, "CONTINUE") + continue; + } + + bool isMoreSpecificInner = false; + + for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) { + Node& seq2 = *seqs2Iter; + + Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2, ctx); + + DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity()) + DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false")) + DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false")) + + isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1); + + if (isMoreSpecificInner) { + DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC") + break; + } + } + + // If we found something more specific, we're done. Let the outer loop know and stop iterating. + if (isMoreSpecificInner) { + isMoreSpecificOuter = true; + break; + } + + resultIndex++; + } + + if (!isMoreSpecificOuter) { + DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1) + tempResult.collection()->push_back(seq1); + } + + } + + DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result) + DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult) + (*result.collection())[toTrimIndex] = tempResult; + + toTrimIndex++; + + DEBUG_PRINTLN(TRIM, "RESULT: " << result) + } + + return result; + } + + + + static bool parentSuperselector(const Node& one, const Node& two, Context& ctx) { + // TODO: figure out a better way to create a Complex_Selector from scratch + // TODO: There's got to be a better way. This got ugly quick... + Position noPosition(-1, -1, -1); + Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); + Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); + fakeHead->elements().push_back(&fakeParent); + Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/); + + Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one, ctx); + pOneWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two, ctx); + pTwoWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + + return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); + } + + + class ParentSuperselectorChunker { + public: + ParentSuperselectorChunker(Node& lcs, Context& ctx) : mLcs(lcs), mCtx(ctx) {} + Node& mLcs; + Context& mCtx; + + bool operator()(const Node& seq) const { + // {|s| parent_superselector?(s.first, lcs.first)} + if (seq.collection()->size() == 0) return false; + return parentSuperselector(seq.collection()->front(), mLcs.collection()->front(), mCtx); + } + }; + + class SubweaveEmptyChunker { + public: + bool operator()(const Node& seq) const { + // {|s| s.empty?} + + return seq.collection()->empty(); + } + }; + + /* + # Takes initial subsequences of `seq1` and `seq2` and returns all + # orderings of those subsequences. The initial subsequences are determined + # by a block. + # + # Destructively removes the initial subsequences of `seq1` and `seq2`. + # + # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` + # denoting the boundary of the initial subsequence), this would return + # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and + # `(3 4 5)`. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @yield [a] Used to determine when to cut off the initial subsequences. + # Called repeatedly for each sequence until it returns true. + # @yieldparam a [Array] A final subsequence of one input sequence after + # cutting off some initial subsequence. + # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence + # here. + # @return [Array] All possible orderings of the initial subsequences. + def chunks(seq1, seq2) + chunk1 = [] + chunk1 << seq1.shift until yield seq1 + chunk2 = [] + chunk2 << seq2.shift until yield seq2 + return [] if chunk1.empty? && chunk2.empty? + return [chunk2] if chunk1.empty? + return [chunk1] if chunk2.empty? + [chunk1 + chunk2, chunk2 + chunk1] + end + */ + template + static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) { + Node chunk1 = Node::createCollection(); + while (seq1.collection()->size() && !chunker(seq1)) { + chunk1.collection()->push_back(seq1.collection()->front()); + seq1.collection()->pop_front(); + } + + Node chunk2 = Node::createCollection(); + while (!chunker(seq2)) { + chunk2.collection()->push_back(seq2.collection()->front()); + seq2.collection()->pop_front(); + } + + if (chunk1.collection()->empty() && chunk2.collection()->empty()) { + DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY") + return Node::createCollection(); + } + + if (chunk1.collection()->empty()) { + Node chunk2Wrapper = Node::createCollection(); + chunk2Wrapper.collection()->push_back(chunk2); + DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY") + return chunk2Wrapper; + } + + if (chunk2.collection()->empty()) { + Node chunk1Wrapper = Node::createCollection(); + chunk1Wrapper.collection()->push_back(chunk1); + DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY") + return chunk1Wrapper; + } + + Node perms = Node::createCollection(); + + Node firstPermutation = Node::createCollection(); + firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); + firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); + perms.collection()->push_back(firstPermutation); + + Node secondPermutation = Node::createCollection(); + secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); + secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); + perms.collection()->push_back(secondPermutation); + + DEBUG_PRINTLN(CHUNKS, "RETURNING PERM") + + return perms; + } + + + static Node groupSelectors(Node& seq, Context& ctx) { + Node newSeq = Node::createCollection(); + + Node tail = Node::createCollection(); + tail.plus(seq); + + while (!tail.collection()->empty()) { + Node head = Node::createCollection(); + + do { + head.collection()->push_back(tail.collection()->front()); + tail.collection()->pop_front(); + } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator())); + + newSeq.collection()->push_back(head); + } + + return newSeq; + } + + + static void getAndRemoveInitialOps(Node& seq, Node& ops) { + NodeDeque& seqCollection = *(seq.collection()); + NodeDeque& opsCollection = *(ops.collection()); + + while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) { + opsCollection.push_back(seqCollection.front()); + seqCollection.pop_front(); + } + } + + + static void getAndRemoveFinalOps(Node& seq, Node& ops) { + NodeDeque& seqCollection = *(seq.collection()); + NodeDeque& opsCollection = *(ops.collection()); + + while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) { + opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code + seqCollection.pop_back(); + } + } + + + /* + def merge_initial_ops(seq1, seq2) + ops1, ops2 = [], [] + ops1 << seq1.shift while seq1.first.is_a?(String) + ops2 << seq2.shift while seq2.first.is_a?(String) + + newline = false + newline ||= !!ops1.shift if ops1.first == "\n" + newline ||= !!ops2.shift if ops2.first == "\n" + + # If neither sequence is a subsequence of the other, they cannot be + # merged successfully + lcs = Sass::Util.lcs(ops1, ops2) + return unless lcs == ops1 || lcs == ops2 + return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) + end + */ + static Node mergeInitialOps(Node& seq1, Node& seq2, Context& ctx) { + Node ops1 = Node::createCollection(); + Node ops2 = Node::createCollection(); + + getAndRemoveInitialOps(seq1, ops1); + getAndRemoveInitialOps(seq2, ops2); + + // TODO: Do we have this information available to us? + // newline = false + // newline ||= !!ops1.shift if ops1.first == "\n" + // newline ||= !!ops2.shift if ops2.first == "\n" + + // If neither sequence is a subsequence of the other, they cannot be merged successfully + DefaultLcsComparator lcsDefaultComparator; + Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx); + + if (!(nodesEqual(opsLcs, ops1, true) || nodesEqual(opsLcs, ops2, true))) { + return Node::createNil(); + } + + // TODO: more newline logic + // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) + + return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2); + } + + + /* + def merge_final_ops(seq1, seq2, res = []) + + + # This code looks complicated, but it's actually just a bunch of special + # cases for interactions between different combinators. + op1, op2 = ops1.first, ops2.first + if op1 && op2 + sel1 = seq1.pop + sel2 = seq2.pop + if op1 == '~' && op2 == '~' + if sel1.superselector?(sel2) + res.unshift sel2, '~' + elsif sel2.superselector?(sel1) + res.unshift sel1, '~' + else + merged = sel1.unify(sel2.members, sel2.subject?) + res.unshift [ + [sel1, '~', sel2, '~'], + [sel2, '~', sel1, '~'], + ([merged, '~'] if merged) + ].compact + end + elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~') + if op1 == '~' + tilde_sel, plus_sel = sel1, sel2 + else + tilde_sel, plus_sel = sel2, sel1 + end + + if tilde_sel.superselector?(plus_sel) + res.unshift plus_sel, '+' + else + merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) + res.unshift [ + [tilde_sel, '~', plus_sel, '+'], + ([merged, '+'] if merged) + ].compact + end + elsif op1 == '>' && %w[~ +].include?(op2) + res.unshift sel2, op2 + seq1.push sel1, op1 + elsif op2 == '>' && %w[~ +].include?(op1) + res.unshift sel1, op1 + seq2.push sel2, op2 + elsif op1 == op2 + return unless merged = sel1.unify(sel2.members, sel2.subject?) + res.unshift merged, op1 + else + # Unknown selector combinators can't be unified + return + end + return merge_final_ops(seq1, seq2, res) + elsif op1 + seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last) + res.unshift seq1.pop, op1 + return merge_final_ops(seq1, seq2, res) + else # op2 + seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last) + res.unshift seq2.pop, op2 + return merge_final_ops(seq1, seq2, res) + end + end + */ + static Node mergeFinalOps(Node& seq1, Node& seq2, Context& ctx, Node& res) { + + Node ops1 = Node::createCollection(); + Node ops2 = Node::createCollection(); + + getAndRemoveFinalOps(seq1, ops1); + getAndRemoveFinalOps(seq2, ops2); + + // TODO: do we have newlines to remove? + // ops1.reject! {|o| o == "\n"} + // ops2.reject! {|o| o == "\n"} + + if (ops1.collection()->empty() && ops2.collection()->empty()) { + return res; + } + + if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) { + DefaultLcsComparator lcsDefaultComparator; + Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx); + + // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. + + if (!(nodesEqual(opsLcs, ops1, true) || nodesEqual(opsLcs, ops2, true))) { + return Node::createNil(); + } + + if (ops1.collection()->size() > ops2.collection()->size()) { + res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend()); + } else { + res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend()); + } + + return res; + } + + if (!ops1.collection()->empty() && !ops2.collection()->empty()) { + + Node op1 = ops1.collection()->front(); + Node op2 = ops2.collection()->front(); + + Node sel1 = seq1.collection()->back(); + seq1.collection()->pop_back(); + + Node sel2 = seq2.collection()->back(); + seq2.collection()->pop_back(); + + if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) { + + if (sel1.selector()->is_superselector_of(sel2.selector())) { + + res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); + res.collection()->push_front(sel2); + + } else if (sel2.selector()->is_superselector_of(sel1.selector())) { + + res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); + res.collection()->push_front(sel1); + + } else { + + DEBUG_PRINTLN(ALL, "sel1: " << sel1) + DEBUG_PRINTLN(ALL, "sel2: " << sel2) + + Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result + // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(&sel2.selector()->head(), ctx); + pMergedWrapper->head(pMerged); + + DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) + + Node newRes = Node::createCollection(); + + Node firstPerm = Node::createCollection(); + firstPerm.collection()->push_back(sel1); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + firstPerm.collection()->push_back(sel2); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + newRes.collection()->push_back(firstPerm); + + Node secondPerm = Node::createCollection(); + secondPerm.collection()->push_back(sel2); + secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + secondPerm.collection()->push_back(sel1); + secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + newRes.collection()->push_back(secondPerm); + + if (pMerged) { + Node mergedPerm = Node::createCollection(); + mergedPerm.collection()->push_back(Node::createSelector(&pMergedWrapper, ctx)); + mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + newRes.collection()->push_back(mergedPerm); + } + + res.collection()->push_front(newRes); + + DEBUG_PRINTLN(ALL, "RESULT: " << res) + + } + + } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) { + + Node tildeSel = sel1; + Node tildeOp = op1; + Node plusSel = sel2; + Node plusOp = op2; + if (op1.combinator() != Complex_Selector::PRECEDES) { + tildeSel = sel2; + tildeOp = op2; + plusSel = sel1; + plusOp = op1; + } + + if (tildeSel.selector()->is_superselector_of(plusSel.selector())) { + + res.collection()->push_front(plusOp); + res.collection()->push_front(plusSel); + + } else { + + DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel) + DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel) + + Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result + // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) + Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(&tildeSel.selector()->head(), ctx); + pMergedWrapper->head(pMerged); + + DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) + + Node newRes = Node::createCollection(); + + Node firstPerm = Node::createCollection(); + firstPerm.collection()->push_back(tildeSel); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + firstPerm.collection()->push_back(plusSel); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); + newRes.collection()->push_back(firstPerm); + + if (pMerged) { + Node mergedPerm = Node::createCollection(); + mergedPerm.collection()->push_back(Node::createSelector(&pMergedWrapper, ctx)); + mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); + newRes.collection()->push_back(mergedPerm); + } + + res.collection()->push_front(newRes); + + DEBUG_PRINTLN(ALL, "RESULT: " << res) + + } + } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) { + + res.collection()->push_front(op2); + res.collection()->push_front(sel2); + + seq1.collection()->push_back(sel1); + seq1.collection()->push_back(op1); + + } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) { + + res.collection()->push_front(op1); + res.collection()->push_front(sel1); + + seq2.collection()->push_back(sel2); + seq2.collection()->push_back(op2); + + } else if (op1.combinator() == op2.combinator()) { + + DEBUG_PRINTLN(ALL, "sel1: " << sel1) + DEBUG_PRINTLN(ALL, "sel2: " << sel2) + + Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result + // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(&sel2.selector()->head(), ctx); + pMergedWrapper->head(pMerged); + + DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) + + if (!pMerged) { + return Node::createNil(); + } + + res.collection()->push_front(op1); + res.collection()->push_front(Node::createSelector(&pMergedWrapper, ctx)); + + DEBUG_PRINTLN(ALL, "RESULT: " << res) + + } else { + return Node::createNil(); + } + + return mergeFinalOps(seq1, seq2, ctx, res); + + } else if (!ops1.collection()->empty()) { + + Node op1 = ops1.collection()->front(); + + if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) { + seq2.collection()->pop_back(); + } + + // TODO: consider unshift(NodeCollection, Node) + res.collection()->push_front(op1); + res.collection()->push_front(seq1.collection()->back()); + seq1.collection()->pop_back(); + + return mergeFinalOps(seq1, seq2, ctx, res); + + } else { // !ops2.collection()->empty() + + Node op2 = ops2.collection()->front(); + + if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) { + seq1.collection()->pop_back(); + } + + res.collection()->push_front(op2); + res.collection()->push_front(seq2.collection()->back()); + seq2.collection()->pop_back(); + + return mergeFinalOps(seq1, seq2, ctx, res); + + } + + } + + + /* + This is the equivalent of ruby's Sequence.subweave. + + Here is the original subweave code for reference during porting. + + def subweave(seq1, seq2) + return [seq2] if seq1.empty? + return [seq1] if seq2.empty? + + seq1, seq2 = seq1.dup, seq2.dup + return unless init = merge_initial_ops(seq1, seq2) + return unless fin = merge_final_ops(seq1, seq2) + seq1 = group_selectors(seq1) + seq2 = group_selectors(seq2) + lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2| + next s1 if s1 == s2 + next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) + next s2 if parent_superselector?(s1, s2) + next s1 if parent_superselector?(s2, s1) + end + + diff = [[init]] + until lcs.empty? + diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift] + seq1.shift + seq2.shift + end + diff << chunks(seq1, seq2) {|s| s.empty?} + diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} + diff.reject! {|c| c.empty?} + + result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)} + + result + end + */ + Node Extend::subweave(Node& one, Node& two, Context& ctx) { + // Check for the simple cases + if (one.collection()->size() == 0) { + Node out = Node::createCollection(); + out.collection()->push_back(two); + return out; + } + if (two.collection()->size() == 0) { + Node out = Node::createCollection(); + out.collection()->push_back(one); + return out; + } + + + + Node seq1 = Node::createCollection(); + seq1.plus(one); + Node seq2 = Node::createCollection(); + seq2.plus(two); + + DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1) + DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) + + Node init = mergeInitialOps(seq1, seq2, ctx); + if (init.isNil()) { + return Node::createNil(); + } + + DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init) + + Node res = Node::createCollection(); + Node fin = mergeFinalOps(seq1, seq2, ctx, res); + if (fin.isNil()) { + return Node::createNil(); + } + + DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin) + + + // Moving this line up since fin isn't modified between now and when it happened before + // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} + + for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end(); + finIter != finEndIter; ++finIter) { + + Node& childNode = *finIter; + + if (!childNode.isCollection()) { + Node wrapper = Node::createCollection(); + wrapper.collection()->push_back(childNode); + childNode = wrapper; + } + + } + + DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin) + + + + Node groupSeq1 = groupSelectors(seq1, ctx); + DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) + + Node groupSeq2 = groupSelectors(seq2, ctx); + DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2) + + + ComplexSelectorDeque groupSeq1Converted; + nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted, ctx); + + ComplexSelectorDeque groupSeq2Converted; + nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted, ctx); + + ComplexSelectorDeque out; + LcsCollectionComparator collectionComparator(ctx); + lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, ctx, out); + Node seqLcs = complexSelectorDequeToNode(out, ctx); + + DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) + + + Node initWrapper = Node::createCollection(); + initWrapper.collection()->push_back(init); + Node diff = Node::createCollection(); + diff.collection()->push_back(initWrapper); + + DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff) + + + while (!seqLcs.collection()->empty()) { + ParentSuperselectorChunker superselectorChunker(seqLcs, ctx); + Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker); + diff.collection()->push_back(chunksResult); + + Node lcsWrapper = Node::createCollection(); + lcsWrapper.collection()->push_back(seqLcs.collection()->front()); + seqLcs.collection()->pop_front(); + diff.collection()->push_back(lcsWrapper); + + if (groupSeq1.collection()->size()) groupSeq1.collection()->pop_front(); + if (groupSeq2.collection()->size()) groupSeq2.collection()->pop_front(); + } + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff) + + + DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2) + + + SubweaveEmptyChunker emptyChunker; + Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker); + diff.collection()->push_back(chunksResult); + + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff) + + + diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end()); + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff) + + // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection) + Node diffFiltered = Node::createCollection(); + for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end(); + diffIter != diffEndIter; ++diffIter) { + Node& node = *diffIter; + if (node.collection() && !node.collection()->empty()) { + diffFiltered.collection()->push_back(node); + } + } + diff = diffFiltered; + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff) + + + Node pathsResult = paths(diff, ctx); + + DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) + + + // We're flattening in place + for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end(); + pathsIter != pathsEndIter; ++pathsIter) { + + Node& child = *pathsIter; + child = flatten(child, ctx); + } + + DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult) + + + /* + TODO: implement + rejected = mapped.reject {|p| path_has_two_subjects?(p)} + $stderr.puts "REJECTED: #{rejected}" + */ + + + return pathsResult; + + } + /* + // disabled to avoid clang warning [-Wunused-function] + static Node subweaveNaive(const Node& one, const Node& two, Context& ctx) { + Node out = Node::createCollection(); + + // Check for the simple cases + if (one.isNil()) { + out.collection()->push_back(two.klone(ctx)); + } else if (two.isNil()) { + out.collection()->push_back(one.klone(ctx)); + } else { + // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B + // See https://gist.github.com/nex3/7609394 for details. + + Node firstPerm = one.klone(ctx); + Node twoCloned = two.klone(ctx); + firstPerm.plus(twoCloned); + out.collection()->push_back(firstPerm); + + Node secondPerm = two.klone(ctx); + Node oneCloned = one.klone(ctx); + secondPerm.plus(oneCloned ); + out.collection()->push_back(secondPerm); + } + + return out; + } + */ + + + /* + This is the equivalent of ruby's Sequence.weave. + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def weave(path) + # This function works by moving through the selector path left-to-right, + # building all possible prefixes simultaneously. These prefixes are + # `befores`, while the remaining parenthesized suffixes is `afters`. + befores = [[]] + afters = path.dup + + until afters.empty? + current = afters.shift.dup + last_current = [current.pop] + + tempResult = [] + + for before in befores do + sub = subweave(before, current) + if sub.nil? + next + end + + for seqs in sub do + tempResult.push(seqs + last_current) + end + end + + befores = tempResult + + end + + return befores + end + */ + /* + def weave(path) + befores = [[]] + afters = path.dup + + until afters.empty? + current = afters.shift.dup + + last_current = [current.pop] + + + tempResult = [] + + for before in befores do + sub = subweave(before, current) + + if sub.nil? + next [] + end + + + for seqs in sub do + toPush = seqs + last_current + + tempResult.push(seqs + last_current) + end + + end + + befores = tempResult + + end + + return befores + end + */ + static Node weave(Node& path, Context& ctx) { + + DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) + + Node befores = Node::createCollection(); + befores.collection()->push_back(Node::createCollection()); + + Node afters = Node::createCollection(); + afters.plus(path); + + while (!afters.collection()->empty()) { + Node current = afters.collection()->front().klone(ctx); + afters.collection()->pop_front(); + DEBUG_PRINTLN(WEAVE, "CURRENT: " << current) + if (current.collection()->size() == 0) continue; + + Node last_current = Node::createCollection(); + last_current.collection()->push_back(current.collection()->back()); + current.collection()->pop_back(); + DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current) + DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current) + + Node tempResult = Node::createCollection(); + + for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { + Node& before = *beforesIter; + + Node sub = Extend::subweave(before, current, ctx); + + DEBUG_PRINTLN(WEAVE, "SUB: " << sub) + + if (sub.isNil()) { + return Node::createCollection(); + } + + for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) { + Node& seqs = *subIter; + + Node toPush = Node::createCollection(); + toPush.plus(seqs); + toPush.plus(last_current); + + tempResult.collection()->push_back(toPush); + + } + } + + befores = tempResult; + + } + + return befores; + } + + + + // This forward declaration is needed since extendComplexSelector calls extendCompoundSelector, which may recursively + // call extendComplexSelector again. + static Node extendComplexSelector( + Complex_Selector_Ptr pComplexSelector, + Context& ctx, + Subset_Map& subset_map, + std::set seen, bool isReplace, bool isOriginal); + + + + /* + This is the equivalent of ruby's SimpleSequence.do_extend. + + // TODO: I think I have some modified ruby code to put here. Check. + */ + /* + ISSUES: + - Previous TODO: Do we need to group the results by extender? + - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?) + - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything... + - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing... + */ + template + class GroupByToAFunctor { + public: + KeyType operator()(ExtensionPair& extPair) const { + Complex_Selector_Obj pSelector = extPair.first; + return &pSelector; + } + }; + static Node extendCompoundSelector( + Compound_Selector_Ptr pSelector, + Context& ctx, + Subset_Map& subset_map, + std::set seen, bool isReplace) { + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: ")) + // TODO: Ruby has another loop here to skip certain members? + + Node extendedSelectors = Node::createCollection(); + // extendedSelectors.got_line_feed = true; + + SubsetMapEntries entries = subset_map.get_v(pSelector); + + typedef std::vector > > GroupedByToAResult; + + GroupByToAFunctor extPairKeyFunctor; + GroupedByToAResult arr; + group_by_to_a(entries, extPairKeyFunctor, arr); + + typedef std::pair SelsNewSeqPair; + typedef std::vector SelsNewSeqPairCollection; + + + SelsNewSeqPairCollection holder; + + + for (GroupedByToAResult::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) { + std::pair >& groupedPair = *groupedIter; + + Complex_Selector_Obj seq = groupedPair.first; + std::vector& group = groupedPair.second; + + DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(&seq, "SEQ: ")) + +// changing this makes aua + Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); + for (std::vector::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) { + ExtensionPair& pair = *groupIter; + Compound_Selector_Obj pCompound = pair.second; + for (size_t index = 0; index < pCompound->length(); index++) { + Simple_Selector_Obj pSimpleSelector = (*pCompound)[index]; + pSels->append(&pSimpleSelector); + pCompound->extended(true); + } + } + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(&pSels, "SELS: ")) + + Complex_Selector_Ptr pExtComplexSelector = &seq; // The selector up to where the @extend is (ie, the thing to merge) + + // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? + // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) + Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(&pSels, ctx); + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) + + Compound_Selector_Obj pInnermostCompoundSelector = pExtComplexSelector->last()->head(); + + if (!pInnermostCompoundSelector) { + pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); + } + Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(&pSelectorWithoutExtendSelectors, ctx); + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: ")) + + // RUBY: next unless unified + if (!pUnifiedSelector || pUnifiedSelector->length() == 0) { + continue; + } + + // TODO: implement the parent directive match (if necessary based on test failures) + // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none? + + // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just + // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more + // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered + // out and aren't operated on. + Complex_Selector_Obj pNewSelector = SASS_MEMORY_CLONE(pExtComplexSelector); // ->first(); + + Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); + + Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); + pNewSelector->set_innermost(&pNewInnerMost, combinator); + +#ifdef DEBUG + SourcesSet debugSet; + debugSet = pNewSelector->sources(); + if (debugSet.size() > 0) { + throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this."); + } + debugSet = pExtComplexSelector->sources(); + if (debugSet.size() > 0) { + throw std::runtime_error("The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this."); + } +#endif + + + // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true); + // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. + DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector, ctx)) + + DEBUG_EXEC(EXTEND_COMPOUND, SourcesSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: ")) + + SourcesSet newSourcesSet = pSelector->sources(); + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES THIS EXTEND: ")) + + newSourcesSet.insert(pExtComplexSelector); + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES WITH NEW SOURCE: ")) + + // RUBY: new_seq.add_sources!(sources + [seq]) + pNewSelector->addSources(newSourcesSet, ctx); + + DEBUG_EXEC(EXTEND_COMPOUND, SourcesSet newSet = pNewSelector->sources(); printSourcesSet(newSet, ctx, "SOURCES ON NEW SELECTOR AFTER ADD: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), ctx, "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) + + + if (pSels->has_line_feed()) pNewSelector->has_line_feed(true); + + holder.push_back(std::make_pair(pSels, pNewSelector)); + } + + + for (SelsNewSeqPairCollection::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) { + SelsNewSeqPair& pair = *holderIter; + + Compound_Selector_Obj pSels = pair.first; + Complex_Selector_Obj pNewSelector = pair.second; + + + // RUBY??: next [] if seen.include?(sels) + if (seen.find(*pSels) != seen.end()) { + continue; + } + + + std::set recurseSeen(seen); + recurseSeen.insert(*pSels); + + + DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector, ctx)) + Node recurseExtendedSelectors = extendComplexSelector(&pNewSelector, ctx, subset_map, recurseSeen, isReplace, false); // !:isOriginal + + DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) + + for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end(); + iterator != endIterator; ++iterator) { + Node newSelector = *iterator; + +// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << extendedSelectors) +// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)); + + if (!extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)) { +// DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR") + extendedSelectors.collection()->push_back(newSelector); + } + } + } + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) + + return extendedSelectors; + } + + + static bool complexSelectorHasExtension( + Complex_Selector_Ptr pComplexSelector, + Context& ctx, + Subset_Map& subset_map, + std::set& seen) { + + bool hasExtension = false; + + Complex_Selector_Obj pIter = pComplexSelector; + + while (!hasExtension && pIter) { + Compound_Selector_Obj pHead = pIter->head(); + + if (pHead) { + SubsetMapEntries entries = subset_map.get_v(pHead); + for (ExtensionPair ext : entries) { + // check if both selectors have the same media block parent + // if (ext.first->media_block() == pComplexSelector->media_block()) continue; + if (ext.second->media_block() == 0) continue; + if (pHead->media_block() && + ext.second->media_block()->media_queries() && + pHead->media_block()->media_queries() + ) { + std::string query_left(ext.second->media_block()->media_queries()->to_string(ctx.c_options)); + std::string query_right(pHead->media_block()->media_queries()->to_string(ctx.c_options)); + if (query_left == query_right) continue; + } + + // fail if one goes across media block boundaries + std::stringstream err; + std::string cwd(Sass::File::get_cwd()); + ParserState pstate(ext.second->pstate()); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + err << "You may not @extend an outer selector from within @media.\n"; + err << "You may only @extend selectors within the same directive.\n"; + err << "From \"@extend " << ext.second->to_string(ctx.c_options) << "\""; + err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; + error(err.str(), pComplexSelector->pstate()); + } + if (entries.size() > 0) hasExtension = true; + } + + pIter = pIter->tail(); + } + + return hasExtension; + } + + + /* + This is the equivalent of ruby's Sequence.do_extend. + + // TODO: I think I have some modified ruby code to put here. Check. + */ + /* + ISSUES: + - check to automatically include combinators doesn't transfer over to libsass' data model where + the combinator and compound selector are one unit + next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) + */ + static Node extendComplexSelector( + Complex_Selector_Ptr pComplexSelector, + Context& ctx, + Subset_Map& subset_map, + std::set seen, bool isReplace, bool isOriginal) { + + Node complexSelector = complexSelectorToNode(pComplexSelector, ctx); + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) + + Node extendedNotExpanded = Node::createCollection(); + + for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(), + complexSelIterEnd = complexSelector.collection()->end(); + complexSelIter != complexSelIterEnd; ++complexSelIter) + { + + Node& sseqOrOp = *complexSelIter; + + DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) + + // If it's not a selector (meaning it's a combinator), just include it automatically + // RUBY: next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) + if (!sseqOrOp.isSelector()) { + // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node + // with one collection child. The collection child represents a Complex_Selector that is only a combinator. + Node outer = Node::createCollection(); + Node inner = Node::createCollection(); + outer.collection()->push_back(inner); + inner.collection()->push_back(sseqOrOp); + extendedNotExpanded.collection()->push_back(outer); + continue; + } + + Compound_Selector_Obj pCompoundSelector = sseqOrOp.selector()->head(); + + // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) + Node extended = extendCompoundSelector(&pCompoundSelector, ctx, subset_map, seen, isReplace); + if (sseqOrOp.got_line_feed) extended.got_line_feed = true; + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) + + + // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with an ruby Array instead of a Sequence + // due to the member mapping: choices = extended.map {|seq| seq.members} + Complex_Selector_Obj pJustCurrentCompoundSelector = sseqOrOp.selector(); + + // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? + if (isOriginal && !pComplexSelector->has_placeholder()) { + SourcesSet srcset; + srcset.insert(pComplexSelector); + pJustCurrentCompoundSelector->addSources(srcset, ctx); + DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) + } + + bool isSuperselector = false; + for (NodeDeque::iterator iterator = extended.collection()->begin(), endIterator = extended.collection()->end(); + iterator != endIterator; ++iterator) { + Node& childNode = *iterator; + Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode, ctx); + if (pExtensionSelector->is_superselector_of(pJustCurrentCompoundSelector)) { + isSuperselector = true; + break; + } + } + + if (!isSuperselector) { + if (sseqOrOp.got_line_feed) pJustCurrentCompoundSelector->has_line_feed(sseqOrOp.got_line_feed); + extended.collection()->push_front(complexSelectorToNode(&pJustCurrentCompoundSelector, ctx)); + } + + DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) + + // Aggregate our current extensions + extendedNotExpanded.collection()->push_back(extended); + } + + + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << extendedNotExpanded) + + + + // Ruby Equivalent: paths + Node paths = Sass::paths(extendedNotExpanded, ctx); + + DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) + + + + // Ruby Equivalent: weave + Node weaves = Node::createCollection(); + + for (NodeDeque::iterator pathsIter = paths.collection()->begin(), pathsEndIter = paths.collection()->end(); pathsIter != pathsEndIter; ++pathsIter) { + Node& path = *pathsIter; + Node weaved = weave(path, ctx); + weaved.got_line_feed = path.got_line_feed; + weaves.collection()->push_back(weaved); + } + + DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) + + + + // Ruby Equivalent: trim + Node trimmed = trim(weaves, ctx, isReplace); + + DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) + + + // Ruby Equivalent: flatten + Node extendedSelectors = flatten(trimmed, ctx, 1); + + DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors) + + + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) + + + return extendedSelectors; + } + + + + /* + This is the equivalent of ruby's CommaSequence.do_extend. + */ + Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething) { + std::set seen; + return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen); + } + + /* + This is the equivalent of ruby's CommaSequence.do_extend. + */ + Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set& seen) { + + Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); + + extendedSomething = false; + + for (size_t index = 0, length = pSelectorList->length(); index < length; index++) { + Complex_Selector_Obj pSelector = (*pSelectorList)[index]; + + // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that. + // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to + // run through the extend code (which does a data model transformation), check if there is anything to extend before doing + // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps + // when debugging). + if (!complexSelectorHasExtension(&pSelector, ctx, subset_map, seen)) { + pNewSelectors->append(&pSelector); + continue; + } + + extendedSomething = true; + + Node extendedSelectors = extendComplexSelector(&pSelector, ctx, subset_map, seen, isReplace, true); + if (!pSelector->has_placeholder()) { + if (!extendedSelectors.contains(complexSelectorToNode(&pSelector, ctx), true /*simpleSelectorOrderDependent*/)) { + pNewSelectors->append(pSelector); + continue; + } + } + + for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorBegin = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) { + // When it is a replace, skip the first one, unless there is only one + if(isReplace && iterator == iteratorBegin && extendedSelectors.collection()->size() > 1 ) continue; + + Node& childNode = *iterator; + pNewSelectors->append(nodeToComplexSelector(childNode, ctx)); + } + } + + Remove_Placeholders remove_placeholders(ctx); + // it seems that we have to remove the place holders early here + // normally we do this as the very last step (compare to ruby sass) + pNewSelectors = remove_placeholders.remove_placeholders(&pNewSelectors); + + // unwrap all wrapped selectors with inner lists + for (Complex_Selector_Obj cur : pNewSelectors->elements()) { + // process tails + while (cur) { + // process header + if (cur->head() && seen.find(*cur->head()) == seen.end()) { + std::set recseen(seen); + recseen.insert(*cur->head()); + // create a copy since we add multiple items if stuff get unwrapped + Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); + for (Simple_Selector_Obj hs : *cur->head()) { + if (Wrapped_Selector_Obj ws = SASS_MEMORY_CAST(Wrapped_Selector, hs)) { + ws->selector(SASS_MEMORY_CLONE(ws->selector())); + if (Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) { + // special case for ruby ass + if (sl->empty()) { + // this seems inconsistent but it is how ruby sass seems to remove parentheses + cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name())); + } + // has wrapped selectors + else { + // extend the inner list of wrapped selector + Selector_List_Obj ext_sl = extendSelectorList(sl, ctx, subset_map, recseen); + for (size_t i = 0; i < ext_sl->length(); i += 1) { + if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { + // create clones for wrapped selector and the inner list + Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(&ws); + Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); + // remove parent selectors from inner selector + if (ext_cs->first() && ext_cs->first()->head()->length() > 0) { + Wrapped_Selector_Ptr ext_ws = SASS_MEMORY_CAST(Wrapped_Selector, ext_cs->first()->head()->first()); + if (ext_ws/* && ext_cs->length() == 1*/) { + Selector_List_Obj ws_cs = SASS_MEMORY_CAST(Selector_List, ext_ws->selector()); + Compound_Selector_Obj ws_ss = ws_cs->first()->head(); + if (!( + SASS_MEMORY_CAST(Pseudo_Selector, ws_ss->first()) || + SASS_MEMORY_CAST(Element_Selector, ws_ss->first()) || + SASS_MEMORY_CAST(Placeholder_Selector, ws_ss->first()) + )) continue; + } + cpy_ws_sl->append(ext_cs->first()); + } + // assign list to clone + cpy_ws->selector(&cpy_ws_sl); + // append the clone + cpy_head->append(&cpy_ws); + } + } + } + } else { + cpy_head->append(&hs); + } + } else { + cpy_head->append(&hs); + } + } + // replace header + cur->head(cpy_head); + } + // process tail + cur = cur->tail(); + } + } + return pNewSelectors.detach(); + + } + + + bool shouldExtendBlock(Block_Obj b) { + + // If a block is empty, there's no reason to extend it since any rules placed on this block + // won't have any output. The main benefit of this is for structures like: + // + // .a { + // .b { + // x: y; + // } + // } + // + // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b). + // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since + // there are no child statements. However .a .b should have extensions applied. + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + + if (dynamic_cast(&stm)) { + // Do nothing. This doesn't count as a statement that causes extension since we'll iterate over this rule set in a future visit and try to extend it. + } + else { + return true; + } + } + + return false; + + } + + + // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. + template + static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, Subset_Map& subset_map) { + + DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << static_cast(pObject->selector())->to_string(ctx.c_options)) + + // Ruby sass seems to filter nodes that don't have any content well before we get here. I'm not sure the repercussions + // of doing so, so for now, let's just not extend things that won't be output later. + if (!shouldExtendBlock(pObject->block())) { + DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT") + return; + } + + bool extendedSomething = false; + Selector_List_Obj pNewSelectorList = Extend::extendSelectorList(SASS_MEMORY_CAST(Selector_List, pObject->selector()), ctx, subset_map, false, extendedSomething); + + if (extendedSomething && pNewSelectorList) { + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << static_cast(pObject->selector())->to_string(ctx.c_options)) + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string(ctx.c_options)) + pNewSelectorList->remove_parent_selectors(); + pObject->selector(&pNewSelectorList); + } else { + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING") + } + } + + + + Extend::Extend(Context& ctx, Subset_Map& ssm) + : ctx(ctx), subset_map(ssm) + { } + + void Extend::operator()(Block_Ptr b) + { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + } + // do final check if everything was extended + // we set `extended` flag on extended selectors + if (b->is_root()) { + // debug_subset_map(subset_map); + for(auto const &it : subset_map.values()) { + Complex_Selector_Ptr sel = it.first ? &it.first->first() : NULL; + Compound_Selector_Ptr ext = it.second ? &it.second : NULL; + if (ext && (ext->extended() || ext->is_optional())) continue; + std::string str_sel(sel->to_string({ NESTED, 5 })); + std::string str_ext(ext->to_string({ NESTED, 5 })); + // debug_ast(sel, "sel: "); + // debug_ast(ext, "ext: "); + error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" + "The selector \"" + str_ext + "\" was not found.\n" + "Use \"@extend " + str_ext + " !optional\" if the" + " extend should be able to fail.", ext->pstate()); + } + } + + } + + void Extend::operator()(Ruleset_Ptr pRuleset) + { + extendObjectWithSelectorAndBlock( pRuleset, ctx, subset_map); + pRuleset->block()->perform(this); + } + + void Extend::operator()(Supports_Block_Ptr pFeatureBlock) + { + pFeatureBlock->block()->perform(this); + } + + void Extend::operator()(Media_Block_Ptr pMediaBlock) + { + pMediaBlock->block()->perform(this); + } + + void Extend::operator()(Directive_Ptr a) + { + // Selector_List_Ptr ls = dynamic_cast(a->selector()); + // selector_stack.push_back(ls); + if (a->block()) a->block()->perform(this); + // exp.selector_stack.pop_back(); + } +} diff --git a/src/libsass/src/extend.hpp b/src/libsass/src/extend.hpp new file mode 100755 index 000000000..a785d1fb7 --- /dev/null +++ b/src/libsass/src/extend.hpp @@ -0,0 +1,51 @@ +#ifndef SASS_EXTEND_H +#define SASS_EXTEND_H + +#include +#include + +#include "ast.hpp" +#include "operation.hpp" +#include "subset_map.hpp" + +namespace Sass { + + class Context; + class Node; + + class Extend : public Operation_CRTP { + + Context& ctx; + Subset_Map& subset_map; + + void fallback_impl(AST_Node_Ptr n) { } + + public: + static Node subweave(Node& one, Node& two, Context& ctx); + static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set& seen); + static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething); + static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace = false) { + bool extendedSomething = false; + return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething); + } + static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, std::set& seen) { + bool isReplace = false; + bool extendedSomething = false; + return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen); + } + Extend(Context&, Subset_Map&); + ~Extend() { } + + void operator()(Block_Ptr); + void operator()(Ruleset_Ptr); + void operator()(Supports_Block_Ptr); + void operator()(Media_Block_Ptr); + void operator()(Directive_Ptr); + + template + void fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp new file mode 100755 index 000000000..8b2d19498 --- /dev/null +++ b/src/libsass/src/file.cpp @@ -0,0 +1,418 @@ +#ifdef _WIN32 +# ifdef __MINGW32__ +# ifndef off64_t +# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ +# endif +# endif +# include +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#else +# include +#endif +#include "sass.hpp" +#include +#include +#include +#include +#include +#include +#include "file.hpp" +#include "context.hpp" +#include "prelexer.hpp" +#include "utf8_string.hpp" +#include "sass2scss.h" + +#ifdef _WIN32 +# include + +# ifdef _MSC_VER +# include +inline static std::string wstring_to_string(const std::wstring& wstr) +{ + std::wstring_convert, wchar_t> wchar_converter; + return wchar_converter.to_bytes(wstr); +} +# else // mingw(/gcc) does not support C++11's codecvt yet. +inline static std::string wstring_to_string(const std::wstring &wstr) +{ + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} +# endif +#endif + +namespace Sass { + namespace File { + + // return the current directory + // always with forward slashes + std::string get_cwd() + { + const size_t wd_len = 1024; + #ifndef _WIN32 + char wd[wd_len]; + std::string cwd = getcwd(wd, wd_len); + #else + wchar_t wd[wd_len]; + std::string cwd = wstring_to_string(_wgetcwd(wd, wd_len)); + //convert backslashes to forward slashes + replace(cwd.begin(), cwd.end(), '\\', '/'); + #endif + if (cwd[cwd.length() - 1] != '/') cwd += '/'; + return cwd; + } + + // test if path exists and is a file + bool file_exists(const std::string& path) + { + #ifdef _WIN32 + std::wstring wpath = UTF_8::convert_to_utf16(path); + DWORD dwAttrib = GetFileAttributesW(wpath.c_str()); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + #else + struct stat st_buf; + return (stat (path.c_str(), &st_buf) == 0) && + (!S_ISDIR (st_buf.st_mode)); + #endif + } + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path) + { + #ifdef _WIN32 + if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; + #endif + size_t i = 0; + // check if we have a protocol + if (path[i] && Prelexer::is_alpha(path[i])) { + // skip over all alphanumeric characters + while (path[i] && Prelexer::is_alnum(path[i])) ++i; + i = i && path[i] == ':' ? i + 1 : 0; + } + return path[i] == '/'; + } + + // helper function to find the last directory seperator + inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) + { + size_t pos = std::string::npos; + size_t pos_p = path.find_last_of('/', limit); + #ifdef _WIN32 + size_t pos_w = path.find_last_of('\\', limit); + #else + size_t pos_w = std::string::npos; + #endif + if (pos_p != std::string::npos && pos_w != std::string::npos) { + pos = std::max(pos_p, pos_w); + } + else if (pos_p != std::string::npos) { + pos = pos_p; + } + else { + pos = pos_w; + } + return pos; + } + + // return only the directory part of path + std::string dir_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return ""; + else return path.substr(0, pos+1); + } + + // return only the filename part of path + std::string base_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return path; + else return path.substr(pos+1); + } + + // do a logical clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path) + { + + // declarations + size_t pos; + + #ifdef _WIN32 + //convert backslashes to forward slashes + replace(path.begin(), path.end(), '\\', '/'); + #endif + + pos = 0; // remove all self references inside the path string + while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); + + pos = 0; // remove all leading and trailing self references + while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); + while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); + + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // then skip over start slashes + while (path[proto++] == '/') {} + + pos = proto; // collapse multiple delimiters into a single one + while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1); + + return path; + + } + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string l, std::string r) + { + + #ifdef _WIN32 + // convert Windows backslashes to URL forward slashes + replace(l.begin(), l.end(), '\\', '/'); + replace(r.begin(), r.end(), '\\', '/'); + #endif + + if (l.empty()) return r; + if (r.empty()) return l; + + if (is_absolute_path(r)) return r; + if (l[l.length()-1] != '/') l += '/'; + + while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { + size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); + bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); + bool is_self = pos + 3 == L && (l[pos+1] == '.'); + if (!is_self && !is_slash) r = r.substr(3); + else if (pos == std::string::npos) break; + l = l.substr(0, pos == std::string::npos ? pos : pos + 1); + } + + return l + r; + } + + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path) + { + // magic algorith goes here!! + + // if the file is outside this directory show the absolute path + if (rel_path.substr(0, 3) == "../") { + return orig_path; + } + // this seems to work most of the time + return abs_path == orig_path ? abs_path : rel_path; + } + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd) + { + return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); + } + + // create a path that is relative to the given base directory + // path and base will first be resolved against cwd to make them absolute + std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd) + { + + std::string abs_path = rel2abs(path, cwd); + std::string abs_base = rel2abs(base, cwd); + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // distinguish between windows absolute paths and valid protocols + // we assume that protocols must at least have two chars to be valid + if (proto && path[proto++] == '/' && proto > 3) return path; + + #ifdef _WIN32 + // absolute link must have a drive letter, and we know that we + // can only create relative links if both are on the same drive + if (abs_base[0] != abs_path[0]) return abs_path; + #endif + + std::string stripped_uri = ""; + std::string stripped_base = ""; + + size_t index = 0; + size_t minSize = std::min(abs_path.size(), abs_base.size()); + for (size_t i = 0; i < minSize; ++i) { + #ifdef FS_CASE_SENSITIVE + if (abs_path[i] != abs_base[i]) break; + #else + // compare the charactes in a case insensitive manner + // windows fs is only case insensitive in ascii ranges + if (tolower(abs_path[i]) != tolower(abs_base[i])) break; + #endif + if (abs_path[i] == '/') index = i + 1; + } + for (size_t i = index; i < abs_path.size(); ++i) { + stripped_uri += abs_path[i]; + } + for (size_t i = index; i < abs_base.size(); ++i) { + stripped_base += abs_base[i]; + } + + size_t left = 0; + size_t directories = 0; + for (size_t right = 0; right < stripped_base.size(); ++right) { + if (stripped_base[right] == '/') { + if (stripped_base.substr(left, 2) != "..") { + ++directories; + } + else if (directories > 1) { + --directories; + } + else { + directories = 0; + } + left = right + 1; + } + } + + std::string result = ""; + for (size_t i = 0; i < directories; ++i) { + result += "../"; + } + result += stripped_uri; + + return result; + } + + // Resolution order for ambiguous imports: + // (1) filename as given + // (2) underscore + given + // (3) underscore + given + extension + // (4) given + extension + std::vector resolve_includes(const std::string& root, const std::string& file) + { + std::string filename = join_paths(root, file); + // supported extensions + const std::vector exts = { + ".scss", ".sass", ".css" + }; + // split the filename + std::string base(dir_name(file)); + std::string name(base_name(file)); + std::vector includes; + // create full path (maybe relative) + std::string rel_path(join_paths(base, name)); + std::string abs_path(join_paths(root, rel_path)); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test variation with underscore + rel_path = join_paths(base, "_" + name); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test exts plus underscore + for(auto ext : exts) { + rel_path = join_paths(base, "_" + name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + } + // next test plain name with exts + for(auto ext : exts) { + rel_path = join_paths(base, name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + } + // nothing found + return includes; + } + + // helper function to resolve a filename + std::string find_file(const std::string& file, const std::vector paths) + { + // search in every include path for a match + for (size_t i = 0, S = paths.size(); i < S; ++i) + { + std::vector resolved(resolve_includes(paths[i], file)); + if (resolved.size()) return resolved[0].abs_path; + } + // nothing found + return std::string(""); + } + + // inc paths can be directly passed from C code + std::string find_file(const std::string& file, const char* paths[]) + { + if (paths == 0) return std::string(""); + std::vector includes(0); + // includes.push_back("."); + const char** it = paths; + while (it && *it) { + includes.push_back(*it); + ++it; + } + return find_file(file, includes); + } + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& path) + { + #ifdef _WIN32 + BYTE* pBuffer; + DWORD dwBytes; + // windows unicode filepaths are encoded in utf16 + std::wstring wpath = UTF_8::convert_to_utf16(path); + HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + DWORD dwFileLength = GetFileSize(hFile, NULL); + if (dwFileLength == INVALID_FILE_SIZE) return 0; + // allocate an extra byte for the null char + pBuffer = (BYTE*)malloc((dwFileLength+1)*sizeof(BYTE)); + ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); + pBuffer[dwFileLength] = '\0'; + CloseHandle(hFile); + // just convert from unsigned char* + char* contents = (char*) pBuffer; + #else + struct stat st; + if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0; + std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + char* contents = 0; + if (file.is_open()) { + size_t size = file.tellg(); + // allocate an extra byte for the null char + contents = (char*) malloc((size+1)*sizeof(char)); + file.seekg(0, std::ios::beg); + file.read(contents, size); + contents[size] = '\0'; + file.close(); + } + #endif + std::string extension; + if (path.length() > 5) { + extension = path.substr(path.length() - 5, 5); + } + for(size_t i=0; i +#include + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + namespace File { + + // return the current directory + // always with forward slashes + std::string get_cwd(); + + // test if path exists and is a file + bool file_exists(const std::string& file); + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path); + + // return only the directory part of path + std::string dir_name(const std::string& path); + + // return only the filename part of path + std::string base_name(const std::string&); + + // do a locigal clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path); + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string root, std::string name); + + // if the relative path is outside of the cwd we want want to + // show the absolute path in console messages + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path); + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // create a path that is relative to the given base directory + // path and base will first be resolved against cwd to make them absolute + std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // helper function to resolve a filename + std::string find_file(const std::string& file, const std::vector paths); + // inc paths can be directly passed from C code + std::string find_file(const std::string& file, const char** paths); + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& file); + + } + + // requested import + class Importer { + public: + // requested import path + std::string imp_path; + // parent context path + std::string ctx_path; + // base derived from context path + // this really just acts as a cache + std::string base_path; + public: + Importer(std::string imp_path, std::string ctx_path) + : imp_path(File::make_canonical_path(imp_path)), + ctx_path(File::make_canonical_path(ctx_path)), + base_path(File::dir_name(ctx_path)) + { } + }; + + // a resolved include (final import) + class Include : public Importer { + public: + // resolved absolute path + std::string abs_path; + public: + Include(const Importer& imp, std::string abs_path) + : Importer(imp), abs_path(abs_path) + { } + }; + + // a loaded resource + class Resource { + public: + // the file contents + char* contents; + // conected sourcemap + char* srcmap; + public: + Resource(char* contents, char* srcmap) + : contents(contents), srcmap(srcmap) + { } + }; + + // parsed stylesheet from loaded resource + class StyleSheet : public Resource { + public: + // parsed root block + Block_Obj root; + public: + StyleSheet(const Resource& res, Block_Obj root) + : Resource(res), root(root) + { } + }; + + namespace File { + + std::vector resolve_includes(const std::string& root, const std::string& file); + + } + +} + +#endif diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp new file mode 100755 index 000000000..fcbd6cc59 --- /dev/null +++ b/src/libsass/src/functions.cpp @@ -0,0 +1,1985 @@ +#include "sass.hpp" +#include "functions.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "parser.hpp" +#include "constants.hpp" +#include "inspect.hpp" +#include "extend.hpp" +#include "eval.hpp" +#include "util.hpp" +#include "expand.hpp" +#include "utf8_string.hpp" +#include "sass/base.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, backtrace) +#define ARGR(argname, argtype, lo, hi) get_arg_r(argname, env, sig, pstate, lo, hi, backtrace) +#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, backtrace, ctx) + +namespace Sass { + using std::stringstream; + using std::endl; + + Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) + { + Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[built-in function]")); + sig_parser.lex(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + sig, + name, + params, + func, + false); + } + + Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) + { + using namespace Prelexer; + + const char* sig = sass_function_get_signature(c_func); + Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[c function]")); + // allow to overload generic callback plus @warn, @error and @debug with custom functions + sig_parser.lex < alternatives < identifier, exactly <'*'>, + exactly < Constants::warn_kwd >, + exactly < Constants::error_kwd >, + exactly < Constants::debug_kwd > + > >(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[c function]"), + sig, + name, + params, + c_func, + false, true); + } + + std::string function_name(Signature sig) + { + std::string str(sig); + return str.substr(0, str.find('(')); + } + + namespace Functions { + + inline void handle_utf8_error (const ParserState& pstate, Backtrace* backtrace) + { + try { + throw; + } + catch (utf8::invalid_code_point) { + std::string msg("utf8::invalid_code_point"); + error(msg, pstate, backtrace); + } + catch (utf8::not_enough_room) { + std::string msg("utf8::not_enough_room"); + error(msg, pstate, backtrace); + } + catch (utf8::invalid_utf8) { + std::string msg("utf8::invalid_utf8"); + error(msg, pstate, backtrace); + } + catch (...) { throw; } + } + + template + T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + T* val = dynamic_cast(&env[argname]); + if (!val) { + std::string msg("argument `"); + msg += argname; + msg += "` of `"; + msg += sig; + msg += "` must be a "; + msg += T::type_name(); + error(msg, pstate, backtrace); + } + return val; + } + + Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Map_Ptr val = SASS_MEMORY_CAST(Map, env[argname]); + if (val) return val; + + List_Ptr lval = SASS_MEMORY_CAST(List, env[argname]); + if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); + + // fallback on get_arg for error handling + val = get_arg(argname, env, sig, pstate, backtrace); + return val; + } + + Number_Ptr get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, double lo, double hi, Backtrace* backtrace) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + double v = val->value(); + if (!(lo <= v && v <= hi)) { + std::stringstream msg; + msg << "argument `" << argname << "` of `" << sig << "` must be between "; + msg << lo << " and " << hi; + error(msg.str(), pstate, backtrace); + } + return val; + } + + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, backtrace, ctx) + + template + T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx); + + template <> + Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; + error(msg.str(), pstate); + } + if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options) + "{"; + return Parser::parse_selector(exp_src.c_str(), ctx); + } + + template <> + Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a string for `" << function_name(sig) << "'"; + error(msg.str(), pstate); + } + if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options) + "{"; + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx); + return (sel_list->length() > 0) ? &sel_list->first()->tail()->head() : 0; + } + + #ifdef __MINGW32__ + uint64_t GetSeed() + { + HCRYPTPROV hp = 0; + BYTE rb[8]; + CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hp, sizeof(rb), rb); + CryptReleaseContext(hp, 0); + + uint64_t seed; + memcpy(&seed, &rb[0], sizeof(seed)); + + return seed; + } + #else + uint64_t GetSeed() + { + std::random_device rd; + return rd(); + } + #endif + + // note: the performance of many implementations of + // random_device degrades sharply once the entropy pool + // is exhausted. For practical use, random_device is + // generally only used to seed a PRNG such as mt19937. + static std::mt19937 rand(static_cast(GetSeed())); + + // features + static std::set features { + "global-variable-shadowing", + "extend-selector-pseudoclass", + "at-error", + "units-level-3" + }; + + //////////////// + // RGB FUNCTIONS + //////////////// + + inline double color_num(Number_Ptr n) { + if (n->unit() == "%") { + return std::min(std::max(n->value() * 255 / 100.0, 0.0), 255.0); + } else { + return std::min(std::max(n->value(), 0.0), 255.0); + } + } + + inline double alpha_num(Number_Ptr n) { + if (n->unit() == "%") { + return std::min(std::max(n->value(), 0.0), 100.0); + } else { + return std::min(std::max(n->value(), 0.0), 1.0); + } + } + + Signature rgb_sig = "rgb($red, $green, $blue)"; + BUILT_IN(rgb) + { + return SASS_MEMORY_NEW(Color, + pstate, + color_num(ARG("$red", Number)), + color_num(ARG("$green", Number)), + color_num(ARG("$blue", Number))); + } + + Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; + BUILT_IN(rgba_4) + { + return SASS_MEMORY_NEW(Color, + pstate, + color_num(ARG("$red", Number)), + color_num(ARG("$green", Number)), + color_num(ARG("$blue", Number)), + alpha_num(ARG("$alpha", Number))); + } + + Signature rgba_2_sig = "rgba($color, $alpha)"; + BUILT_IN(rgba_2) + { + Color_Ptr c_arg = ARG("$color", Color); + Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); + new_c->a(alpha_num(ARG("$alpha", Number))); + new_c->disp(""); + return new_c; + } + + Signature red_sig = "red($color)"; + BUILT_IN(red) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } + + Signature green_sig = "green($color)"; + BUILT_IN(green) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } + + Signature blue_sig = "blue($color)"; + BUILT_IN(blue) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } + + Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; + BUILT_IN(mix) + { + Color_Ptr color1 = ARG("$color-1", Color); + Color_Ptr color2 = ARG("$color-2", Color); + Number_Ptr weight = ARGR("$weight", Number, 0, 100); + + double p = weight->value()/100; + double w = 2*p - 1; + double a = color1->a() - color2->a(); + + double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; + double w2 = 1 - w1; + + return SASS_MEMORY_NEW(Color, + pstate, + Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), + Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), + Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), + color1->a()*p + color2->a()*(1-p)); + } + + //////////////// + // HSL FUNCTIONS + //////////////// + + // RGB to HSL helper function + struct HSL { double h; double s; double l; }; + HSL rgb_to_hsl(double r, double g, double b) + { + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + r /= 255.0; g /= 255.0; b /= 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0, s = 0, l = (max + min) / 2.0; + + if (max == min) { + h = s = 0; // achromatic + } + else { + if (l < 0.5) s = delta / (max + min); + else s = delta / (2.0 - max - min); + + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; + } + + HSL hsl_struct; + hsl_struct.h = h / 6 * 360; + hsl_struct.s = s * 100; + hsl_struct.l = l * 100; + + return hsl_struct; + } + + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) { + while (h < 0) h += 1; + while (h > 1) h -= 1; + if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; + if (h*2.0 < 1) return m2; + if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; + return m1; + } + + Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) + { + h /= 360.0; + s /= 100.0; + l /= 100.0; + + if (l < 0) l = 0; + if (s < 0) s = 0; + if (l > 1) l = 1; + if (s > 1) s = 1; + while (h < 0) h += 1; + while (h > 1) h -= 1; + + // if saturation is exacly zero, we loose + // information for hue, since it will evaluate + // to zero if converted back from rgb. Setting + // saturation to a very tiny number solves this. + if (s == 0) s = 1e-10; + + // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + double m2; + if (l <= 0.5) m2 = l*(s+1.0); + else m2 = (l+s)-(l*s); + double m1 = (l*2.0)-m2; + // round the results -- consider moving this into the Color constructor + double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); + double g = (h_to_rgb(m1, m2, h) * 255.0); + double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); + + return SASS_MEMORY_NEW(Color, pstate, r, g, b, a); + } + + Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; + BUILT_IN(hsl) + { + return hsla_impl(ARG("$hue", Number)->value(), + ARG("$saturation", Number)->value(), + ARG("$lightness", Number)->value(), + 1.0, + ctx, + pstate); + } + + Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; + BUILT_IN(hsla) + { + return hsla_impl(ARG("$hue", Number)->value(), + ARG("$saturation", Number)->value(), + ARG("$lightness", Number)->value(), + ARG("$alpha", Number)->value(), + ctx, + pstate); + } + + Signature hue_sig = "hue($color)"; + BUILT_IN(hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); + } + + Signature saturation_sig = "saturation($color)"; + BUILT_IN(saturation) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); + } + + Signature lightness_sig = "lightness($color)"; + BUILT_IN(lightness) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); + } + + Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; + BUILT_IN(adjust_hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + Number_Ptr degrees = ARG("$degrees", Number); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h + degrees->value(), + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature lighten_sig = "lighten($color, $amount)"; + BUILT_IN(lighten) + { + Color_Ptr rgb_color = ARG("$color", Color); + Number_Ptr amount = ARGR("$amount", Number, 0, 100); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + //Check lightness is not negative before lighten it + double hslcolorL = hsl_color.l; + if (hslcolorL < 0) { + hslcolorL = 0; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL + amount->value(), + rgb_color->a(), + ctx, + pstate); + } + + Signature darken_sig = "darken($color, $amount)"; + BUILT_IN(darken) + { + Color_Ptr rgb_color = ARG("$color", Color); + Number_Ptr amount = ARGR("$amount", Number, 0, 100); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + //Check lightness if not over 100, before darken it + double hslcolorL = hsl_color.l; + if (hslcolorL > 100) { + hslcolorL = 100; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL - amount->value(), + rgb_color->a(), + ctx, + pstate); + } + + Signature saturate_sig = "saturate($color, $amount: false)"; + BUILT_IN(saturate) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$amount"]); + if (!amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); + } + + ARGR("$amount", Number, 0, 100); + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s + amount->value(); + + // Saturation cannot be below 0 or above 100 + if (hslcolorS < 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature desaturate_sig = "desaturate($color, $amount)"; + BUILT_IN(desaturate) + { + Color_Ptr rgb_color = ARG("$color", Color); + Number_Ptr amount = ARGR("$amount", Number, 0, 100); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s - amount->value(); + + // Saturation cannot be below 0 or above 100 + if (hslcolorS <= 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature grayscale_sig = "grayscale($color)"; + BUILT_IN(grayscale) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); + } + + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h, + 0.0, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature complement_sig = "complement($color)"; + BUILT_IN(complement) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h - 180.0, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature invert_sig = "invert($color)"; + BUILT_IN(invert) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); + } + + Color_Ptr rgb_color = ARG("$color", Color); + return SASS_MEMORY_NEW(Color, + pstate, + 255 - rgb_color->r(), + 255 - rgb_color->g(), + 255 - rgb_color->b(), + rgb_color->a()); + } + + //////////////////// + // OPACITY FUNCTIONS + //////////////////// + Signature alpha_sig = "alpha($color)"; + Signature opacity_sig = "opacity($color)"; + BUILT_IN(alpha) + { + String_Constant_Ptr ie_kwd = SASS_MEMORY_CAST(String_Constant, env["$color"]); + if (ie_kwd) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); + } + + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); + } + + return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); + } + + Signature opacify_sig = "opacify($color, $amount)"; + Signature fade_in_sig = "fade-in($color, $amount)"; + BUILT_IN(opacify) + { + Color_Ptr color = ARG("$color", Color); + double amount = ARGR("$amount", Number, 0, 1)->value(); + double alpha = std::min(color->a() + amount, 1.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + Signature transparentize_sig = "transparentize($color, $amount)"; + Signature fade_out_sig = "fade-out($color, $amount)"; + BUILT_IN(transparentize) + { + Color_Ptr color = ARG("$color", Color); + double amount = ARGR("$amount", Number, 0, 1)->value(); + double alpha = std::max(color->a() - amount, 0.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + //////////////////////// + // OTHER COLOR FUNCTIONS + //////////////////////// + + Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(adjust_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]); + Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]); + Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]); + Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]); + Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]); + Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]); + Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate); + } + if (rgb) { + double rr = r ? ARGR("$red", Number, -255, 255)->value() : 0; + double gg = g ? ARGR("$green", Number, -255, 255)->value() : 0; + double bb = b ? ARGR("$blue", Number, -255, 255)->value() : 0; + double aa = a ? ARGR("$alpha", Number, -1, 1)->value() : 0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rr, + color->g() + gg, + color->b() + bb, + color->a() + aa); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + double ss = s ? ARGR("$saturation", Number, -100, 100)->value() : 0; + double ll = l ? ARGR("$lightness", Number, -100, 100)->value() : 0; + double aa = a ? ARGR("$alpha", Number, -1, 1)->value() : 0; + return hsla_impl(hsl_struct.h + (h ? h->value() : 0), + hsl_struct.s + ss, + hsl_struct.l + ll, + color->a() + aa, + ctx, + pstate); + } + if (a) { + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + (a ? a->value() : 0)); + } + error("not enough arguments for `adjust-color'", pstate); + // unreachable + return color; + } + + Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(scale_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]); + Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]); + Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]); + Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]); + Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]); + Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]); + Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate); + } + if (rgb) { + double rscale = (r ? ARGR("$red", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double gscale = (g ? ARGR("$green", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double bscale = (b ? ARGR("$blue", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), + color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), + color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + if (hsl) { + double hscale = (h ? ARGR("$hue", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double sscale = (s ? ARGR("$saturation", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double lscale = (l ? ARGR("$lightness", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); + hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); + hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); + double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + error("not enough arguments for `scale-color'", pstate); + // unreachable + return color; + } + + Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(change_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]); + Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]); + Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]); + Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]); + Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]); + Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]); + Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate); + } + if (rgb) { + return SASS_MEMORY_NEW(Color, + pstate, + r ? ARGR("$red", Number, 0, 255)->value() : color->r(), + g ? ARGR("$green", Number, 0, 255)->value() : color->g(), + b ? ARGR("$blue", Number, 0, 255)->value() : color->b(), + a ? ARGR("$alpha", Number, 0, 255)->value() : color->a()); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + if (h) hsl_struct.h = std::fmod(h->value(), 360.0); + if (s) hsl_struct.s = ARGR("$saturation", Number, 0, 100)->value(); + if (l) hsl_struct.l = ARGR("$lightness", Number, 0, 100)->value(); + double alpha = a ? ARGR("$alpha", Number, 0, 1.0)->value() : color->a(); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double alpha = a ? ARGR("$alpha", Number, 0, 1.0)->value() : color->a(); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + error("not enough arguments for `change-color'", pstate); + // unreachable + return color; + } + + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + Signature ie_hex_str_sig = "ie-hex-str($color)"; + BUILT_IN(ie_hex_str) + { + Color_Ptr c = ARG("$color", Color); + double r = cap_channel<0xff>(c->r()); + double g = cap_channel<0xff>(c->g()); + double b = cap_channel<0xff>(c->b()); + double a = cap_channel<1> (c->a()) * 255; + + std::stringstream ss; + ss << '#' << std::setw(2) << std::setfill('0'); + ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); + + std::string result(ss.str()); + for (size_t i = 0, L = result.length(); i < L; ++i) { + result[i] = std::toupper(result[i]); + } + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + /////////////////// + // STRING FUNCTIONS + /////////////////// + + Signature unquote_sig = "unquote($string)"; + BUILT_IN(sass_unquote) + { + AST_Node_Obj arg = env["$string"]; + if (String_Quoted_Ptr string_quoted = SASS_MEMORY_CAST(String_Quoted, arg)) { + String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); + // remember if the string was quoted (color tokens) + result->is_delayed(true); // delay colors + return result; + } + else if (SASS_MEMORY_CAST(String_Constant, arg)) { + return (Expression_Ptr) &arg; + } + else { + Sass_Output_Style oldstyle = ctx.c_options.output_style; + ctx.c_options.output_style = SASS_STYLE_NESTED; + std::string val(arg->to_string(ctx.c_options)); + val = SASS_MEMORY_CAST(Null, arg) ? "null" : val; + ctx.c_options.output_style = oldstyle; + + deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); + return (Expression_Ptr) &arg; + } + } + + Signature quote_sig = "quote($string)"; + BUILT_IN(sass_quote) + { + AST_Node_Obj arg = env["$string"]; + // only set quote mark to true if already a string + if (String_Quoted_Ptr qstr = SASS_MEMORY_CAST(String_Quoted, arg)) { + qstr->quote_mark('*'); + return qstr; + } + // all other nodes must be converted to a string node + std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); + String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); + result->quote_mark('*'); + return result; + } + + + Signature str_length_sig = "str-length($string)"; + BUILT_IN(str_length) + { + size_t len = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + len = UTF_8::code_point_count(s->value(), 0, s->value().size()); + + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, backtrace); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)len); + } + + Signature str_insert_sig = "str-insert($string, $insert, $index)"; + BUILT_IN(str_insert) + { + std::string str; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + str = s->value(); + str = unquote(str); + String_Constant_Ptr i = ARG("$insert", String_Constant); + std::string ins = i->value(); + ins = unquote(ins); + Number_Ptr ind = ARG("$index", Number); + double index = ind->value(); + size_t len = UTF_8::code_point_count(str, 0, str.size()); + + if (index > 0 && index <= len) { + // positive and within string length + str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); + } + else if (index > len) { + // positive and past string length + str += ins; + } + else if (index == 0) { + str = ins + str; + } + else if (std::abs(index) <= len) { + // negative and within string length + index += len + 1; + str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); + } + else { + // negative and past string length + str = ins + str; + } + + if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) { + if (ss->quote_mark()) str = quote(str); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, backtrace); } + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature str_index_sig = "str-index($string, $substring)"; + BUILT_IN(str_index) + { + size_t index = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + String_Constant_Ptr t = ARG("$substring", String_Constant); + std::string str = s->value(); + str = unquote(str); + std::string substr = t->value(); + substr = unquote(substr); + + size_t c_index = str.find(substr); + if(c_index == std::string::npos) { + return SASS_MEMORY_NEW(Null, pstate); + } + index = UTF_8::code_point_count(str, 0, c_index) + 1; + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, backtrace); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)index); + } + + Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; + BUILT_IN(str_slice) + { + std::string newstr; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + double start_at = ARG("$start-at", Number)->value(); + double end_at = ARG("$end-at", Number)->value(); + String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s); + + std::string str = unquote(s->value()); + + size_t size = utf8::distance(str.begin(), str.end()); + + if (!SASS_MEMORY_CAST(Number, env["$end-at"])) { + end_at = -1; + } + + if (end_at == 0 || (end_at + size) < 0) { + if (ss && ss->quote_mark()) newstr = quote(""); + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + if (end_at < 0) { + end_at += size + 1; + if (end_at == 0) end_at = 1; + } + if (end_at > size) { end_at = (double)size; } + if (start_at < 0) { + start_at += size + 1; + if (start_at < 0) start_at = 0; + } + else if (start_at == 0) { ++ start_at; } + + if (start_at <= end_at) + { + std::string::iterator start = str.begin(); + utf8::advance(start, start_at - 1, str.end()); + std::string::iterator end = start; + utf8::advance(end, end_at - start_at + 1, str.end()); + newstr = std::string(start, end); + } + if (ss) { + if(ss->quote_mark()) newstr = quote(newstr); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, backtrace); } + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + Signature to_upper_case_sig = "to-upper-case($string)"; + BUILT_IN(to_upper_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::toupper(str[i]); + } + } + + if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + Signature to_lower_case_sig = "to-lower-case($string)"; + BUILT_IN(to_lower_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::tolower(str[i]); + } + } + + if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + /////////////////// + // NUMBER FUNCTIONS + /////////////////// + + Signature percentage_sig = "percentage($number)"; + BUILT_IN(percentage) + { + Number_Ptr n = ARG("$number", Number); + if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate); + return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); + } + + Signature round_sig = "round($number)"; + BUILT_IN(round) + { + Number_Ptr n = ARG("$number", Number); + Number_Ptr r = SASS_MEMORY_COPY(n); + r->pstate(pstate); + r->value(Sass::round(r->value(), ctx.c_options.precision)); + return r; + } + + Signature ceil_sig = "ceil($number)"; + BUILT_IN(ceil) + { + Number_Ptr n = ARG("$number", Number); + Number_Ptr r = SASS_MEMORY_COPY(n); + r->pstate(pstate); + r->value(std::ceil(r->value())); + return r; + } + + Signature floor_sig = "floor($number)"; + BUILT_IN(floor) + { + Number_Ptr n = ARG("$number", Number); + Number_Ptr r = SASS_MEMORY_COPY(n); + r->pstate(pstate); + r->value(std::floor(r->value())); + return r; + } + + Signature abs_sig = "abs($number)"; + BUILT_IN(abs) + { + Number_Ptr n = ARG("$number", Number); + Number_Ptr r = SASS_MEMORY_COPY(n); + r->pstate(pstate); + r->value(std::abs(r->value())); + return r; + } + + Signature min_sig = "min($numbers...)"; + BUILT_IN(min) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj least = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = SASS_MEMORY_CAST(Number, val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate); + } + if (least) { + if (*xi < *least) least = xi; + } else least = xi; + } + return least.detach(); + } + + Signature max_sig = "max($numbers...)"; + BUILT_IN(max) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj greatest = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = SASS_MEMORY_CAST(Number, val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate); + } + if (greatest) { + if (*greatest < *xi) greatest = xi; + } else greatest = xi; + } + return greatest.detach(); + } + + Signature random_sig = "random($limit:false)"; + BUILT_IN(random) + { + AST_Node_Obj arg = env["$limit"]; + Value_Ptr v = SASS_MEMORY_CAST(Value, arg); + Number_Ptr l = SASS_MEMORY_CAST(Number, arg); + Boolean_Ptr b = SASS_MEMORY_CAST(Boolean, arg); + if (l) { + double v = l->value(); + if (v < 1) { + stringstream err; + err << "$limit " << v << " must be greater than or equal to 1 for `random'"; + error(err.str(), pstate); + } + bool eq_int = std::fabs(trunc(v) - v) < NUMBER_EPSILON; + if (!eq_int) { + stringstream err; + err << "Expected $limit to be an integer but got " << v << " for `random'"; + error(err.str(), pstate); + } + std::uniform_real_distribution<> distributor(1, v + 1); + uint_fast32_t distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, (double)distributed); + } + else if (b) { + std::uniform_real_distribution<> distributor(0, 1); + double distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, distributed); + } else if (v) { + throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number", v); + } else { + throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number"); + } + return 0; + } + + ///////////////// + // LIST FUNCTIONS + ///////////////// + + Signature length_sig = "length($list)"; + BUILT_IN(length) + { + if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) { + return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); + } + Expression_Ptr v = ARG("$list", Expression); + if (v->concrete_type() == Expression::MAP) { + Map_Ptr map = SASS_MEMORY_CAST(Map, env["$list"]); + return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); + } + if (v->concrete_type() == Expression::SELECTOR) { + if (Compound_Selector_Ptr h = dynamic_cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); + } else if (Selector_List_Ptr ls = SASS_MEMORY_CAST_PTR(Selector_List, v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); + } else { + return SASS_MEMORY_NEW(Number, pstate, 1); + } + } + + List_Ptr list = SASS_MEMORY_CAST(List, env["$list"]); + return SASS_MEMORY_NEW(Number, + pstate, + (double)(list ? list->size() : 1)); + } + + Signature nth_sig = "nth($list, $n)"; + BUILT_IN(nth) + { + Number_Ptr n = ARG("$n", Number); + Map_Ptr m = SASS_MEMORY_CAST(Map, env["$list"]); + if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) { + size_t len = m ? m->length() : sl->length(); + bool empty = m ? m->empty() : sl->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + double index = std::floor(n->value() < 0 ? len + n->value() : n->value() - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + // return (*sl)[static_cast(index)]; + Listize listize; + return (*sl)[static_cast(index)]->perform(&listize); + } + List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + if (n->value() == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate); + // if the argument isn't a list, then wrap it in a singleton list + if (!m && !l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + size_t len = m ? m->length() : l->length(); + bool empty = m ? m->empty() : l->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + double index = std::floor(n->value() < 0 ? len + n->value() : n->value() - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + + if (m) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(m->keys()[static_cast(index)]); + l->append(m->at(m->keys()[static_cast(index)])); + return l.detach(); + } + else { + Expression_Obj rv = l->value_at_index(static_cast(index)); + rv->set_delayed(false); + return rv.detach(); + } + } + + Signature set_nth_sig = "set-nth($list, $n, $value)"; + BUILT_IN(set_nth) + { + Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]); + List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + Number_Obj n = ARG("$n", Number); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(ctx, pstate); + } + if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); + if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + result->append(((i == index) ? v : (*l)[i])); + } + return result; + } + + Signature index_sig = "index($list, $value)"; + BUILT_IN(index) + { + Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]); + List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(ctx, pstate); + } + for (size_t i = 0, L = l->length(); i < L; ++i) { + if (Eval::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); + } + return SASS_MEMORY_NEW(Null, pstate); + } + + Signature join_sig = "join($list1, $list2, $separator: auto)"; + BUILT_IN(join) + { + Map_Obj m1 = SASS_MEMORY_CAST(Map, env["$list1"]); + Map_Obj m2 = SASS_MEMORY_CAST(Map, env["$list2"]); + List_Obj l1 = SASS_MEMORY_CAST(List, env["$list1"]); + List_Obj l2 = SASS_MEMORY_CAST(List, env["$list2"]); + String_Constant_Obj sep = ARG("$separator", String_Constant); + enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); + if (!l1) { + l1 = SASS_MEMORY_NEW(List, pstate, 1); + l1->append(ARG("$list1", Expression)); + sep_val = (l2 ? l2->separator() : SASS_SPACE); + } + if (!l2) { + l2 = SASS_MEMORY_NEW(List, pstate, 1); + l2->append(ARG("$list2", Expression)); + } + if (m1) { + l1 = m1->to_list(ctx, pstate); + sep_val = SASS_COMMA; + } + if (m2) { + l2 = m2->to_list(ctx, pstate); + } + size_t len = l1->length() + l2->length(); + std::string sep_str = unquote(sep->value()); + if (sep_str == "space") sep_val = SASS_SPACE; + else if (sep_str == "comma") sep_val = SASS_COMMA; + else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); + List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val); + result->concat(&l1); + result->concat(&l2); + return result.detach(); + } + + Signature append_sig = "append($list, $val, $separator: auto)"; + BUILT_IN(append) + { + Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]); + List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + Expression_Obj v = ARG("$val", Expression); + if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) { + Listize listize; + l = SASS_MEMORY_CAST_PTR(List, sl->perform(&listize)); + } + String_Constant_Obj sep = ARG("$separator", String_Constant); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(ctx, pstate); + } + List_Ptr result = SASS_MEMORY_COPY(l); + std::string sep_str(unquote(sep->value())); + if (sep_str != "auto") { // check default first + if (sep_str == "space") result->separator(SASS_SPACE); + else if (sep_str == "comma") result->separator(SASS_COMMA); + else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); + } + if (l->is_arglist()) { + result->append(SASS_MEMORY_NEW(Argument, + v->pstate(), + v, + "", + false, + false)); + + } else { + result->append(v); + } + return result; + } + + Signature zip_sig = "zip($lists...)"; + BUILT_IN(zip) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); + size_t shortest = 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + List_Obj ith = SASS_MEMORY_CAST(List, arglist->value_at_index(i)); + Map_Obj mith = SASS_MEMORY_CAST(Map, arglist->value_at_index(i)); + if (!ith) { + if (mith) { + ith = mith->to_list(ctx, pstate); + } else { + ith = SASS_MEMORY_NEW(List, pstate, 1); + ith->append(arglist->value_at_index(i)); + } + if (arglist->is_arglist()) { + Argument_Obj arg = (Argument_Ptr)(&arglist->at(i)); + arg->value(&ith); + } else { + (*arglist)[i] = &ith; + } + } + shortest = (i ? std::min(shortest, ith->length()) : ith->length()); + } + List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); + size_t L = arglist->length(); + for (size_t i = 0; i < shortest; ++i) { + List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); + for (size_t j = 0; j < L; ++j) { + zipper->append(SASS_MEMORY_CAST(List, arglist->value_at_index(j))->at(i)); + } + zippers->append(zipper); + } + return zippers; + } + + Signature list_separator_sig = "list_separator($list)"; + BUILT_IN(list_separator) + { + List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + return SASS_MEMORY_NEW(String_Quoted, + pstate, + l->separator() == SASS_COMMA ? "comma" : "space"); + } + + ///////////////// + // MAP FUNCTIONS + ///////////////// + + Signature map_get_sig = "map-get($map, $key)"; + BUILT_IN(map_get) + { + // leaks for "map-get((), foo)" if not Obj + // investigate why this is (unexpected) + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + try { + Expression_Obj val = m->at(v); // XXX + return val ? val.detach() : SASS_MEMORY_NEW(Null, pstate); + } catch (const std::out_of_range&) { + return SASS_MEMORY_NEW(Null, pstate); + } + catch (...) { throw; } + } + + Signature map_has_key_sig = "map-has-key($map, $key)"; + BUILT_IN(map_has_key) + { + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); + } + + Signature map_keys_sig = "map-keys($map)"; + BUILT_IN(map_keys) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(key); + } + return result; + } + + Signature map_values_sig = "map-values($map)"; + BUILT_IN(map_values) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(m->at(key)); + } + return result; + } + + Signature map_merge_sig = "map-merge($map1, $map2)"; + BUILT_IN(map_merge) + { + Map_Obj m1 = ARGM("$map1", Map, ctx); + Map_Obj m2 = ARGM("$map2", Map, ctx); + + size_t len = m1->length() + m2->length(); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); + // concat not implemented for maps + *result += &m1; + *result += &m2; + return result; + } + + Signature map_remove_sig = "map-remove($map, $keys...)"; + BUILT_IN(map_remove) + { + bool remove; + Map_Obj m = ARGM("$map", Map, ctx); + List_Obj arglist = ARG("$keys", List); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, 1); + for (auto key : m->keys()) { + remove = false; + for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { + remove = Eval::eq(key, arglist->value_at_index(j)); + } + if (!remove) *result << std::make_pair(key, m->at(key)); + } + return result; + } + + Signature keywords_sig = "keywords($args)"; + BUILT_IN(keywords) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy + Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); + for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr)&obj; + std::string name = std::string(arg->name()); + name = name.erase(0, 1); // sanitize name (remove dollar sign) + *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, + pstate, name), + arg->value()); + } + return result.detach(); + } + + ////////////////////////// + // INTROSPECTION FUNCTIONS + ////////////////////////// + + Signature type_of_sig = "type-of($value)"; + BUILT_IN(type_of) + { + Expression_Ptr v = ARG("$value", Expression); + return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); + } + + Signature unit_sig = "unit($number)"; + BUILT_IN(unit) + { return SASS_MEMORY_NEW(String_Quoted, pstate, quote(ARG("$number", Number)->unit(), '"')); } + + Signature unitless_sig = "unitless($number)"; + BUILT_IN(unitless) + { return SASS_MEMORY_NEW(Boolean, pstate, ARG("$number", Number)->is_unitless()); } + + Signature comparable_sig = "comparable($number-1, $number-2)"; + BUILT_IN(comparable) + { + Number_Ptr n1 = ARG("$number-1", Number); + Number_Ptr n2 = ARG("$number-2", Number); + if (n1->is_unitless() || n2->is_unitless()) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + Number tmp_n2(n2); // copy + tmp_n2.normalize(n1->find_convertible_unit()); + return SASS_MEMORY_NEW(Boolean, pstate, n1->unit() == tmp_n2.unit()); + } + + Signature variable_exists_sig = "variable-exists($name)"; + BUILT_IN(variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature global_variable_exists_sig = "global-variable-exists($name)"; + BUILT_IN(global_variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature function_exists_sig = "function-exists($name)"; + BUILT_IN(function_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global(s+"[f]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature mixin_exists_sig = "mixin-exists($name)"; + BUILT_IN(mixin_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global(s+"[m]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature feature_exists_sig = "feature-exists($name)"; + BUILT_IN(feature_exists) + { + std::string s = unquote(ARG("$name", String_Constant)->value()); + + if(features.find(s) == features.end()) { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + } + + Signature call_sig = "call($name, $args...)"; + BUILT_IN(call) + { + std::string name = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); + + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + // std::string full_name(name + "[f]"); + // Definition_Ptr def = d_env.has(full_name) ? static_cast((d_env)[full_name]) : 0; + // Parameters_Ptr params = def ? def->parameters() : 0; + // size_t param_size = params ? params->length() : 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj expr = arglist->value_at_index(i); + // if (params && params->has_rest_parameter()) { + // Parameter_Obj p = param_size > i ? (*params)[i] : 0; + // List_Ptr list = SASS_MEMORY_CAST(List, expr); + // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; + // } + if (arglist->is_arglist()) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr)&obj; + args->append(SASS_MEMORY_NEW(Argument, + pstate, + expr, + arg ? arg->name() : "", + arg ? arg->is_rest_argument() : false, + arg ? arg->is_keyword_argument() : false)); + } else { + args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); + } + } + Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, &args); + Expand expand(ctx, &d_env, backtrace, &selector_stack); + func->via_call(true); // calc invoke is allowed + return func->perform(&expand.eval); + + } + + //////////////////// + // BOOLEAN FUNCTIONS + //////////////////// + + Signature not_sig = "not($value)"; + BUILT_IN(sass_not) + { + return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); + } + + Signature if_sig = "if($condition, $if-true, $if-false)"; + // BUILT_IN(sass_if) + // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } + BUILT_IN(sass_if) + { + Expand expand(ctx, &d_env, backtrace, &selector_stack); + Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); + bool is_true = !cond->is_false(); + Expression_Ptr res = ARG(is_true ? "$if-true" : "$if-false", Expression); + res = res->perform(&expand.eval); + res->set_delayed(false); // clone? + return res; + } + + //////////////// + // URL FUNCTIONS + //////////////// + + Signature image_url_sig = "image-url($path, $only-path: false, $cache-buster: false)"; + BUILT_IN(image_url) + { + error("`image_url` has been removed from libsass because it's not part of the Sass spec", pstate); + return 0; // suppress warning, error will exit anyway + } + + ////////////////////////// + // MISCELLANEOUS FUNCTIONS + ////////////////////////// + + // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) + // unquoted_string(value.to_sass) + + Signature inspect_sig = "inspect($value)"; + BUILT_IN(inspect) + { + Expression_Ptr v = ARG("$value", Expression); + if (v->concrete_type() == Expression::NULL_VAL) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); + } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); + } else if (v->concrete_type() == Expression::STRING) { + return v; + } else { + // ToDo: fix to_sass for nested parentheses + Sass_Output_Style old_style; + old_style = ctx.c_options.output_style; + ctx.c_options.output_style = TO_SASS; + Emitter emitter(ctx.c_options); + Inspect i(emitter); + i.in_declaration = false; + v->perform(&i); + ctx.c_options.output_style = old_style; + return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); + } + // return v; + } + Signature selector_nest_sig = "selector-nest($selectors...)"; + BUILT_IN(selector_nest) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-nest'", pstate); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = SASS_MEMORY_CAST(Expression, arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; + error(msg.str(), pstate); + } + if (String_Constant_Obj str = SASS_MEMORY_CAST(String_Constant, exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options) + "{"; + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); + parsedSelectors.push_back(&sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector exploded; + selector_stack.push_back(&result); + Selector_List_Obj rv = child->resolve_parent_refs(ctx, selector_stack); + selector_stack.pop_back(); + for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { + exploded.push_back((*rv)[m]); + } + result->elements(exploded); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_append_sig = "selector-append($selectors...)"; + BUILT_IN(selector_append) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-append'", pstate); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = SASS_MEMORY_CAST(Expression, arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-append'"; + error(msg.str(), pstate); + } + if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string() + "{"; + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); + parsedSelectors.push_back(&sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector newElements; + + // For every COMPLEX_SELECTOR in `result` + // For every COMPLEX_SELECTOR in `child` + // let parentSeqClone equal a copy of result->elements[i] + // let childSeq equal child->elements[j] + // Append all of childSeq head elements into parentSeqClone + // Set the innermost tail of parentSeqClone, to childSeq's tail + // Replace result->elements with newElements + for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { + for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { + Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); + Complex_Selector_Obj childSeq = (*child)[j]; + Complex_Selector_Obj base = childSeq->tail(); + + // Must be a simple sequence + if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, backtrace); + } + + // Cannot be a Universal selector + Element_Selector_Obj pType = SASS_MEMORY_CAST(Element_Selector, childSeq->head()->first()); + if(pType && pType->name() == "*") { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, backtrace); + } + + // TODO: Add check for namespace stuff + + // append any selectors in childSeq's head + parentSeqClone->innermost()->head()->concat(&base->head()); + + // Set parentSeqClone new tail + parentSeqClone->innermost()->tail( base->tail() ); + + newElements.push_back(&parentSeqClone); + } + } + + result->elements(newElements); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; + BUILT_IN(selector_unify) + { + Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); + Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); + + Selector_List_Obj result = selector1->unify_with(&selector2, ctx); + Listize listize; + return result->perform(&listize); + } + + Signature simple_selectors_sig = "simple-selectors($selector)"; + BUILT_IN(simple_selectors) + { + Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); + + List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Simple_Selector_Obj ss = (*sel)[i]; + std::string ss_string = ss->to_string() ; + + l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + } + + return l; + } + + Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; + BUILT_IN(selector_extend) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); + Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); + + Subset_Map subset_map; + extender->populate_extends(extendee, ctx, subset_map); + + Selector_List_Obj result = Extend::extendSelectorList(selector, ctx, subset_map, false); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; + BUILT_IN(selector_replace) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); + Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); + Subset_Map subset_map; + replacement->populate_extends(original, ctx, subset_map); + + Selector_List_Obj result = Extend::extendSelectorList(selector, ctx, subset_map, true); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_parse_sig = "selector-parse($selector)"; + BUILT_IN(selector_parse) + { + Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + + Listize listize; + return sel->perform(&listize); + } + + Signature is_superselector_sig = "is-superselector($super, $sub)"; + BUILT_IN(is_superselector) + { + Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); + Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); + bool result = sel_sup->is_superselector_of(sel_sub); + return SASS_MEMORY_NEW(Boolean, pstate, result); + } + + Signature unique_id_sig = "unique-id()"; + BUILT_IN(unique_id) + { + std::stringstream ss; + std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 + uint_fast32_t distributed = static_cast(distributor(rand)); + ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; + return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); + } + + } +} diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp new file mode 100755 index 000000000..ef4f60ef4 --- /dev/null +++ b/src/libsass/src/functions.hpp @@ -0,0 +1,195 @@ +#ifndef SASS_FUNCTIONS_H +#define SASS_FUNCTIONS_H + +#include "listize.hpp" +#include "position.hpp" +#include "environment.hpp" +#include "ast_fwd_decl.hpp" +#include "sass/functions.h" + +#define BUILT_IN(name) Expression_Ptr \ +name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtrace* backtrace, std::vector selector_stack) + +namespace Sass { + struct Backtrace; + typedef Environment Env; + typedef const char* Signature; + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); + + Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx); + Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx); + + std::string function_name(Signature); + + namespace Functions { + + extern Signature rgb_sig; + extern Signature rgba_4_sig; + extern Signature rgba_2_sig; + extern Signature red_sig; + extern Signature green_sig; + extern Signature blue_sig; + extern Signature mix_sig; + extern Signature hsl_sig; + extern Signature hsla_sig; + extern Signature hue_sig; + extern Signature saturation_sig; + extern Signature lightness_sig; + extern Signature adjust_hue_sig; + extern Signature lighten_sig; + extern Signature darken_sig; + extern Signature saturate_sig; + extern Signature desaturate_sig; + extern Signature grayscale_sig; + extern Signature complement_sig; + extern Signature invert_sig; + extern Signature alpha_sig; + extern Signature opacity_sig; + extern Signature opacify_sig; + extern Signature fade_in_sig; + extern Signature transparentize_sig; + extern Signature fade_out_sig; + extern Signature adjust_color_sig; + extern Signature scale_color_sig; + extern Signature change_color_sig; + extern Signature ie_hex_str_sig; + extern Signature unquote_sig; + extern Signature quote_sig; + extern Signature str_length_sig; + extern Signature str_insert_sig; + extern Signature str_index_sig; + extern Signature str_slice_sig; + extern Signature to_upper_case_sig; + extern Signature to_lower_case_sig; + extern Signature percentage_sig; + extern Signature round_sig; + extern Signature ceil_sig; + extern Signature floor_sig; + extern Signature abs_sig; + extern Signature min_sig; + extern Signature max_sig; + extern Signature inspect_sig; + extern Signature random_sig; + extern Signature length_sig; + extern Signature nth_sig; + extern Signature index_sig; + extern Signature join_sig; + extern Signature append_sig; + extern Signature zip_sig; + extern Signature list_separator_sig; + extern Signature type_of_sig; + extern Signature unit_sig; + extern Signature unitless_sig; + extern Signature comparable_sig; + extern Signature variable_exists_sig; + extern Signature global_variable_exists_sig; + extern Signature function_exists_sig; + extern Signature mixin_exists_sig; + extern Signature feature_exists_sig; + extern Signature call_sig; + extern Signature not_sig; + extern Signature if_sig; + extern Signature image_url_sig; + extern Signature map_get_sig; + extern Signature map_merge_sig; + extern Signature map_remove_sig; + extern Signature map_keys_sig; + extern Signature map_values_sig; + extern Signature map_has_key_sig; + extern Signature keywords_sig; + extern Signature set_nth_sig; + extern Signature unique_id_sig; + extern Signature selector_nest_sig; + extern Signature selector_append_sig; + extern Signature selector_extend_sig; + extern Signature selector_replace_sig; + extern Signature selector_unify_sig; + extern Signature is_superselector_sig; + extern Signature simple_selectors_sig; + extern Signature selector_parse_sig; + + BUILT_IN(rgb); + BUILT_IN(rgba_4); + BUILT_IN(rgba_2); + BUILT_IN(red); + BUILT_IN(green); + BUILT_IN(blue); + BUILT_IN(mix); + BUILT_IN(hsl); + BUILT_IN(hsla); + BUILT_IN(hue); + BUILT_IN(saturation); + BUILT_IN(lightness); + BUILT_IN(adjust_hue); + BUILT_IN(lighten); + BUILT_IN(darken); + BUILT_IN(saturate); + BUILT_IN(desaturate); + BUILT_IN(grayscale); + BUILT_IN(complement); + BUILT_IN(invert); + BUILT_IN(alpha); + BUILT_IN(opacify); + BUILT_IN(transparentize); + BUILT_IN(adjust_color); + BUILT_IN(scale_color); + BUILT_IN(change_color); + BUILT_IN(ie_hex_str); + BUILT_IN(sass_unquote); + BUILT_IN(sass_quote); + BUILT_IN(str_length); + BUILT_IN(str_insert); + BUILT_IN(str_index); + BUILT_IN(str_slice); + BUILT_IN(to_upper_case); + BUILT_IN(to_lower_case); + BUILT_IN(percentage); + BUILT_IN(round); + BUILT_IN(ceil); + BUILT_IN(floor); + BUILT_IN(abs); + BUILT_IN(min); + BUILT_IN(max); + BUILT_IN(inspect); + BUILT_IN(random); + BUILT_IN(length); + BUILT_IN(nth); + BUILT_IN(index); + BUILT_IN(join); + BUILT_IN(append); + BUILT_IN(zip); + BUILT_IN(list_separator); + BUILT_IN(type_of); + BUILT_IN(unit); + BUILT_IN(unitless); + BUILT_IN(comparable); + BUILT_IN(variable_exists); + BUILT_IN(global_variable_exists); + BUILT_IN(function_exists); + BUILT_IN(mixin_exists); + BUILT_IN(feature_exists); + BUILT_IN(call); + BUILT_IN(sass_not); + BUILT_IN(sass_if); + BUILT_IN(image_url); + BUILT_IN(map_get); + BUILT_IN(map_merge); + BUILT_IN(map_remove); + BUILT_IN(map_keys); + BUILT_IN(map_values); + BUILT_IN(map_has_key); + BUILT_IN(keywords); + BUILT_IN(set_nth); + BUILT_IN(unique_id); + BUILT_IN(selector_nest); + BUILT_IN(selector_append); + BUILT_IN(selector_extend); + BUILT_IN(selector_replace); + BUILT_IN(selector_unify); + BUILT_IN(is_superselector); + BUILT_IN(simple_selectors); + BUILT_IN(selector_parse); + } +} + +#endif diff --git a/src/libsass/src/inspect.cpp b/src/libsass/src/inspect.cpp new file mode 100755 index 000000000..3b68900d5 --- /dev/null +++ b/src/libsass/src/inspect.cpp @@ -0,0 +1,1083 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "inspect.hpp" +#include "context.hpp" +#include "listize.hpp" +#include "color_maps.hpp" +#include "utf8/checked.h" + +namespace Sass { + + Inspect::Inspect(Emitter emi) + : Emitter(emi) + { } + Inspect::~Inspect() { } + + // statements + void Inspect::operator()(Block_Ptr block) + { + if (!block->is_root()) { + add_open_mapping(block); + append_scope_opener(); + } + if (output_style() == NESTED) indentation += block->tabs(); + for (size_t i = 0, L = block->length(); i < L; ++i) { + (*block)[i]->perform(this); + } + if (output_style() == NESTED) indentation -= block->tabs(); + if (!block->is_root()) { + append_scope_closer(); + add_close_mapping(block); + } + + } + + void Inspect::operator()(Ruleset_Ptr ruleset) + { + if (ruleset->selector()) { + ruleset->selector()->perform(this); + } + if (ruleset->block()) { + ruleset->block()->perform(this); + } + } + + void Inspect::operator()(Keyframe_Rule_Ptr rule) + { + if (rule->name()) rule->name()->perform(this); + if (rule->block()) rule->block()->perform(this); + } + + void Inspect::operator()(Bubble_Ptr bubble) + { + append_indentation(); + append_token("::BUBBLE", bubble); + append_scope_opener(); + bubble->node()->perform(this); + append_scope_closer(); + } + + void Inspect::operator()(Media_Block_Ptr media_block) + { + append_indentation(); + append_token("@media", media_block); + append_mandatory_space(); + in_media_block = true; + media_block->media_queries()->perform(this); + in_media_block = false; + media_block->block()->perform(this); + } + + void Inspect::operator()(Supports_Block_Ptr feature_block) + { + append_indentation(); + append_token("@supports", feature_block); + append_mandatory_space(); + feature_block->condition()->perform(this); + feature_block->block()->perform(this); + } + + void Inspect::operator()(At_Root_Block_Ptr at_root_block) + { + append_indentation(); + append_token("@at-root ", at_root_block); + append_mandatory_space(); + if(at_root_block->expression()) at_root_block->expression()->perform(this); + at_root_block->block()->perform(this); + } + + void Inspect::operator()(Directive_Ptr at_rule) + { + append_indentation(); + append_token(at_rule->keyword(), at_rule); + if (at_rule->selector()) { + append_mandatory_space(); + bool was_wrapped = in_wrapped; + in_wrapped = true; + at_rule->selector()->perform(this); + in_wrapped = was_wrapped; + } + if (at_rule->value()) { + append_mandatory_space(); + at_rule->value()->perform(this); + } + if (at_rule->block()) { + at_rule->block()->perform(this); + } + else { + append_delimiter(); + } + } + + void Inspect::operator()(Declaration_Ptr dec) + { + if (dec->value()->concrete_type() == Expression::NULL_VAL) return; + bool was_decl = in_declaration; + in_declaration = true; + if (output_style() == NESTED) + indentation += dec->tabs(); + append_indentation(); + if (dec->property()) + dec->property()->perform(this); + append_colon_separator(); + + if (dec->value()->concrete_type() == Expression::SELECTOR) { + Listize listize; + Expression_Obj ls = dec->value()->perform(&listize); + ls->perform(this); + } else { + dec->value()->perform(this); + } + + if (dec->is_important()) { + append_optional_space(); + append_string("!important"); + } + append_delimiter(); + if (output_style() == NESTED) + indentation -= dec->tabs(); + in_declaration = was_decl; + } + + void Inspect::operator()(Assignment_Ptr assn) + { + append_token(assn->variable(), assn); + append_colon_separator(); + assn->value()->perform(this); + if (assn->is_default()) { + append_optional_space(); + append_string("!default"); + } + append_delimiter(); + } + + void Inspect::operator()(Import_Ptr import) + { + if (!import->urls().empty()) { + append_token("@import", import); + append_mandatory_space(); + + import->urls().front()->perform(this); + if (import->urls().size() == 1) { + if (&import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + for (size_t i = 1, S = import->urls().size(); i < S; ++i) { + append_mandatory_linefeed(); + append_token("@import", import); + append_mandatory_space(); + + import->urls()[i]->perform(this); + if (import->urls().size() - 1 == i) { + if (&import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + } + } + } + + void Inspect::operator()(Import_Stub_Ptr import) + { + append_indentation(); + append_token("@import", import); + append_mandatory_space(); + append_string(import->imp_path()); + append_delimiter(); + } + + void Inspect::operator()(Warning_Ptr warning) + { + append_indentation(); + append_token("@warn", warning); + append_mandatory_space(); + warning->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Error_Ptr error) + { + append_indentation(); + append_token("@error", error); + append_mandatory_space(); + error->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Debug_Ptr debug) + { + append_indentation(); + append_token("@debug", debug); + append_mandatory_space(); + debug->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Comment_Ptr comment) + { + in_comment = true; + comment->text()->perform(this); + in_comment = false; + } + + void Inspect::operator()(If_Ptr cond) + { + append_indentation(); + append_token("@if", cond); + append_mandatory_space(); + cond->predicate()->perform(this); + cond->block()->perform(this); + if (cond->alternative()) { + append_optional_linefeed(); + append_indentation(); + append_string("else"); + cond->alternative()->perform(this); + } + } + + void Inspect::operator()(For_Ptr loop) + { + append_indentation(); + append_token("@for", loop); + append_mandatory_space(); + append_string(loop->variable()); + append_string(" from "); + loop->lower_bound()->perform(this); + append_string(loop->is_inclusive() ? " through " : " to "); + loop->upper_bound()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Each_Ptr loop) + { + append_indentation(); + append_token("@each", loop); + append_mandatory_space(); + append_string(loop->variables()[0]); + for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { + append_comma_separator(); + append_string(loop->variables()[i]); + } + append_string(" in "); + loop->list()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(While_Ptr loop) + { + append_indentation(); + append_token("@while", loop); + append_mandatory_space(); + loop->predicate()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Return_Ptr ret) + { + append_indentation(); + append_token("@return", ret); + append_mandatory_space(); + ret->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Extension_Ptr extend) + { + append_indentation(); + append_token("@extend", extend); + append_mandatory_space(); + extend->selector()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Definition_Ptr def) + { + append_indentation(); + if (def->type() == Definition::MIXIN) { + append_token("@mixin", def); + append_mandatory_space(); + } else { + append_token("@function", def); + append_mandatory_space(); + } + append_string(def->name()); + def->parameters()->perform(this); + def->block()->perform(this); + } + + void Inspect::operator()(Mixin_Call_Ptr call) + { + append_indentation(); + append_token("@include", call); + append_mandatory_space(); + append_string(call->name()); + if (call->arguments()) { + call->arguments()->perform(this); + } + if (call->block()) { + append_optional_space(); + call->block()->perform(this); + } + if (!call->block()) append_delimiter(); + } + + void Inspect::operator()(Content_Ptr content) + { + append_indentation(); + append_token("@content", content); + append_delimiter(); + } + + void Inspect::operator()(Map_Ptr map) + { + if (output_style() == TO_SASS && map->empty()) { + append_string("()"); + return; + } + if (map->empty()) return; + if (map->is_invisible()) return; + bool items_output = false; + append_string("("); + for (auto key : map->keys()) { + if (items_output) append_comma_separator(); + key->perform(this); + append_colon_separator(); + map->at(key)->perform(this); + items_output = true; + } + append_string(")"); + } + + void Inspect::operator()(List_Ptr list) + { + if (output_style() == TO_SASS && list->empty()) { + append_string("()"); + return; + } + std::string sep(list->separator() == SASS_SPACE ? " " : ","); + if ((output_style() != COMPRESSED) && sep == ",") sep += " "; + else if (in_media_block && sep != " ") sep += " "; // verified + if (list->empty()) return; + bool items_output = false; + + bool was_space_array = in_space_array; + bool was_comma_array = in_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !SASS_MEMORY_CAST(List, list->at(0)) && + !SASS_MEMORY_CAST(Selector_List, list->at(0))) { + append_string("("); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string("("); + } + + if (list->separator() == SASS_SPACE) in_space_array = true; + else if (list->separator() == SASS_COMMA) in_comma_array = true; + + for (size_t i = 0, L = list->size(); i < L; ++i) { + if (list->separator() == SASS_HASH) + { sep[0] = i % 2 ? ':' : ','; } + Expression_Obj list_item = list->at(i); + if (output_style() != TO_SASS) { + if (list_item->is_invisible()) { + // this fixes an issue with "" in a list + if (!SASS_MEMORY_CAST(String_Constant, list_item)) { + continue; + } + } + } + if (items_output) { + append_string(sep); + } + if (items_output && sep != " ") + append_optional_space(); + list_item->perform(this); + items_output = true; + } + + in_comma_array = was_comma_array; + in_space_array = was_space_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !SASS_MEMORY_CAST(List, list->at(0)) && + !SASS_MEMORY_CAST(Selector_List, list->at(0))) { + append_string(",)"); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(")"); + } + + } + + void Inspect::operator()(Binary_Expression_Ptr expr) + { + expr->left()->perform(this); + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_before + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + + )) append_string(" "); + switch (expr->type()) { + case Sass_OP::AND: append_string("&&"); break; + case Sass_OP::OR: append_string("||"); break; + case Sass_OP::EQ: append_string("=="); break; + case Sass_OP::NEQ: append_string("!="); break; + case Sass_OP::GT: append_string(">"); break; + case Sass_OP::GTE: append_string(">="); break; + case Sass_OP::LT: append_string("<"); break; + case Sass_OP::LTE: append_string("<="); break; + case Sass_OP::ADD: append_string("+"); break; + case Sass_OP::SUB: append_string("-"); break; + case Sass_OP::MUL: append_string("*"); break; + case Sass_OP::DIV: append_string("/"); break; + case Sass_OP::MOD: append_string("%"); break; + default: break; // shouldn't get here + } + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_after + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + )) append_string(" "); + expr->right()->perform(this); + } + + void Inspect::operator()(Unary_Expression_Ptr expr) + { + if (expr->type() == Unary_Expression::PLUS) append_string("+"); + else append_string("-"); + expr->operand()->perform(this); + } + + void Inspect::operator()(Function_Call_Ptr call) + { + append_token(call->name(), call); + call->arguments()->perform(this); + } + + void Inspect::operator()(Function_Call_Schema_Ptr call) + { + call->name()->perform(this); + call->arguments()->perform(this); + } + + void Inspect::operator()(Variable_Ptr var) + { + append_token(var->name(), var); + } + + void Inspect::operator()(Textual_Ptr txt) + { + append_token(txt->value(), txt); + } + + void Inspect::operator()(Number_Ptr n) + { + + std::string res; + + // check if the fractional part of the value equals to zero + // neat trick from http://stackoverflow.com/a/1521682/1550314 + // double int_part; bool is_int = modf(value, &int_part) == 0.0; + + // this all cannot be done with one run only, since fixed + // output differs from normal output and regular output + // can contain scientific notation which we do not want! + + // first sample + std::stringstream ss; + ss.precision(12); + ss << n->value(); + + // check if we got scientific notation in result + if (ss.str().find_first_of("e") != std::string::npos) { + ss.clear(); ss.str(std::string()); + ss.precision(std::max(12, opt.precision)); + ss << std::fixed << n->value(); + } + + std::string tmp = ss.str(); + size_t pos_point = tmp.find_first_of(".,"); + size_t pos_fract = tmp.find_last_not_of("0"); + bool is_int = pos_point == pos_fract || + pos_point == std::string::npos; + + // reset stream for another run + ss.clear(); ss.str(std::string()); + + // take a shortcut for integers + if (is_int) + { + ss.precision(0); + ss << std::fixed << n->value(); + res = std::string(ss.str()); + } + // process floats + else + { + // do we have have too much precision? + if (pos_fract < opt.precision + pos_point) + { ss.precision((int)(pos_fract - pos_point)); } + else { ss.precision(opt.precision); } + // round value again + ss << std::fixed << n->value(); + res = std::string(ss.str()); + // maybe we truncated up to decimal point + size_t pos = res.find_last_not_of("0"); + // handle case where we have a "0" + if (pos == std::string::npos) { + res = "0.0"; + } else { + bool at_dec_point = res[pos] == '.' || + res[pos] == ','; + // don't leave a blank point + if (at_dec_point) ++ pos; + res.resize (pos + 1); + } + } + + // some final cosmetics + if (res == "0.0") res = "0"; + else if (res == "") res = "0"; + else if (res == "-0") res = "0"; + else if (res == "-0.0") res = "0"; + else if (opt.output_style == COMPRESSED) + { + // check if handling negative nr + size_t off = res[0] == '-' ? 1 : 0; + // remove leading zero from floating point in compressed mode + if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + } + + // add unit now + res += n->unit(); + + // output the final token + append_token(res, n); + } + + // helper function for serializing colors + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + void Inspect::operator()(Color_Ptr c) + { + // output the final token + std::stringstream ss; + + // original color name + // maybe an unknown token + std::string name = c->disp(); + + // resolved color + std::string res_name = name; + + double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); + double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); + double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); + double a = cap_channel<1> (c->a()); + + // get color from given name (if one was given at all) + if (name != "" && name_to_color(name)) { + Color_Ptr_Const n = name_to_color(name); + r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); + g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); + b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); + a = cap_channel<1> (n->a()); + } + // otherwise get the possible resolved color name + else { + double numval = r * 0x10000 + g * 0x100 + b; + if (color_to_name(numval)) + res_name = color_to_name(numval); + } + + std::stringstream hexlet; + bool compressed = opt.output_style == COMPRESSED; + hexlet << '#' << std::setw(1) << std::setfill('0'); + // create a short color hexlet if there is any need for it + if (compressed && is_color_doublet(r, g, b) && a == 1) { + hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); + } else { + hexlet << std::hex << std::setw(2) << static_cast(r); + hexlet << std::hex << std::setw(2) << static_cast(g); + hexlet << std::hex << std::setw(2) << static_cast(b); + } + + if (compressed && !c->is_delayed()) name = ""; + if (opt.output_style == INSPECT && a >= 1) { + append_token(hexlet.str(), c); + return; + } + + // retain the originally specified color definition if unchanged + if (name != "") { + ss << name; + } + else if (r == 0 && g == 0 && b == 0 && a == 0) { + ss << "transparent"; + } + else if (a >= 1) { + if (res_name != "") { + if (compressed && hexlet.str().size() < res_name.size()) { + ss << hexlet.str(); + } else { + ss << res_name; + } + } + else { + ss << hexlet.str(); + } + } + else { + ss << "rgba("; + ss << static_cast(r) << ","; + if (!compressed) ss << " "; + ss << static_cast(g) << ","; + if (!compressed) ss << " "; + ss << static_cast(b) << ","; + if (!compressed) ss << " "; + ss << a << ')'; + } + + append_token(ss.str(), c); + + } + + void Inspect::operator()(Boolean_Ptr b) + { + // output the final token + append_token(b->value() ? "true" : "false", b); + } + + void Inspect::operator()(String_Schema_Ptr ss) + { + // Evaluation should turn these into String_Constants, + // so this method is only for inspection purposes. + for (size_t i = 0, L = ss->length(); i < L; ++i) { + if ((*ss)[i]->is_interpolant()) append_string("#{"); + (*ss)[i]->perform(this); + if ((*ss)[i]->is_interpolant()) append_string("}"); + } + } + + void Inspect::operator()(String_Constant_Ptr s) + { + append_token(s->value(), s); + } + + void Inspect::operator()(String_Quoted_Ptr s) + { + if (const char q = s->quote_mark()) { + append_token(quote(s->value(), q), s); + } else { + append_token(s->value(), s); + } + } + + void Inspect::operator()(Custom_Error_Ptr e) + { + append_token(e->message(), e); + } + + void Inspect::operator()(Custom_Warning_Ptr w) + { + append_token(w->message(), w); + } + + void Inspect::operator()(Supports_Operator_Ptr so) + { + + if (so->needs_parens(so->left())) append_string("("); + so->left()->perform(this); + if (so->needs_parens(so->left())) append_string(")"); + + if (so->operand() == Supports_Operator::AND) { + append_mandatory_space(); + append_token("and", so); + append_mandatory_space(); + } else if (so->operand() == Supports_Operator::OR) { + append_mandatory_space(); + append_token("or", so); + append_mandatory_space(); + } + + if (so->needs_parens(so->right())) append_string("("); + so->right()->perform(this); + if (so->needs_parens(so->right())) append_string(")"); + } + + void Inspect::operator()(Supports_Negation_Ptr sn) + { + append_token("not", sn); + append_mandatory_space(); + if (sn->needs_parens(&sn->condition())) append_string("("); + sn->condition()->perform(this); + if (sn->needs_parens(&sn->condition())) append_string(")"); + } + + void Inspect::operator()(Supports_Declaration_Ptr sd) + { + append_string("("); + sd->feature()->perform(this); + append_string(": "); + sd->value()->perform(this); + append_string(")"); + } + + void Inspect::operator()(Supports_Interpolation_Ptr sd) + { + sd->value()->perform(this); + } + + void Inspect::operator()(Media_Query_Ptr mq) + { + size_t i = 0; + if (&mq->media_type()) { + if (mq->is_negated()) append_string("not "); + else if (mq->is_restricted()) append_string("only "); + mq->media_type()->perform(this); + } + else { + (*mq)[i++]->perform(this); + } + for (size_t L = mq->length(); i < L; ++i) { + append_string(" and "); + (*mq)[i]->perform(this); + } + } + + void Inspect::operator()(Media_Query_Expression_Ptr mqe) + { + if (mqe->is_interpolated()) { + mqe->feature()->perform(this); + } + else { + append_string("("); + mqe->feature()->perform(this); + if (mqe->value()) { + append_string(": "); // verified + mqe->value()->perform(this); + } + append_string(")"); + } + } + + void Inspect::operator()(At_Root_Query_Ptr ae) + { + append_string("("); + ae->feature()->perform(this); + if (ae->value()) { + append_colon_separator(); + ae->value()->perform(this); + } + append_string(")"); + } + + void Inspect::operator()(Null_Ptr n) + { + // output the final token + append_token("null", n); + } + + // parameters and arguments + void Inspect::operator()(Parameter_Ptr p) + { + append_token(p->name(), p); + if (p->default_value()) { + append_colon_separator(); + p->default_value()->perform(this); + } + else if (p->is_rest_parameter()) { + append_string("..."); + } + } + + void Inspect::operator()(Parameters_Ptr p) + { + append_string("("); + if (!p->empty()) { + (*p)[0]->perform(this); + for (size_t i = 1, L = p->length(); i < L; ++i) { + append_comma_separator(); + (*p)[i]->perform(this); + } + } + append_string(")"); + } + + void Inspect::operator()(Argument_Ptr a) + { + if (!a->name().empty()) { + append_token(a->name(), a); + append_colon_separator(); + } + if (!a->value()) return; + // Special case: argument nulls can be ignored + if (a->value()->concrete_type() == Expression::NULL_VAL) { + return; + } + if (a->value()->concrete_type() == Expression::STRING) { + String_Constant_Ptr s = SASS_MEMORY_CAST(String_Constant, a->value()); + if (s) s->perform(this); + } else { + a->value()->perform(this); + } + if (a->is_rest_argument()) { + append_string("..."); + } + } + + void Inspect::operator()(Arguments_Ptr a) + { + append_string("("); + if (!a->empty()) { + (*a)[0]->perform(this); + for (size_t i = 1, L = a->length(); i < L; ++i) { + append_string(", "); // verified + // Sass Bug? append_comma_separator(); + (*a)[i]->perform(this); + } + } + append_string(")"); + } + + void Inspect::operator()(Selector_Schema_Ptr s) + { + s->contents()->perform(this); + } + + void Inspect::operator()(Parent_Selector_Ptr p) + { + if (p->is_real_parent_ref()) append_string("&"); + } + + void Inspect::operator()(Placeholder_Selector_Ptr s) + { + append_token(s->name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + + } + + void Inspect::operator()(Element_Selector_Ptr s) + { + append_token(s->ns_name(), s); + } + + void Inspect::operator()(Class_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Id_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Attribute_Selector_Ptr s) + { + append_string("["); + add_open_mapping(s); + append_token(s->ns_name(), s); + if (!s->matcher().empty()) { + append_string(s->matcher()); + if (&s->value() && *s->value()) { + s->value()->perform(this); + } + } + add_close_mapping(s); + append_string("]"); + } + + void Inspect::operator()(Pseudo_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->expression()) { + append_string("("); + s->expression()->perform(this); + append_string(")"); + } + } + + void Inspect::operator()(Wrapped_Selector_Ptr s) + { + bool was = in_wrapped; + in_wrapped = true; + append_token(s->name(), s); + append_string("("); + bool was_comma_array = in_comma_array; + in_comma_array = false; + s->selector()->perform(this); + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; + } + + void Inspect::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0, L = s->length(); i < L; ++i) { + (*s)[i]->perform(this); + } + if (s->has_line_break()) { + if (output_style() != COMPACT) { + append_optional_linefeed(); + } + } + } + + void Inspect::operator()(Complex_Selector_Ptr c) + { + Compound_Selector_Obj head = c->head(); + Complex_Selector_Obj tail = c->tail(); + Complex_Selector::Combinator comb = c->combinator(); + + if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { + if (tail) tail->perform(this); + return; + } + + if (c->has_line_feed()) { + if (!(c->has_parent_ref())) { + append_optional_linefeed(); + append_indentation(); + } + } + + if (head && head->length() != 0) head->perform(this); + bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); + bool is_tail = head && !head->is_empty_reference() && tail; + if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; + + switch (comb) { + case Complex_Selector::ANCESTOR_OF: + if (is_tail) append_mandatory_space(); + break; + case Complex_Selector::PARENT_OF: + append_optional_space(); + append_string(">"); + append_optional_space(); + break; + case Complex_Selector::ADJACENT_TO: + append_optional_space(); + append_string("+"); + append_optional_space(); + break; + case Complex_Selector::REFERENCE: + append_mandatory_space(); + append_string("/"); + c->reference()->perform(this); + append_string("/"); + append_mandatory_space(); + break; + case Complex_Selector::PRECEDES: + if (is_empty) append_optional_space(); + else append_mandatory_space(); + append_string("~"); + if (tail) append_mandatory_space(); + else append_optional_space(); + break; + } + if (tail && comb != Complex_Selector::ANCESTOR_OF) { + if (c->has_line_break()) append_optional_linefeed(); + } + if (tail) tail->perform(this); + if (!tail && c->has_line_break()) { + if (output_style() == COMPACT) { + append_mandatory_space(); + } + } + } + + void Inspect::operator()(Selector_List_Ptr g) + { + + if (g->empty()) { + if (output_style() == TO_SASS) { + append_token("()", g); + } + return; + } + + + bool was_comma_array = in_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!SASS_MEMORY_CAST(List, (*g)[0]) && + !SASS_MEMORY_CAST(Selector_List, (*g)[0]))) { + append_string("("); + } + else if (!in_declaration && in_comma_array) { + append_string("("); + } + + if (in_declaration) in_comma_array = true; + + for (size_t i = 0, L = g->length(); i < L; ++i) { + if (!in_wrapped && i == 0) append_indentation(); + if ((*g)[i] == 0) continue; + schedule_mapping(&g->at(i)->last()); + // add_open_mapping((*g)[i]->last()); + (*g)[i]->perform(this); + // add_close_mapping((*g)[i]->last()); + if (i < L - 1) { + scheduled_space = 0; + append_comma_separator(); + } + } + + in_comma_array = was_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!SASS_MEMORY_CAST(List, (*g)[0]) && + !SASS_MEMORY_CAST(Selector_List, (*g)[0]))) { + append_string(",)"); + } + else if (!in_declaration && in_comma_array) { + append_string(")"); + } + + } + + void Inspect::fallback_impl(AST_Node_Ptr n) + { + } + +} diff --git a/src/libsass/src/inspect.hpp b/src/libsass/src/inspect.hpp new file mode 100755 index 000000000..a5fc20570 --- /dev/null +++ b/src/libsass/src/inspect.hpp @@ -0,0 +1,100 @@ +#ifndef SASS_INSPECT_H +#define SASS_INSPECT_H + +#include "position.hpp" +#include "operation.hpp" +#include "emitter.hpp" + +namespace Sass { + class Context; + + class Inspect : public Operation_CRTP, public Emitter { + protected: + // import all the class-specific methods and override as desired + using Operation_CRTP::operator(); + + void fallback_impl(AST_Node_Ptr n); + + public: + + Inspect(Emitter emi); + virtual ~Inspect(); + + // statements + virtual void operator()(Block_Ptr); + virtual void operator()(Ruleset_Ptr); + virtual void operator()(Bubble_Ptr); + virtual void operator()(Supports_Block_Ptr); + virtual void operator()(Media_Block_Ptr); + virtual void operator()(At_Root_Block_Ptr); + virtual void operator()(Directive_Ptr); + virtual void operator()(Keyframe_Rule_Ptr); + virtual void operator()(Declaration_Ptr); + virtual void operator()(Assignment_Ptr); + virtual void operator()(Import_Ptr); + virtual void operator()(Import_Stub_Ptr); + virtual void operator()(Warning_Ptr); + virtual void operator()(Error_Ptr); + virtual void operator()(Debug_Ptr); + virtual void operator()(Comment_Ptr); + virtual void operator()(If_Ptr); + virtual void operator()(For_Ptr); + virtual void operator()(Each_Ptr); + virtual void operator()(While_Ptr); + virtual void operator()(Return_Ptr); + virtual void operator()(Extension_Ptr); + virtual void operator()(Definition_Ptr); + virtual void operator()(Mixin_Call_Ptr); + virtual void operator()(Content_Ptr); + // expressions + virtual void operator()(Map_Ptr); + virtual void operator()(List_Ptr); + virtual void operator()(Binary_Expression_Ptr); + virtual void operator()(Unary_Expression_Ptr); + virtual void operator()(Function_Call_Ptr); + virtual void operator()(Function_Call_Schema_Ptr); + // virtual void operator()(Custom_Warning_Ptr); + // virtual void operator()(Custom_Error_Ptr); + virtual void operator()(Variable_Ptr); + virtual void operator()(Textual_Ptr); + virtual void operator()(Number_Ptr); + virtual void operator()(Color_Ptr); + virtual void operator()(Boolean_Ptr); + virtual void operator()(String_Schema_Ptr); + virtual void operator()(String_Constant_Ptr); + virtual void operator()(String_Quoted_Ptr); + virtual void operator()(Custom_Error_Ptr); + virtual void operator()(Custom_Warning_Ptr); + virtual void operator()(Supports_Operator_Ptr); + virtual void operator()(Supports_Negation_Ptr); + virtual void operator()(Supports_Declaration_Ptr); + virtual void operator()(Supports_Interpolation_Ptr); + virtual void operator()(Media_Query_Ptr); + virtual void operator()(Media_Query_Expression_Ptr); + virtual void operator()(At_Root_Query_Ptr); + virtual void operator()(Null_Ptr); + virtual void operator()(Parent_Selector_Ptr p); + // parameters and arguments + virtual void operator()(Parameter_Ptr); + virtual void operator()(Parameters_Ptr); + virtual void operator()(Argument_Ptr); + virtual void operator()(Arguments_Ptr); + // selectors + virtual void operator()(Selector_Schema_Ptr); + virtual void operator()(Placeholder_Selector_Ptr); + virtual void operator()(Element_Selector_Ptr); + virtual void operator()(Class_Selector_Ptr); + virtual void operator()(Id_Selector_Ptr); + virtual void operator()(Attribute_Selector_Ptr); + virtual void operator()(Pseudo_Selector_Ptr); + virtual void operator()(Wrapped_Selector_Ptr); + virtual void operator()(Compound_Selector_Ptr); + virtual void operator()(Complex_Selector_Ptr); + virtual void operator()(Selector_List_Ptr); + + // template + // void fallback(U x) { fallback_impl(reinterpret_cast(x)); } + }; + +} +#endif diff --git a/src/libsass/src/json.cpp b/src/libsass/src/json.cpp new file mode 100755 index 000000000..8f433f5d0 --- /dev/null +++ b/src/libsass/src/json.cpp @@ -0,0 +1,1436 @@ +/* + Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#include "json.hpp" + +// include utf8 library used by libsass +// ToDo: replace internal json utf8 code +#include "utf8.h" + +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +#ifdef snprintf +#undef snprintf +#endif +extern "C" int snprintf(char *, size_t, const char *, ...); +#endif + +#define out_of_memory() do { \ + fprintf(stderr, "Out of memory.\n"); \ + exit(EXIT_FAILURE); \ + } while (0) + +/* Sadly, strdup is not portable. */ +static char *json_strdup(const char *str) +{ + char *ret = (char*) malloc(strlen(str) + 1); + if (ret == NULL) + out_of_memory(); + strcpy(ret, str); + return ret; +} + +/* String buffer */ + +typedef struct +{ + char *cur; + char *end; + char *start; +} SB; + +static void sb_init(SB *sb) +{ + sb->start = (char*) malloc(17); + if (sb->start == NULL) + out_of_memory(); + sb->cur = sb->start; + sb->end = sb->start + 16; +} + +/* sb and need may be evaluated multiple times. */ +#define sb_need(sb, need) do { \ + if ((sb)->end - (sb)->cur < (need)) \ + sb_grow(sb, need); \ + } while (0) + +static void sb_grow(SB *sb, int need) +{ + size_t length = sb->cur - sb->start; + size_t alloc = sb->end - sb->start; + + do { + alloc *= 2; + } while (alloc < length + need); + + sb->start = (char*) realloc(sb->start, alloc + 1); + if (sb->start == NULL) + out_of_memory(); + sb->cur = sb->start + length; + sb->end = sb->start + alloc; +} + +static void sb_put(SB *sb, const char *bytes, int count) +{ + sb_need(sb, count); + memcpy(sb->cur, bytes, count); + sb->cur += count; +} + +#define sb_putc(sb, c) do { \ + if ((sb)->cur >= (sb)->end) \ + sb_grow(sb, 1); \ + *(sb)->cur++ = (c); \ + } while (0) + +static void sb_puts(SB *sb, const char *str) +{ + sb_put(sb, str, (int)strlen(str)); +} + +static char *sb_finish(SB *sb) +{ + *sb->cur = 0; + assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start)); + return sb->start; +} + +static void sb_free(SB *sb) +{ + free(sb->start); +} + +/* + * Unicode helper functions + * + * These are taken from the ccan/charset module and customized a bit. + * Putting them here means the compiler can (choose to) inline them, + * and it keeps ccan/json from having a dependency. + * + * We use uint32_t Type for Unicode codepoints. + * We need our own because wchar_t might be 16 bits. + */ + +/* + * Validate a single UTF-8 character starting at @s. + * The string must be null-terminated. + * + * If it's valid, return its length (1 thru 4). + * If it's invalid or clipped, return 0. + * + * This function implements the syntax given in RFC3629, which is + * the same as that given in The Unicode Standard, Version 6.0. + * + * It has the following properties: + * + * * All codepoints U+0000..U+10FFFF may be encoded, + * except for U+D800..U+DFFF, which are reserved + * for UTF-16 surrogate pair encoding. + * * UTF-8 byte sequences longer than 4 bytes are not permitted, + * as they exceed the range of Unicode. + * * The sixty-six Unicode "non-characters" are permitted + * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF). + */ +static int utf8_validate_cz(const char *s) +{ + unsigned char c = *s++; + + if (c <= 0x7F) { /* 00..7F */ + return 1; + } else if (c <= 0xC1) { /* 80..C1 */ + /* Disallow overlong 2-byte sequence. */ + return 0; + } else if (c <= 0xDF) { /* C2..DF */ + /* Make sure subsequent byte is in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 2; + } else if (c <= 0xEF) { /* E0..EF */ + /* Disallow overlong 3-byte sequence. */ + if (c == 0xE0 && (unsigned char)*s < 0xA0) + return 0; + + /* Disallow U+D800..U+DFFF. */ + if (c == 0xED && (unsigned char)*s > 0x9F) + return 0; + + /* Make sure subsequent bytes are in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 3; + } else if (c <= 0xF4) { /* F0..F4 */ + /* Disallow overlong 4-byte sequence. */ + if (c == 0xF0 && (unsigned char)*s < 0x90) + return 0; + + /* Disallow codepoints beyond U+10FFFF. */ + if (c == 0xF4 && (unsigned char)*s > 0x8F) + return 0; + + /* Make sure subsequent bytes are in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 4; + } else { /* F5..FF */ + return 0; + } +} + +/* Validate a null-terminated UTF-8 string. */ +static bool utf8_validate(const char *s) +{ + int len; + + for (; *s != 0; s += len) { + len = utf8_validate_cz(s); + if (len == 0) + return false; + } + + return true; +} + +/* + * Read a single UTF-8 character starting at @s, + * returning the length, in bytes, of the character read. + * + * This function assumes input is valid UTF-8, + * and that there are enough characters in front of @s. + */ +static int utf8_read_char(const char *s, uint32_t *out) +{ + const unsigned char *c = (const unsigned char*) s; + + assert(utf8_validate_cz(s)); + + if (c[0] <= 0x7F) { + /* 00..7F */ + *out = c[0]; + return 1; + } else if (c[0] <= 0xDF) { + /* C2..DF (unless input is invalid) */ + *out = ((uint32_t)c[0] & 0x1F) << 6 | + ((uint32_t)c[1] & 0x3F); + return 2; + } else if (c[0] <= 0xEF) { + /* E0..EF */ + *out = ((uint32_t)c[0] & 0xF) << 12 | + ((uint32_t)c[1] & 0x3F) << 6 | + ((uint32_t)c[2] & 0x3F); + return 3; + } else { + /* F0..F4 (unless input is invalid) */ + *out = ((uint32_t)c[0] & 0x7) << 18 | + ((uint32_t)c[1] & 0x3F) << 12 | + ((uint32_t)c[2] & 0x3F) << 6 | + ((uint32_t)c[3] & 0x3F); + return 4; + } +} + +/* + * Write a single UTF-8 character to @s, + * returning the length, in bytes, of the character written. + * + * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF. + * + * This function will write up to 4 bytes to @out. + */ +static int utf8_write_char(uint32_t unicode, char *out) +{ + unsigned char *o = (unsigned char*) out; + + assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF)); + + if (unicode <= 0x7F) { + /* U+0000..U+007F */ + *o++ = unicode; + return 1; + } else if (unicode <= 0x7FF) { + /* U+0080..U+07FF */ + *o++ = 0xC0 | unicode >> 6; + *o++ = 0x80 | (unicode & 0x3F); + return 2; + } else if (unicode <= 0xFFFF) { + /* U+0800..U+FFFF */ + *o++ = 0xE0 | unicode >> 12; + *o++ = 0x80 | (unicode >> 6 & 0x3F); + *o++ = 0x80 | (unicode & 0x3F); + return 3; + } else { + /* U+10000..U+10FFFF */ + *o++ = 0xF0 | unicode >> 18; + *o++ = 0x80 | (unicode >> 12 & 0x3F); + *o++ = 0x80 | (unicode >> 6 & 0x3F); + *o++ = 0x80 | (unicode & 0x3F); + return 4; + } +} + +/* + * Compute the Unicode codepoint of a UTF-16 surrogate pair. + * + * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF. + * If they aren't, this function returns false. + */ +static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uint32_t *unicode) +{ + if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) { + *unicode = 0x10000 + ((((uint32_t)uc & 0x3FF) << 10) | (lc & 0x3FF)); + return true; + } else { + return false; + } +} + +/* + * Construct a UTF-16 surrogate pair given a Unicode codepoint. + * + * @unicode must be U+10000..U+10FFFF. + */ +static void to_surrogate_pair(uint32_t unicode, uint16_t *uc, uint16_t *lc) +{ + uint32_t n; + + assert(unicode >= 0x10000 && unicode <= 0x10FFFF); + + n = unicode - 0x10000; + *uc = ((n >> 10) & 0x3FF) | 0xD800; + *lc = (n & 0x3FF) | 0xDC00; +} + +static bool is_space (const char *c); +static bool is_digit (const char *c); +static bool parse_value (const char **sp, JsonNode **out); +static bool parse_string (const char **sp, char **out); +static bool parse_number (const char **sp, double *out); +static bool parse_array (const char **sp, JsonNode **out); +static bool parse_object (const char **sp, JsonNode **out); +static bool parse_hex16 (const char **sp, uint16_t *out); + +static bool expect_literal (const char **sp, const char *str); +static void skip_space (const char **sp); + +static void emit_value (SB *out, const JsonNode *node); +static void emit_value_indented (SB *out, const JsonNode *node, const char *space, int indent_level); +static void emit_string (SB *out, const char *str); +static void emit_number (SB *out, double num); +static void emit_array (SB *out, const JsonNode *array); +static void emit_array_indented (SB *out, const JsonNode *array, const char *space, int indent_level); +static void emit_object (SB *out, const JsonNode *object); +static void emit_object_indented (SB *out, const JsonNode *object, const char *space, int indent_level); + +static int write_hex16(char *out, uint16_t val); + +static JsonNode *mknode(JsonTag tag); +static void append_node(JsonNode *parent, JsonNode *child); +static void prepend_node(JsonNode *parent, JsonNode *child); +static void append_member(JsonNode *object, char *key, JsonNode *value); + +/* Assertion-friendly validity checks */ +static bool tag_is_valid(unsigned int tag); +static bool number_is_valid(const char *num); + +JsonNode *json_decode(const char *json) +{ + const char *s = json; + JsonNode *ret; + + skip_space(&s); + if (!parse_value(&s, &ret)) + return NULL; + + skip_space(&s); + if (*s != 0) { + json_delete(ret); + return NULL; + } + + return ret; +} + +char *json_encode(const JsonNode *node) +{ + return json_stringify(node, NULL); +} + +char *json_encode_string(const char *str) +{ + SB sb; + sb_init(&sb); + + try { + emit_string(&sb, str); + } + catch (std::exception) { + sb_free(&sb); + throw; + } + + return sb_finish(&sb); +} + +char *json_stringify(const JsonNode *node, const char *space) +{ + SB sb; + sb_init(&sb); + + try { + if (space != NULL) + emit_value_indented(&sb, node, space, 0); + else + emit_value(&sb, node); + } + catch (std::exception) { + sb_free(&sb); + throw; + } + + return sb_finish(&sb); +} + +void json_delete(JsonNode *node) +{ + if (node != NULL) { + json_remove_from_parent(node); + + switch (node->tag) { + case JSON_STRING: + free(node->string_); + break; + case JSON_ARRAY: + case JSON_OBJECT: + { + JsonNode *child, *next; + for (child = node->children.head; child != NULL; child = next) { + next = child->next; + json_delete(child); + } + break; + } + default:; + } + + free(node); + } +} + +bool json_validate(const char *json) +{ + const char *s = json; + + skip_space(&s); + if (!parse_value(&s, NULL)) + return false; + + skip_space(&s); + if (*s != 0) + return false; + + return true; +} + +JsonNode *json_find_element(JsonNode *array, int index) +{ + JsonNode *element; + int i = 0; + + if (array == NULL || array->tag != JSON_ARRAY) + return NULL; + + json_foreach(element, array) { + if (i == index) + return element; + i++; + } + + return NULL; +} + +JsonNode *json_find_member(JsonNode *object, const char *name) +{ + JsonNode *member; + + if (object == NULL || object->tag != JSON_OBJECT) + return NULL; + + json_foreach(member, object) + if (strcmp(member->key, name) == 0) + return member; + + return NULL; +} + +JsonNode *json_first_child(const JsonNode *node) +{ + if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT)) + return node->children.head; + return NULL; +} + +static JsonNode *mknode(JsonTag tag) +{ + JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode)); + if (ret == NULL) + out_of_memory(); + ret->tag = tag; + return ret; +} + +JsonNode *json_mknull(void) +{ + return mknode(JSON_NULL); +} + +JsonNode *json_mkbool(bool b) +{ + JsonNode *ret = mknode(JSON_BOOL); + ret->bool_ = b; + return ret; +} + +static JsonNode *mkstring(char *s) +{ + JsonNode *ret = mknode(JSON_STRING); + ret->string_ = s; + return ret; +} + +JsonNode *json_mkstring(const char *s) +{ + return mkstring(json_strdup(s)); +} + +JsonNode *json_mknumber(double n) +{ + JsonNode *node = mknode(JSON_NUMBER); + node->number_ = n; + return node; +} + +JsonNode *json_mkarray(void) +{ + return mknode(JSON_ARRAY); +} + +JsonNode *json_mkobject(void) +{ + return mknode(JSON_OBJECT); +} + +static void append_node(JsonNode *parent, JsonNode *child) +{ + if (child != NULL && parent != NULL) { + child->parent = parent; + child->prev = parent->children.tail; + child->next = NULL; + + if (parent->children.tail != NULL) + parent->children.tail->next = child; + else + parent->children.head = child; + parent->children.tail = child; + } +} + +static void prepend_node(JsonNode *parent, JsonNode *child) +{ + if (child != NULL && parent != NULL) { + child->parent = parent; + child->prev = NULL; + child->next = parent->children.head; + + if (parent->children.head != NULL) + parent->children.head->prev = child; + else + parent->children.tail = child; + parent->children.head = child; + } +} + +static void append_member(JsonNode *object, char *key, JsonNode *value) +{ + if (value != NULL && object != NULL) { + value->key = key; + append_node(object, value); + } +} + +void json_append_element(JsonNode *array, JsonNode *element) +{ + if (array != NULL && element !=NULL) { + assert(array->tag == JSON_ARRAY); + assert(element->parent == NULL); + + append_node(array, element); + } +} + +void json_prepend_element(JsonNode *array, JsonNode *element) +{ + assert(array->tag == JSON_ARRAY); + assert(element->parent == NULL); + + prepend_node(array, element); +} + +void json_append_member(JsonNode *object, const char *key, JsonNode *value) +{ + if (object != NULL && key != NULL && value != NULL) { + assert(object->tag == JSON_OBJECT); + assert(value->parent == NULL); + + append_member(object, json_strdup(key), value); + } +} + +void json_prepend_member(JsonNode *object, const char *key, JsonNode *value) +{ + if (object != NULL && key != NULL && value != NULL) { + assert(object->tag == JSON_OBJECT); + assert(value->parent == NULL); + + value->key = json_strdup(key); + prepend_node(object, value); + } +} + +void json_remove_from_parent(JsonNode *node) +{ + if (node != NULL) { + JsonNode *parent = node->parent; + + if (parent != NULL) { + if (node->prev != NULL) + node->prev->next = node->next; + else + parent->children.head = node->next; + + if (node->next != NULL) + node->next->prev = node->prev; + else + parent->children.tail = node->prev; + + free(node->key); + + node->parent = NULL; + node->prev = node->next = NULL; + node->key = NULL; + } + } +} + +static bool parse_value(const char **sp, JsonNode **out) +{ + const char *s = *sp; + + switch (*s) { + case 'n': + if (expect_literal(&s, "null")) { + if (out) + *out = json_mknull(); + *sp = s; + return true; + } + return false; + + case 'f': + if (expect_literal(&s, "false")) { + if (out) + *out = json_mkbool(false); + *sp = s; + return true; + } + return false; + + case 't': + if (expect_literal(&s, "true")) { + if (out) + *out = json_mkbool(true); + *sp = s; + return true; + } + return false; + + case '"': { + char *str = NULL; + if (parse_string(&s, out ? &str : NULL)) { + if (out) + *out = mkstring(str); + *sp = s; + return true; + } + return false; + } + + case '[': + if (parse_array(&s, out)) { + *sp = s; + return true; + } + return false; + + case '{': + if (parse_object(&s, out)) { + *sp = s; + return true; + } + return false; + + default: { + double num; + if (parse_number(&s, out ? &num : NULL)) { + if (out) + *out = json_mknumber(num); + *sp = s; + return true; + } + return false; + } + } +} + +static bool parse_array(const char **sp, JsonNode **out) +{ + const char *s = *sp; + JsonNode *ret = out ? json_mkarray() : NULL; + JsonNode *element = NULL; + + if (*s++ != '[') + goto failure; + skip_space(&s); + + if (*s == ']') { + s++; + goto success; + } + + for (;;) { + if (!parse_value(&s, out ? &element : NULL)) + goto failure; + skip_space(&s); + + if (out) + json_append_element(ret, element); + + if (*s == ']') { + s++; + goto success; + } + + if (*s++ != ',') + goto failure; + skip_space(&s); + } + +success: + *sp = s; + if (out) + *out = ret; + return true; + +failure: + json_delete(ret); + return false; +} + +static bool parse_object(const char **sp, JsonNode **out) +{ + const char *s = *sp; + JsonNode *ret = out ? json_mkobject() : NULL; + char *key = NULL; + JsonNode *value = NULL; + + if (*s++ != '{') + goto failure; + skip_space(&s); + + if (*s == '}') { + s++; + goto success; + } + + for (;;) { + if (!parse_string(&s, out ? &key : NULL)) + goto failure; + skip_space(&s); + + if (*s++ != ':') + goto failure_free_key; + skip_space(&s); + + if (!parse_value(&s, out ? &value : NULL)) + goto failure_free_key; + skip_space(&s); + + if (out) + append_member(ret, key, value); + + if (*s == '}') { + s++; + goto success; + } + + if (*s++ != ',') + goto failure; + skip_space(&s); + } + +success: + *sp = s; + if (out) + *out = ret; + return true; + +failure_free_key: + if (out) + free(key); +failure: + json_delete(ret); + return false; +} + +bool parse_string(const char **sp, char **out) +{ + const char *s = *sp; + SB sb = { 0, 0, 0 }; + char throwaway_buffer[4]; + /* enough space for a UTF-8 character */ + char *b; + + if (*s++ != '"') + return false; + + if (out) { + sb_init(&sb); + sb_need(&sb, 4); + b = sb.cur; + } else { + b = throwaway_buffer; + } + + while (*s != '"') { + unsigned char c = *s++; + + /* Parse next character, and write it to b. */ + if (c == '\\') { + c = *s++; + switch (c) { + case '"': + case '\\': + case '/': + *b++ = c; + break; + case 'b': + *b++ = '\b'; + break; + case 'f': + *b++ = '\f'; + break; + case 'n': + *b++ = '\n'; + break; + case 'r': + *b++ = '\r'; + break; + case 't': + *b++ = '\t'; + break; + case 'u': + { + uint16_t uc, lc; + uint32_t unicode; + + if (!parse_hex16(&s, &uc)) + goto failed; + + if (uc >= 0xD800 && uc <= 0xDFFF) { + /* Handle UTF-16 surrogate pair. */ + if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc)) + goto failed; /* Incomplete surrogate pair. */ + if (!from_surrogate_pair(uc, lc, &unicode)) + goto failed; /* Invalid surrogate pair. */ + } else if (uc == 0) { + /* Disallow "\u0000". */ + goto failed; + } else { + unicode = uc; + } + + b += utf8_write_char(unicode, b); + break; + } + default: + /* Invalid escape */ + goto failed; + } + } else if (c <= 0x1F) { + /* Control characters are not allowed in string literals. */ + goto failed; + } else { + /* Validate and echo a UTF-8 character. */ + int len; + + s--; + len = utf8_validate_cz(s); + if (len == 0) + goto failed; /* Invalid UTF-8 character. */ + + while (len--) + *b++ = *s++; + } + + /* + * Update sb to know about the new bytes, + * and set up b to write another character. + */ + if (out) { + sb.cur = b; + sb_need(&sb, 4); + b = sb.cur; + } else { + b = throwaway_buffer; + } + } + s++; + + if (out) + *out = sb_finish(&sb); + *sp = s; + return true; + +failed: + if (out) + sb_free(&sb); + return false; +} + +bool is_space(const char *c) { + return ((*c) == '\t' || (*c) == '\n' || (*c) == '\r' || (*c) == ' '); +} + +bool is_digit(const char *c){ + return ((*c) >= '0' && (*c) <= '9'); +} + +/* + * The JSON spec says that a number shall follow this precise pattern + * (spaces and quotes added for readability): + * '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)? + * + * However, some JSON parsers are more liberal. For instance, PHP accepts + * '.5' and '1.'. JSON.parse accepts '+3'. + * + * This function takes the strict approach. + */ +bool parse_number(const char **sp, double *out) +{ + const char *s = *sp; + + /* '-'? */ + if (*s == '-') + s++; + + /* (0 | [1-9][0-9]*) */ + if (*s == '0') { + s++; + } else { + if (!is_digit(s)) + return false; + do { + s++; + } while (is_digit(s)); + } + + /* ('.' [0-9]+)? */ + if (*s == '.') { + s++; + if (!is_digit(s)) + return false; + do { + s++; + } while (is_digit(s)); + } + + /* ([Ee] [+-]? [0-9]+)? */ + if (*s == 'E' || *s == 'e') { + s++; + if (*s == '+' || *s == '-') + s++; + if (!is_digit(s)) + return false; + do { + s++; + } while (is_digit(s)); + } + + if (out) + *out = strtod(*sp, NULL); + + *sp = s; + return true; +} + +static void skip_space(const char **sp) +{ + const char *s = *sp; + while (is_space(s)) + s++; + *sp = s; +} + +static void emit_value(SB *out, const JsonNode *node) +{ + assert(tag_is_valid(node->tag)); + switch (node->tag) { + case JSON_NULL: + sb_puts(out, "null"); + break; + case JSON_BOOL: + sb_puts(out, node->bool_ ? "true" : "false"); + break; + case JSON_STRING: + emit_string(out, node->string_); + break; + case JSON_NUMBER: + emit_number(out, node->number_); + break; + case JSON_ARRAY: + emit_array(out, node); + break; + case JSON_OBJECT: + emit_object(out, node); + break; + default: + assert(false); + } +} + +void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level) +{ + assert(tag_is_valid(node->tag)); + switch (node->tag) { + case JSON_NULL: + sb_puts(out, "null"); + break; + case JSON_BOOL: + sb_puts(out, node->bool_ ? "true" : "false"); + break; + case JSON_STRING: + emit_string(out, node->string_); + break; + case JSON_NUMBER: + emit_number(out, node->number_); + break; + case JSON_ARRAY: + emit_array_indented(out, node, space, indent_level); + break; + case JSON_OBJECT: + emit_object_indented(out, node, space, indent_level); + break; + default: + assert(false); + } +} + +static void emit_array(SB *out, const JsonNode *array) +{ + const JsonNode *element; + + sb_putc(out, '['); + json_foreach(element, array) { + emit_value(out, element); + if (element->next != NULL) + sb_putc(out, ','); + } + sb_putc(out, ']'); +} + +static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level) +{ + const JsonNode *element = array->children.head; + int i; + + if (element == NULL) { + sb_puts(out, "[]"); + return; + } + + sb_puts(out, "[\n"); + while (element != NULL) { + for (i = 0; i < indent_level + 1; i++) + sb_puts(out, space); + emit_value_indented(out, element, space, indent_level + 1); + + element = element->next; + sb_puts(out, element != NULL ? ",\n" : "\n"); + } + for (i = 0; i < indent_level; i++) + sb_puts(out, space); + sb_putc(out, ']'); +} + +static void emit_object(SB *out, const JsonNode *object) +{ + const JsonNode *member; + + sb_putc(out, '{'); + json_foreach(member, object) { + emit_string(out, member->key); + sb_putc(out, ':'); + emit_value(out, member); + if (member->next != NULL) + sb_putc(out, ','); + } + sb_putc(out, '}'); +} + +static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level) +{ + const JsonNode *member = object->children.head; + int i; + + if (member == NULL) { + sb_puts(out, "{}"); + return; + } + + sb_puts(out, "{\n"); + while (member != NULL) { + for (i = 0; i < indent_level + 1; i++) + sb_puts(out, space); + emit_string(out, member->key); + sb_puts(out, ": "); + emit_value_indented(out, member, space, indent_level + 1); + + member = member->next; + sb_puts(out, member != NULL ? ",\n" : "\n"); + } + for (i = 0; i < indent_level; i++) + sb_puts(out, space); + sb_putc(out, '}'); +} + +void emit_string(SB *out, const char *str) +{ + bool escape_unicode = false; + const char *s = str; + char *b; + +// make assertion catchable +#ifndef NDEBUG + if (!utf8_validate(str)) { + throw utf8::invalid_utf8(0); + } +#endif + + assert(utf8_validate(str)); + + /* + * 14 bytes is enough space to write up to two + * \uXXXX escapes and two quotation marks. + */ + sb_need(out, 14); + b = out->cur; + + *b++ = '"'; + while (*s != 0) { + unsigned char c = *s++; + + /* Encode the next character, and write it to b. */ + switch (c) { + case '"': + *b++ = '\\'; + *b++ = '"'; + break; + case '\\': + *b++ = '\\'; + *b++ = '\\'; + break; + case '\b': + *b++ = '\\'; + *b++ = 'b'; + break; + case '\f': + *b++ = '\\'; + *b++ = 'f'; + break; + case '\n': + *b++ = '\\'; + *b++ = 'n'; + break; + case '\r': + *b++ = '\\'; + *b++ = 'r'; + break; + case '\t': + *b++ = '\\'; + *b++ = 't'; + break; + default: { + int len; + + s--; + len = utf8_validate_cz(s); + + if (len == 0) { + /* + * Handle invalid UTF-8 character gracefully in production + * by writing a replacement character (U+FFFD) + * and skipping a single byte. + * + * This should never happen when assertions are enabled + * due to the assertion at the beginning of this function. + */ + assert(false); + if (escape_unicode) { + strcpy(b, "\\uFFFD"); + b += 6; + } else { + *b++ = 0xEFu; + *b++ = 0xBFu; + *b++ = 0xBDu; + } + s++; + } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) { + /* Encode using \u.... */ + uint32_t unicode; + + s += utf8_read_char(s, &unicode); + + if (unicode <= 0xFFFF) { + *b++ = '\\'; + *b++ = 'u'; + b += write_hex16(b, unicode); + } else { + /* Produce a surrogate pair. */ + uint16_t uc, lc; + assert(unicode <= 0x10FFFF); + to_surrogate_pair(unicode, &uc, &lc); + *b++ = '\\'; + *b++ = 'u'; + b += write_hex16(b, uc); + *b++ = '\\'; + *b++ = 'u'; + b += write_hex16(b, lc); + } + } else { + /* Write the character directly. */ + while (len--) + *b++ = *s++; + } + + break; + } + } + + /* + * Update *out to know about the new bytes, + * and set up b to write another encoded character. + */ + out->cur = b; + sb_need(out, 14); + b = out->cur; + } + *b++ = '"'; + + out->cur = b; +} + +static void emit_number(SB *out, double num) +{ + /* + * This isn't exactly how JavaScript renders numbers, + * but it should produce valid JSON for reasonable numbers + * preserve precision well enough, and avoid some oddities + * like 0.3 -> 0.299999999999999988898 . + */ + char buf[64]; + sprintf(buf, "%.16g", num); + + if (number_is_valid(buf)) + sb_puts(out, buf); + else + sb_puts(out, "null"); +} + +static bool tag_is_valid(unsigned int tag) +{ + return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT); +} + +static bool number_is_valid(const char *num) +{ + return (parse_number(&num, NULL) && *num == '\0'); +} + +static bool expect_literal(const char **sp, const char *str) +{ + const char *s = *sp; + + while (*str != '\0') + if (*s++ != *str++) + return false; + + *sp = s; + return true; +} + +/* + * Parses exactly 4 hex characters (capital or lowercase). + * Fails if any input chars are not [0-9A-Fa-f]. + */ +static bool parse_hex16(const char **sp, uint16_t *out) +{ + const char *s = *sp; + uint16_t ret = 0; + uint16_t i; + uint16_t tmp; + char c; + + for (i = 0; i < 4; i++) { + c = *s++; + if (c >= '0' && c <= '9') + tmp = c - '0'; + else if (c >= 'A' && c <= 'F') + tmp = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + tmp = c - 'a' + 10; + else + return false; + + ret <<= 4; + ret += tmp; + } + + if (out) + *out = ret; + *sp = s; + return true; +} + +/* + * Encodes a 16-bit number into hexadecimal, + * writing exactly 4 hex chars. + */ +static int write_hex16(char *out, uint16_t val) +{ + const char *hex = "0123456789ABCDEF"; + + *out++ = hex[(val >> 12) & 0xF]; + *out++ = hex[(val >> 8) & 0xF]; + *out++ = hex[(val >> 4) & 0xF]; + *out++ = hex[ val & 0xF]; + + return 4; +} + +bool json_check(const JsonNode *node, char errmsg[256]) +{ + #define problem(...) do { \ + if (errmsg != NULL) \ + snprintf(errmsg, 256, __VA_ARGS__); \ + return false; \ + } while (0) + + if (node->key != NULL && !utf8_validate(node->key)) + problem("key contains invalid UTF-8"); + + if (!tag_is_valid(node->tag)) + problem("tag is invalid (%u)", node->tag); + + if (node->tag == JSON_BOOL) { + if (node->bool_ != false && node->bool_ != true) + problem("bool_ is neither false (%d) nor true (%d)", (int)false, (int)true); + } else if (node->tag == JSON_STRING) { + if (node->string_ == NULL) + problem("string_ is NULL"); + if (!utf8_validate(node->string_)) + problem("string_ contains invalid UTF-8"); + } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) { + JsonNode *head = node->children.head; + JsonNode *tail = node->children.tail; + + if (head == NULL || tail == NULL) { + if (head != NULL) + problem("tail is NULL, but head is not"); + if (tail != NULL) + problem("head is NULL, but tail is not"); + } else { + JsonNode *child; + JsonNode *last = NULL; + + if (head->prev != NULL) + problem("First child's prev pointer is not NULL"); + + for (child = head; child != NULL; last = child, child = child->next) { + if (child == node) + problem("node is its own child"); + if (child->next == child) + problem("child->next == child (cycle)"); + if (child->next == head) + problem("child->next == head (cycle)"); + + if (child->parent != node) + problem("child does not point back to parent"); + if (child->next != NULL && child->next->prev != child) + problem("child->next does not point back to child"); + + if (node->tag == JSON_ARRAY && child->key != NULL) + problem("Array element's key is not NULL"); + if (node->tag == JSON_OBJECT && child->key == NULL) + problem("Object member's key is NULL"); + + if (!json_check(child, errmsg)) + return false; + } + + if (last != tail) + problem("tail does not match pointer found by starting at head and following next links"); + } + } + + return true; + + #undef problem +} diff --git a/src/libsass/src/json.hpp b/src/libsass/src/json.hpp new file mode 100755 index 000000000..05b35cd94 --- /dev/null +++ b/src/libsass/src/json.hpp @@ -0,0 +1,117 @@ +/* + Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef CCAN_JSON_H +#define CCAN_JSON_H + +#include +#include + +typedef enum { + JSON_NULL, + JSON_BOOL, + JSON_STRING, + JSON_NUMBER, + JSON_ARRAY, + JSON_OBJECT, +} JsonTag; + +typedef struct JsonNode JsonNode; + +struct JsonNode +{ + /* only if parent is an object or array (NULL otherwise) */ + JsonNode *parent; + JsonNode *prev, *next; + + /* only if parent is an object (NULL otherwise) */ + char *key; /* Must be valid UTF-8. */ + + JsonTag tag; + union { + /* JSON_BOOL */ + bool bool_; + + /* JSON_STRING */ + char *string_; /* Must be valid UTF-8. */ + + /* JSON_NUMBER */ + double number_; + + /* JSON_ARRAY */ + /* JSON_OBJECT */ + struct { + JsonNode *head, *tail; + } children; + }; +}; + +/*** Encoding, decoding, and validation ***/ + +JsonNode *json_decode (const char *json); +char *json_encode (const JsonNode *node); +char *json_encode_string (const char *str); +char *json_stringify (const JsonNode *node, const char *space); +void json_delete (JsonNode *node); + +bool json_validate (const char *json); + +/*** Lookup and traversal ***/ + +JsonNode *json_find_element (JsonNode *array, int index); +JsonNode *json_find_member (JsonNode *object, const char *key); + +JsonNode *json_first_child (const JsonNode *node); + +#define json_foreach(i, object_or_array) \ + for ((i) = json_first_child(object_or_array); \ + (i) != NULL; \ + (i) = (i)->next) + +/*** Construction and manipulation ***/ + +JsonNode *json_mknull(void); +JsonNode *json_mkbool(bool b); +JsonNode *json_mkstring(const char *s); +JsonNode *json_mknumber(double n); +JsonNode *json_mkarray(void); +JsonNode *json_mkobject(void); + +void json_append_element(JsonNode *array, JsonNode *element); +void json_prepend_element(JsonNode *array, JsonNode *element); +void json_append_member(JsonNode *object, const char *key, JsonNode *value); +void json_prepend_member(JsonNode *object, const char *key, JsonNode *value); + +void json_remove_from_parent(JsonNode *node); + +/*** Debugging ***/ + +/* + * Look for structure and encoding problems in a JsonNode or its descendents. + * + * If a problem is detected, return false, writing a description of the problem + * to errmsg (unless errmsg is NULL). + */ +bool json_check(const JsonNode *node, char errmsg[256]); + +#endif diff --git a/src/libsass/src/kwd_arg_macros.hpp b/src/libsass/src/kwd_arg_macros.hpp new file mode 100755 index 000000000..e135da7de --- /dev/null +++ b/src/libsass/src/kwd_arg_macros.hpp @@ -0,0 +1,28 @@ +#ifndef SASS_KWD_ARG_MACROS_H +#define SASS_KWD_ARG_MACROS_H + +// Example usage: +// KWD_ARG_SET(Args) { +// KWD_ARG(Args, string, foo); +// KWD_ARG(Args, int, bar); +// ... +// }; +// +// ... and later ... +// +// something(Args().foo("hey").bar(3)); + +#define KWD_ARG_SET(set_name) class set_name + +#define KWD_ARG(set_name, type, name) \ +private: \ + type name##_; \ +public: \ + set_name& name(type name##__) { \ + name##_ = name##__; \ + return *this; \ + } \ + type name() { return name##_; } \ +private: + +#endif diff --git a/src/libsass/src/lexer.cpp b/src/libsass/src/lexer.cpp new file mode 100755 index 000000000..464ca444a --- /dev/null +++ b/src/libsass/src/lexer.cpp @@ -0,0 +1,173 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include "lexer.hpp" +#include "constants.hpp" + + +namespace Sass { + using namespace Constants; + + namespace Prelexer { + + //#################################### + // BASIC CHARACTER MATCHERS + //#################################### + + // Match standard control chars + const char* kwd_at(const char* src) { return exactly<'@'>(src); } + const char* kwd_dot(const char* src) { return exactly<'.'>(src); } + const char* kwd_comma(const char* src) { return exactly<','>(src); }; + const char* kwd_colon(const char* src) { return exactly<':'>(src); }; + const char* kwd_star(const char* src) { return exactly<'*'>(src); }; + const char* kwd_plus(const char* src) { return exactly<'+'>(src); }; + const char* kwd_minus(const char* src) { return exactly<'-'>(src); }; + const char* kwd_slash(const char* src) { return exactly<'/'>(src); }; + + //#################################### + // implement some function that do exist in the standard + // but those are locale aware which brought some trouble + // this even seems to improve performance by quite a bit + //#################################### + + bool is_alpha(const char& chr) + { + return unsigned(chr - 'A') <= 'Z' - 'A' || + unsigned(chr - 'a') <= 'z' - 'a'; + } + + bool is_space(const char& chr) + { + // adapted the technique from is_alpha + return chr == ' ' || unsigned(chr - '\t') <= '\r' - '\t'; + } + + bool is_digit(const char& chr) + { + // adapted the technique from is_alpha + return unsigned(chr - '0') <= '9' - '0'; + } + + bool is_xdigit(const char& chr) + { + // adapted the technique from is_alpha + return unsigned(chr - '0') <= '9' - '0' || + unsigned(chr - 'a') <= 'f' - 'a' || + unsigned(chr - 'A') <= 'F' - 'A'; + } + + bool is_punct(const char& chr) + { + // locale independent + return chr == '.'; + } + + bool is_alnum(const char& chr) + { + return is_alpha(chr) || is_digit(chr); + } + + // check if char is outside ascii range + bool is_unicode(const char& chr) + { + // check for unicode range + return unsigned(chr) > 127; + } + + // check if char is outside ascii range + // but with specific ranges (copied from Ruby Sass) + bool is_nonascii(const char& chr) + { + return ( + (unsigned(chr) >= 128 && unsigned(chr) <= 15572911) || + (unsigned(chr) >= 15630464 && unsigned(chr) <= 15712189) || + (unsigned(chr) >= 4036001920) + ); + } + + // check if char is within a reduced ascii range + // valid in a uri (copied from Ruby Sass) + bool is_uri_character(const char& chr) + { + return (unsigned(chr) > 41 && unsigned(chr) < 127) || + unsigned(chr) == ':' || unsigned(chr) == '/'; + } + + // check if char is within a reduced ascii range + // valid for escaping (copied from Ruby Sass) + bool is_escapable_character(const char& chr) + { + return unsigned(chr) > 31 && unsigned(chr) < 127; + } + + // Match word character (look ahead) + bool is_character(const char& chr) + { + // valid alpha, numeric or unicode char (plus hyphen) + return is_alnum(chr) || is_unicode(chr) || chr == '-'; + } + + //#################################### + // BASIC CLASS MATCHERS + //#################################### + + // create matchers that advance the position + const char* space(const char* src) { return is_space(*src) ? src + 1 : 0; } + const char* alpha(const char* src) { return is_alpha(*src) ? src + 1 : 0; } + const char* unicode(const char* src) { return is_unicode(*src) ? src + 1 : 0; } + const char* nonascii(const char* src) { return is_nonascii(*src) ? src + 1 : 0; } + const char* digit(const char* src) { return is_digit(*src) ? src + 1 : 0; } + const char* xdigit(const char* src) { return is_xdigit(*src) ? src + 1 : 0; } + const char* alnum(const char* src) { return is_alnum(*src) ? src + 1 : 0; } + const char* punct(const char* src) { return is_punct(*src) ? src + 1 : 0; } + const char* hyphen(const char* src) { return *src && *src == '-' ? src + 1 : 0; } + const char* character(const char* src) { return is_character(*src) ? src + 1 : 0; } + const char* uri_character(const char* src) { return is_uri_character(*src) ? src + 1 : 0; } + const char* escapable_character(const char* src) { return is_escapable_character(*src) ? src + 1 : 0; } + + // Match multiple ctype characters. + const char* spaces(const char* src) { return one_plus(src); } + const char* digits(const char* src) { return one_plus(src); } + const char* hyphens(const char* src) { return one_plus(src); } + + // Whitespace handling. + const char* no_spaces(const char* src) { return negate< space >(src); } + const char* optional_spaces(const char* src) { return zero_plus< space >(src); } + + // Match any single character. + const char* any_char(const char* src) { return *src ? src + 1 : src; } + + // Match word boundary (zero-width lookahead). + const char* word_boundary(const char* src) { return is_character(*src) || *src == '#' ? 0 : src; } + + // Match linefeed /(?:\n|\r\n?)/ + const char* re_linebreak(const char* src) + { + // end of file or unix linefeed return here + if (*src == 0 || *src == '\n') return src + 1; + // a carriage return may optionally be followed by a linefeed + if (*src == '\r') return *(src + 1) == '\n' ? src + 2 : src + 1; + // no linefeed + return 0; + } + + // Assert string boundaries (/\Z|\z|\A/) + // This is a zero-width positive lookahead + const char* end_of_line(const char* src) + { + // end of file or unix linefeed return here + return *src == 0 || *src == '\n' || *src == '\r' ? src : 0; + } + + // Assert end_of_file boundary (/\z/) + // This is a zero-width positive lookahead + const char* end_of_file(const char* src) + { + // end of file or unix linefeed return here + return *src == 0 ? src : 0; + } + + } +} diff --git a/src/libsass/src/lexer.hpp b/src/libsass/src/lexer.hpp new file mode 100755 index 000000000..5356c4f9e --- /dev/null +++ b/src/libsass/src/lexer.hpp @@ -0,0 +1,306 @@ +#ifndef SASS_LEXER_H +#define SASS_LEXER_H + +#include + +namespace Sass { + namespace Prelexer { + + //#################################### + // BASIC CHARACTER MATCHERS + //#################################### + + // Match standard control chars + const char* kwd_at(const char* src); + const char* kwd_dot(const char* src); + const char* kwd_comma(const char* src); + const char* kwd_colon(const char* src); + const char* kwd_star(const char* src); + const char* kwd_plus(const char* src); + const char* kwd_minus(const char* src); + const char* kwd_slash(const char* src); + + //#################################### + // BASIC CLASS MATCHERS + //#################################### + + // These are locale independant + bool is_space(const char& src); + bool is_alpha(const char& src); + bool is_punct(const char& src); + bool is_digit(const char& src); + bool is_alnum(const char& src); + bool is_xdigit(const char& src); + bool is_unicode(const char& src); + bool is_nonascii(const char& src); + bool is_character(const char& src); + bool is_uri_character(const char& src); + bool escapable_character(const char& src); + + // Match a single ctype predicate. + const char* space(const char* src); + const char* alpha(const char* src); + const char* digit(const char* src); + const char* xdigit(const char* src); + const char* alnum(const char* src); + const char* punct(const char* src); + const char* hyphen(const char* src); + const char* unicode(const char* src); + const char* nonascii(const char* src); + const char* character(const char* src); + const char* uri_character(const char* src); + const char* escapable_character(const char* src); + + // Match multiple ctype characters. + const char* spaces(const char* src); + const char* digits(const char* src); + const char* hyphens(const char* src); + + // Whitespace handling. + const char* no_spaces(const char* src); + const char* optional_spaces(const char* src); + + // Match any single character (/./). + const char* any_char(const char* src); + + // Assert word boundary (/\b/) + // Is a zero-width positive lookaheads + const char* word_boundary(const char* src); + + // Match a single linebreak (/(?:\n|\r\n?)/). + const char* re_linebreak(const char* src); + + // Assert string boundaries (/\Z|\z|\A/) + // There are zero-width positive lookaheads + const char* end_of_line(const char* src); + + // Assert end_of_file boundary (/\z/) + const char* end_of_file(const char* src); + // const char* start_of_string(const char* src); + + // Type definition for prelexer functions + typedef const char* (*prelexer)(const char*); + + //#################################### + // BASIC "REGEX" CONSTRUCTORS + //#################################### + + // Match a single character literal. + // Regex equivalent: /(?:x)/ + template + const char* exactly(const char* src) { + return *src == chr ? src + 1 : 0; + } + + // Match the full string literal. + // Regex equivalent: /(?:literal)/ + template + const char* exactly(const char* src) { + if (str == 0) return 0; + const char* pre = str; + if (src == 0) return 0; + // there is a small chance that the search string + // is longer than the rest of the string to look at + while (*pre && *src == *pre) { + ++src, ++pre; + } + // did the matcher finish? + return *pre == 0 ? src : 0; + } + + + // Match the full string literal. + // Regex equivalent: /(?:literal)/i + // only define lower case alpha chars + template + const char* insensitive(const char* src) { + if (str == 0) return 0; + const char* pre = str; + if (src == 0) return 0; + // there is a small chance that the search string + // is longer than the rest of the string to look at + while (*pre && (*src == *pre || *src+32 == *pre)) { + ++src, ++pre; + } + // did the matcher finish? + return *pre == 0 ? src : 0; + } + + // Match for members of char class. + // Regex equivalent: /[axy]/ + template + const char* class_char(const char* src) { + const char* cc = char_class; + while (*cc && *src != *cc) ++cc; + return *cc ? src + 1 : 0; + } + + // Match for members of char class. + // Regex equivalent: /[axy]+/ + template + const char* class_chars(const char* src) { + const char* p = src; + while (class_char(p)) ++p; + return p == src ? 0 : p; + } + + // Match for members of char class. + // Regex equivalent: /[^axy]/ + template + const char* neg_class_char(const char* src) { + if (*src == 0) return 0; + const char* cc = neg_char_class; + while (*cc && *src != *cc) ++cc; + return *cc ? 0 : src + 1; + } + + // Match for members of char class. + // Regex equivalent: /[^axy]+/ + template + const char* neg_class_chars(const char* src) { + const char* p = src; + while (neg_class_char(p)) ++p; + return p == src ? 0 : p; + } + + // Match all except the supplied one. + // Regex equivalent: /[^x]/ + template + const char* any_char_but(const char* src) { + return (*src && *src != chr) ? src + 1 : 0; + } + + // Succeeds if the matcher fails. + // Aka. zero-width negative lookahead. + // Regex equivalent: /(?!literal)/ + template + const char* negate(const char* src) { + return mx(src) ? 0 : src; + } + + // Succeeds if the matcher succeeds. + // Aka. zero-width positive lookahead. + // Regex equivalent: /(?=literal)/ + // just hangs around until we need it + template + const char* lookahead(const char* src) { + return mx(src) ? src : 0; + } + + // Tries supplied matchers in order. + // Succeeds if one of them succeeds. + // Regex equivalent: /(?:FOO|BAR)/ + template + const char* alternatives(const char* src) { + const char* rslt; + if ((rslt = mx(src))) return rslt; + return 0; + } + template + const char* alternatives(const char* src) { + const char* rslt; + if ((rslt = mx1(src))) return rslt; + return alternatives(src); + } + + // Tries supplied matchers in order. + // Succeeds if all of them succeeds. + // Regex equivalent: /(?:FOO)(?:BAR)/ + template + const char* sequence(const char* src) { + const char* rslt = src; + if (!(rslt = mx1(rslt))) return 0; + return rslt; + } + template + const char* sequence(const char* src) { + const char* rslt = src; + if (!(rslt = mx1(rslt))) return 0; + return sequence(rslt); + } + + + // Match a pattern or not. Always succeeds. + // Regex equivalent: /(?:literal)?/ + template + const char* optional(const char* src) { + const char* p = mx(src); + return p ? p : src; + } + + // Match zero or more of the patterns. + // Regex equivalent: /(?:literal)*/ + template + const char* zero_plus(const char* src) { + const char* p = mx(src); + while (p) src = p, p = mx(src); + return src; + } + + // Match one or more of the patterns. + // Regex equivalent: /(?:literal)+/ + template + const char* one_plus(const char* src) { + const char* p = mx(src); + if (!p) return 0; + while (p) src = p, p = mx(src); + return src; + } + + // Match mx non-greedy until delimiter. + // Other prelexers are greedy by default. + // Regex equivalent: /(?:$mx)*?(?=$delim)\b/ + template + const char* non_greedy(const char* src) { + while (!delim(src)) { + const char* p = mx(src); + if (p == src) return 0; + if (p == 0) return 0; + src = p; + } + return src; + } + + //#################################### + // ADVANCED "REGEX" CONSTRUCTORS + //#################################### + + // Match with word boundary rule. + // Regex equivalent: /(?:$mx)\b/i + template + const char* keyword(const char* src) { + return sequence < + insensitive < str >, + word_boundary + >(src); + } + + // Match with word boundary rule. + // Regex equivalent: /(?:$mx)\b/ + template + const char* word(const char* src) { + return sequence < + exactly < str >, + word_boundary + >(src); + } + + template + const char* loosely(const char* src) { + return sequence < + optional_spaces, + exactly < chr > + >(src); + } + template + const char* loosely(const char* src) { + return sequence < + optional_spaces, + exactly < str > + >(src); + } + + } +} + +#endif diff --git a/src/libsass/src/listize.cpp b/src/libsass/src/listize.cpp new file mode 100755 index 000000000..7edb2359f --- /dev/null +++ b/src/libsass/src/listize.cpp @@ -0,0 +1,85 @@ +#include "sass.hpp" +#include +#include +#include + +#include "listize.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "error_handling.hpp" + +namespace Sass { + + Listize::Listize() + { } + + Expression_Ptr Listize::operator()(Selector_List_Ptr sel) + { + List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + l->from_selector(true); + for (size_t i = 0, L = sel->length(); i < L; ++i) { + if (!sel->at(i)) continue; + l->append(sel->at(i)->perform(this)); + } + if (l->length()) return l.detach(); + return SASS_MEMORY_NEW(Null, l->pstate()); + } + + Expression_Ptr Listize::operator()(Compound_Selector_Ptr sel) + { + std::string str; + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Expression_Ptr e = (*sel)[i]->perform(this); + if (e) str += e->to_string(); + } + return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); + } + + Expression_Ptr Listize::operator()(Complex_Selector_Ptr sel) + { + List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), 2); + l->from_selector(true); + Compound_Selector_Obj head = sel->head(); + if (head && !head->is_empty_reference()) + { + Expression_Ptr hh = head->perform(this); + if (hh) l->append(hh); + } + + std::string reference = ! sel->reference() ? "" + : sel->reference()->to_string(); + switch(sel->combinator()) + { + case Complex_Selector::PARENT_OF: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), ">")); + break; + case Complex_Selector::ADJACENT_TO: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "+")); + break; + case Complex_Selector::REFERENCE: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "/" + reference + "/")); + break; + case Complex_Selector::PRECEDES: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "~")); + break; + case Complex_Selector::ANCESTOR_OF: + break; + } + + Complex_Selector_Obj tail = sel->tail(); + if (tail) + { + Expression_Obj tt = tail->perform(this); + if (List_Ptr ls = SASS_MEMORY_CAST(List, tt)) + { l->concat(ls); } + } + if (l->length() == 0) return 0; + return l.detach(); + } + + Expression_Ptr Listize::fallback_impl(AST_Node_Ptr n) + { + return dynamic_cast(n); + } + +} diff --git a/src/libsass/src/listize.hpp b/src/libsass/src/listize.hpp new file mode 100755 index 000000000..208f43826 --- /dev/null +++ b/src/libsass/src/listize.hpp @@ -0,0 +1,35 @@ +#ifndef SASS_LISTIZE_H +#define SASS_LISTIZE_H + +#include +#include + +#include "ast.hpp" +#include "context.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + typedef Environment Env; + struct Backtrace; + + class Listize : public Operation_CRTP { + + Expression_Ptr fallback_impl(AST_Node_Ptr n); + + public: + Listize(); + ~Listize() { } + + Expression_Ptr operator()(Selector_List_Ptr); + Expression_Ptr operator()(Complex_Selector_Ptr); + Expression_Ptr operator()(Compound_Selector_Ptr); + + template + Expression_Ptr fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/libsass/src/mapping.hpp b/src/libsass/src/mapping.hpp new file mode 100755 index 000000000..54fb4a0f7 --- /dev/null +++ b/src/libsass/src/mapping.hpp @@ -0,0 +1,18 @@ +#ifndef SASS_MAPPING_H +#define SASS_MAPPING_H + +#include "position.hpp" + +namespace Sass { + + struct Mapping { + Position original_position; + Position generated_position; + + Mapping(const Position& original_position, const Position& generated_position) + : original_position(original_position), generated_position(generated_position) { } + }; + +} + +#endif diff --git a/src/libsass/src/memory/SharedPtr.cpp b/src/libsass/src/memory/SharedPtr.cpp new file mode 100755 index 000000000..53f368131 --- /dev/null +++ b/src/libsass/src/memory/SharedPtr.cpp @@ -0,0 +1,116 @@ +#include "../sass.hpp" +#include +#include + +#include "SharedPtr.hpp" +#include "../ast_fwd_decl.hpp" + +#ifdef DEBUG_SHARED_PTR +#include "../debugger.hpp" +#endif + +namespace Sass { + + #ifdef DEBUG_SHARED_PTR + void SharedObj::dumpMemLeaks() { + if (!all.empty()) { + std::cerr << "###################################\n"; + std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; + std::cerr << "###################################\n"; + for (auto var : all) { + if (AST_Node_Ptr ast = SASS_MEMORY_CAST_PTR(AST_Node, var)) { + debug_ast(ast); + } else { + std::cerr << "LEAKED " << var << "\n"; + } + } + } + } + std::vector SharedObj::all; + #endif + + bool SharedObj::taint = false; + + SharedObj::SharedObj() + : detached(false) + #ifdef DEBUG_SHARED_PTR + , dbg(false) + #endif + { + refcounter = 0; + #ifdef DEBUG_SHARED_PTR + if (taint) all.push_back(this); + #endif + }; + + SharedObj::~SharedObj() { + #ifdef DEBUG_SHARED_PTR + if (dbg) std::cerr << "Destruct " << this << "\n"; + if(!all.empty()) { // check needed for MSVC (no clue why?) + all.erase(std::remove(all.begin(), all.end(), this), all.end()); + } + #endif + }; + + + + void SharedPtr::decRefCount() { + if (node) { + -- node->refcounter; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "- " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; + #endif + if (node->refcounter == 0) { + #ifdef DEBUG_SHARED_PTR + AST_Node_Ptr ptr = SASS_MEMORY_CAST_PTR(AST_Node, node); + if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; + #endif + if (!node->detached) { + delete(node); + } + } + } + } + + void SharedPtr::incRefCount() { + if (node) { + ++ node->refcounter; + node->detached = false; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) { + std::cerr << "+ " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; + } + #endif + } + } + + SharedPtr::~SharedPtr() { + decRefCount(); + } + + + // the create constructor + SharedPtr::SharedPtr(SharedObj* ptr) + : node(ptr) { + incRefCount(); + } + // copy assignment operator + SharedPtr& SharedPtr::operator=(const SharedPtr& rhs) { + void* cur_ptr = (void*) node; + void* rhs_ptr = (void*) rhs.node; + if (cur_ptr == rhs_ptr) { + return *this; + } + decRefCount(); + node = rhs.node; + incRefCount(); + return *this; + } + + // the copy constructor + SharedPtr::SharedPtr(const SharedPtr& obj) + : node(obj.node) { + incRefCount(); + } + +} \ No newline at end of file diff --git a/src/libsass/src/memory/SharedPtr.hpp b/src/libsass/src/memory/SharedPtr.hpp new file mode 100755 index 000000000..3a85fb1a9 --- /dev/null +++ b/src/libsass/src/memory/SharedPtr.hpp @@ -0,0 +1,202 @@ +#ifndef SASS_MEMORY_SHARED_PTR_H +#define SASS_MEMORY_SHARED_PTR_H + +#include "sass/base.h" + +#include + +namespace Sass { + + class SharedPtr; + + /////////////////////////////////////////////////////////////////////////////// + // Use macros for the allocation task, since overloading operator `new` + // has been proven to be flaky under certain compilers (see comment below). + /////////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUG_SHARED_PTR + + #define SASS_MEMORY_NEW(Class, ...) \ + static_cast((new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy(__FILE__, __LINE__)) \ + + #define SASS_MEMORY_CLONE(obj) \ + ((obj)->clone(__FILE__, __LINE__)) \ + + #else + + #define SASS_MEMORY_NEW(Class, ...) \ + new Class(__VA_ARGS__) \ + + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy()) \ + + #define SASS_MEMORY_CLONE(obj) \ + ((obj)->clone()) \ + + #endif + + #define SASS_MEMORY_CAST(Class, obj) \ + (dynamic_cast(&obj)) \ + + #define SASS_MEMORY_CAST_PTR(Class, ptr) \ + (dynamic_cast(ptr)) \ + + class SharedObj { + protected: + friend class SharedPtr; + friend class Memory_Manager; + #ifdef DEBUG_SHARED_PTR + static std::vector all; + std::string file; + size_t line; + #endif + static bool taint; + long refcounter; + // long refcount; + bool detached; + #ifdef DEBUG_SHARED_PTR + bool dbg; + #endif + public: + #ifdef DEBUG_SHARED_PTR + static void dumpMemLeaks(); + SharedObj* trace(std::string file, size_t line) { + this->file = file; + this->line = line; + return this; + } + #endif + SharedObj(); + #ifdef DEBUG_SHARED_PTR + std::string getDbgFile() { + return file; + } + size_t getDbgLine() { + return line; + } + void setDbg(bool dbg) { + this->dbg = dbg; + } + #endif + static void setTaint(bool val) { + taint = val; + } + virtual ~SharedObj(); + long getRefCount() { + return refcounter; + } + }; + + + class SharedPtr { + private: + SharedObj* node; + private: + void decRefCount(); + void incRefCount(); + public: + // the empty constructor + SharedPtr() + : node(NULL) {}; + // the create constructor + SharedPtr(SharedObj* ptr); + // copy assignment operator + SharedPtr& operator=(const SharedPtr& rhs); + // move assignment operator + /* SharedPtr& operator=(SharedPtr&& rhs); */ + // the copy constructor + SharedPtr(const SharedPtr& obj); + // the move constructor + /* SharedPtr(SharedPtr&& obj); */ + // destructor + ~SharedPtr(); + public: + SharedObj* obj () { + return node; + }; + SharedObj* obj () const { + return node; + }; + SharedObj* operator-> () { + return node; + }; + bool isNull () { + return node == NULL; + }; + bool isNull () const { + return node == NULL; + }; + SharedObj* detach() { + node->detached = true; + return node; + }; + SharedObj* detach() const { + if (node) { + node->detached = true; + } + return node; + }; + operator bool() { + return node != NULL; + }; + operator bool() const { + return node != NULL; + }; + + }; + + template < typename T > + class SharedImpl : private SharedPtr { + public: + SharedImpl() + : SharedPtr(NULL) {}; + SharedImpl(T* node) + : SharedPtr(node) {}; + SharedImpl(T&& node) + : SharedPtr(node) {}; + SharedImpl(const T& node) + : SharedPtr(node) {}; + ~SharedImpl() {}; + public: + T* operator& () { + return static_cast(this->obj()); + }; + T* operator& () const { + return static_cast(this->obj()); + }; + T& operator* () { + return *static_cast(this->obj()); + }; + T& operator* () const { + return *static_cast(this->obj()); + }; + T* operator-> () { + return static_cast(this->obj()); + }; + T* operator-> () const { + return static_cast(this->obj()); + }; + T* ptr () { + return static_cast(this->obj()); + }; + T* detach() { + if (this->obj() == NULL) return NULL; + return static_cast(SharedPtr::detach()); + } + bool isNull() { + return this->obj() == NULL; + } + operator bool() { + return this->obj() != NULL; + }; + operator bool() const { + return this->obj() != NULL; + }; + }; + +} + +#endif \ No newline at end of file diff --git a/src/libsass/src/node.cpp b/src/libsass/src/node.cpp new file mode 100755 index 000000000..f408521a2 --- /dev/null +++ b/src/libsass/src/node.cpp @@ -0,0 +1,323 @@ +#include "sass.hpp" +#include + +#include "node.hpp" +#include "context.hpp" +#include "parser.hpp" + +namespace Sass { + + + Node Node::createCombinator(const Complex_Selector::Combinator& combinator) { + NodeDequePtr null; + return Node(COMBINATOR, combinator, NULL /*pSelector*/, null /*pCollection*/); + } + + + Node Node::createSelector(Complex_Selector_Ptr pSelector, Context& ctx) { + NodeDequePtr null; + + Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(pSelector); + pStripped->tail(NULL); + pStripped->combinator(Complex_Selector::ANCESTOR_OF); + + Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/); + if (pSelector) n.got_line_feed = pSelector->has_line_feed(); + return n; + } + + + Node Node::createCollection() { + NodeDequePtr pEmptyCollection = std::make_shared(); + return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pEmptyCollection); + } + + + Node Node::createCollection(const NodeDeque& values) { + NodeDequePtr pShallowCopiedCollection = std::make_shared(values); + return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pShallowCopiedCollection); + } + + + Node Node::createNil() { + NodeDequePtr null; + return Node(NIL, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, null /*pCollection*/); + } + + + Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection) + : got_line_feed(false), mType(type), mCombinator(combinator), mpSelector(pSelector), mpCollection(pCollection) + { if (pSelector) got_line_feed = pSelector->has_line_feed(); } + + + Node Node::klone(Context& ctx) const { + NodeDequePtr pNewCollection = std::make_shared(); + if (mpCollection) { + for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { + Node& toClone = *iter; + pNewCollection->push_back(toClone.klone(ctx)); + } + } + + Node n(mType, mCombinator, mpSelector ? SASS_MEMORY_COPY(mpSelector) : NULL, pNewCollection); + n.got_line_feed = got_line_feed; + return n; + } + + + bool Node::contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const { + bool found = false; + + for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { + Node& toTest = *iter; + + if (nodesEqual(toTest, potentialChild, simpleSelectorOrderDependent)) { + found = true; + break; + } + } + + return found; + } + + + bool Node::operator==(const Node& rhs) const { + return nodesEqual(*this, rhs, true /*simpleSelectorOrderDependent*/); + } + + + bool nodesEqual(const Node& lhs, const Node& rhs, bool simpleSelectorOrderDependent) { + if (lhs.type() != rhs.type()) { + return false; + } + + if (lhs.isCombinator()) { + + return lhs.combinator() == rhs.combinator(); + + } else if (lhs.isNil()) { + + return true; // no state to check + + } else if (lhs.isSelector()){ + + return selectors_equal(*&lhs.selector(), *&rhs.selector(), simpleSelectorOrderDependent); + + } else if (lhs.isCollection()) { + + if (lhs.collection()->size() != rhs.collection()->size()) { + return false; + } + + for (NodeDeque::iterator lhsIter = lhs.collection()->begin(), lhsIterEnd = lhs.collection()->end(), + rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { + + if (!nodesEqual(*lhsIter, *rhsIter, simpleSelectorOrderDependent)) { + return false; + } + + } + + return true; + + } + + // We shouldn't get here. + throw "Comparing unknown node types. A new type was probably added and this method wasn't implemented for it."; + } + + + void Node::plus(Node& rhs) { + if (!this->isCollection() || !rhs.isCollection()) { + throw "Both the current node and rhs must be collections."; + } + this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); + } + +#ifdef DEBUG + std::ostream& operator<<(std::ostream& os, const Node& node) { + + if (node.isCombinator()) { + + switch (node.combinator()) { + case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; + case Complex_Selector::PARENT_OF: os << "\">\""; break; + case Complex_Selector::PRECEDES: os << "\"~\""; break; + case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; + case Complex_Selector::REFERENCE: os << "\"/\""; break; + } + + } else if (node.isNil()) { + + os << "nil"; + + } else if (node.isSelector()){ + + os << node.selector()->head()->to_string(); + + } else if (node.isCollection()) { + + os << "["; + + for (NodeDeque::iterator iter = node.collection()->begin(), iterBegin = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { + if (iter != iterBegin) { + os << ", "; + } + + os << (*iter); + } + + os << "]"; + + } + + return os; + + } +#endif + + + Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx) { + if (pToConvert == NULL) { + return Node::createNil(); + } + Node node = Node::createCollection(); + node.got_line_feed = pToConvert->has_line_feed(); + bool has_lf = pToConvert->has_line_feed(); + + // unwrap the selector from parent ref + if (pToConvert->head() && pToConvert->head()->has_parent_ref()) { + Complex_Selector_Obj tail = pToConvert->tail(); + if (tail) tail->has_line_feed(pToConvert->has_line_feed()); + pToConvert = &tail; + } + + while (pToConvert) { + + bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); + + if (pToConvert->head() || empty_parent_ref) { + } + + // the first Complex_Selector may contain a dummy head pointer, skip it. + if (pToConvert->head() && !empty_parent_ref) { + node.collection()->push_back(Node::createSelector(pToConvert, ctx)); + if (has_lf) node.collection()->back().got_line_feed = has_lf; + has_lf = false; + } + + if (pToConvert->combinator() != Complex_Selector::ANCESTOR_OF) { + node.collection()->push_back(Node::createCombinator(pToConvert->combinator())); + if (has_lf) node.collection()->back().got_line_feed = has_lf; + has_lf = false; + } + + if (pToConvert && empty_parent_ref && pToConvert->tail()) { + // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); + } + + pToConvert = &pToConvert->tail(); + } + + return node; + } + + + Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx) { + if (toConvert.isNil()) { + return NULL; + } + + + if (!toConvert.isCollection()) { + throw "The node to convert to a Complex_Selector_Ptr must be a collection type or nil."; + } + + + NodeDeque& childNodes = *toConvert.collection(); + + std::string noPath(""); + Position noPosition(-1, -1, -1); + Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL); + + Complex_Selector_Obj pCurrent = pFirst; + + if (toConvert.isSelector()) pFirst->has_line_feed(toConvert.got_line_feed); + if (toConvert.isCombinator()) pFirst->has_line_feed(toConvert.got_line_feed); + + for (NodeDeque::iterator childIter = childNodes.begin(), childIterEnd = childNodes.end(); childIter != childIterEnd; childIter++) { + + Node& child = *childIter; + + if (child.isSelector()) { + // JMA - need to clone the selector, because they can end up getting shared across Node + // collections, and can result in an infinite loop during the call to parentSuperselector() + pCurrent->tail(SASS_MEMORY_COPY(child.selector())); + // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); + pCurrent = &pCurrent->tail(); + } else if (child.isCombinator()) { + pCurrent->combinator(child.combinator()); + if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); + + // if the next node is also a combinator, create another Complex_Selector to hold it so it doesn't replace the current combinator + if (childIter+1 != childIterEnd) { + Node& nextNode = *(childIter+1); + if (nextNode.isCombinator()) { + pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL)); + if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed); + pCurrent = &pCurrent->tail(); + } + } + } else { + throw "The node to convert's children must be only combinators or selectors."; + } + } + + // Put the dummy Compound_Selector in the first position, for consistency with the rest of libsass + Compound_Selector_Ptr fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[NODE]"), 1); + Parent_Selector_Ptr selectorRef = SASS_MEMORY_NEW(Parent_Selector, ParserState("[NODE]")); + fakeHead->elements().push_back(selectorRef); + if (toConvert.got_line_feed) pFirst->has_line_feed(toConvert.got_line_feed); + // pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed); + pFirst->head(fakeHead); + return SASS_MEMORY_COPY(pFirst); + } + + // A very naive trim function, which removes duplicates in a node + // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs + Node Node::naiveTrim(Node& seqses, Context& ctx) { + + std::vector res; + std::vector known; + + NodeDeque::reverse_iterator seqsesIter = seqses.collection()->rbegin(), + seqsesIterEnd = seqses.collection()->rend(); + + for (; seqsesIter != seqsesIterEnd; ++seqsesIter) + { + Node& seqs1 = *seqsesIter; + if( seqs1.isSelector() ) { + Complex_Selector_Obj sel = seqs1.selector(); + std::vector::iterator it; + bool found = false; + for (it = known.begin(); it != known.end(); ++it) { + if (**it == *sel) { found = true; break; } + } + if( !found ) { + known.push_back(seqs1.selector()); + res.push_back(&seqs1); + } + } else { + res.push_back(&seqs1); + } + } + + Node result = Node::createCollection(); + + for (size_t i = res.size() - 1; i != std::string::npos; --i) { + result.collection()->push_back(*res[i]); + } + + return result; + } +} diff --git a/src/libsass/src/node.hpp b/src/libsass/src/node.hpp new file mode 100755 index 000000000..21d10fb3a --- /dev/null +++ b/src/libsass/src/node.hpp @@ -0,0 +1,120 @@ +#ifndef SASS_NODE_H +#define SASS_NODE_H + +#include +#include + +#include "ast.hpp" + + +namespace Sass { + + + + + class Context; + + /* + There are a lot of stumbling blocks when trying to port the ruby extend code to C++. The biggest is the choice of + data type. The ruby code will pretty seamlessly switch types between an Array (libsass' + equivalent is the Complex_Selector) to a Sequence, which contains more metadata about the sequence than just the + selector info. They also have the ability to have arbitrary nestings of arrays like [1, [2]], which is hard to + implement using Array equivalents in C++ (like the deque or vector). They also have the ability to include nil + in the arrays, like [1, nil, 3], which has potential semantic differences than an empty array [1, [], 3]. To be + able to represent all of these as unique cases, we need to create a tree of variant objects. The tree nature allows + the inconsistent nesting levels. The variant nature (while making some of the C++ code uglier) allows the code to + more closely match the ruby code, which is a huge benefit when attempting to implement an complex algorithm like + the Extend operator. + + Note that the current libsass data model also pairs the combinator with the Complex_Selector that follows it, but + ruby sass has no such restriction, so we attempt to create a data structure that can handle them split apart. + */ + + class Node; + typedef std::deque NodeDeque; + typedef std::shared_ptr NodeDequePtr; + + class Node { + public: + enum TYPE { + SELECTOR, + COMBINATOR, + COLLECTION, + NIL + }; + + TYPE type() const { return mType; } + bool isCombinator() const { return mType == COMBINATOR; } + bool isSelector() const { return mType == SELECTOR; } + bool isCollection() const { return mType == COLLECTION; } + bool isNil() const { return mType == NIL; } + bool got_line_feed; + + Complex_Selector::Combinator combinator() const { return mCombinator; } + + Complex_Selector_Obj selector() { return mpSelector; } + Complex_Selector_Obj selector() const { return mpSelector; } + + NodeDequePtr collection() { return mpCollection; } + const NodeDequePtr collection() const { return mpCollection; } + + static Node createCombinator(const Complex_Selector::Combinator& combinator); + + // This method will klone the selector, stripping off the tail and combinator + static Node createSelector(Complex_Selector_Ptr pSelector, Context& ctx); + + static Node createCollection(); + static Node createCollection(const NodeDeque& values); + + static Node createNil(); + static Node naiveTrim(Node& seqses, Context& ctx); + + Node klone(Context& ctx) const; + + bool operator==(const Node& rhs) const; + inline bool operator!=(const Node& rhs) const { return !(*this == rhs); } + + + /* + COLLECTION FUNCTIONS + + Most types don't need any helper methods (nil and combinator due to their simplicity and + selector due to the fact that we leverage the non-node selector code on the Complex_Selector + whereever possible). The following methods are intended to be called on Node objects whose + type is COLLECTION only. + */ + + // rhs and this must be node collections. Shallow copy the nodes from rhs to the end of this. + // This function DOES NOT remove the nodes from rhs. + void plus(Node& rhs); + + // potentialChild must be a node collection of selectors/combinators. this must be a collection + // of collections of nodes/combinators. This method checks if potentialChild is a child of this + // Node. + bool contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const; + + private: + // Private constructor; Use the static methods (like createCombinator and createSelector) + // to instantiate this object. This is more expressive, and it allows us to break apart each + // case into separate functions. + Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection); + + TYPE mType; + + // TODO: can we union these to save on memory? + Complex_Selector::Combinator mCombinator; + Complex_Selector_Obj mpSelector; + NodeDequePtr mpCollection; + }; + +#ifdef DEBUG + std::ostream& operator<<(std::ostream& os, const Node& node); +#endif + Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx); + Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx); + + bool nodesEqual(const Node& one, const Node& two, bool simpleSelectorOrderDependent); + +} + +#endif diff --git a/src/libsass/src/operation.hpp b/src/libsass/src/operation.hpp new file mode 100755 index 000000000..fbb98bf57 --- /dev/null +++ b/src/libsass/src/operation.hpp @@ -0,0 +1,173 @@ +#ifndef SASS_OPERATION_H +#define SASS_OPERATION_H + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + template + class Operation { + public: + virtual T operator()(AST_Node_Ptr x) = 0; + virtual ~Operation() { } + // statements + virtual T operator()(Block_Ptr x) = 0; + virtual T operator()(Ruleset_Ptr x) = 0; + virtual T operator()(Bubble_Ptr x) = 0; + virtual T operator()(Trace_Ptr x) = 0; + virtual T operator()(Supports_Block_Ptr x) = 0; + virtual T operator()(Media_Block_Ptr x) = 0; + virtual T operator()(At_Root_Block_Ptr x) = 0; + virtual T operator()(Directive_Ptr x) = 0; + virtual T operator()(Keyframe_Rule_Ptr x) = 0; + virtual T operator()(Declaration_Ptr x) = 0; + virtual T operator()(Assignment_Ptr x) = 0; + virtual T operator()(Import_Ptr x) = 0; + virtual T operator()(Import_Stub_Ptr x) = 0; + virtual T operator()(Warning_Ptr x) = 0; + virtual T operator()(Error_Ptr x) = 0; + virtual T operator()(Debug_Ptr x) = 0; + virtual T operator()(Comment_Ptr x) = 0; + virtual T operator()(If_Ptr x) = 0; + virtual T operator()(For_Ptr x) = 0; + virtual T operator()(Each_Ptr x) = 0; + virtual T operator()(While_Ptr x) = 0; + virtual T operator()(Return_Ptr x) = 0; + virtual T operator()(Content_Ptr x) = 0; + virtual T operator()(Extension_Ptr x) = 0; + virtual T operator()(Definition_Ptr x) = 0; + virtual T operator()(Mixin_Call_Ptr x) = 0; + // expressions + virtual T operator()(List_Ptr x) = 0; + virtual T operator()(Map_Ptr x) = 0; + virtual T operator()(Binary_Expression_Ptr x) = 0; + virtual T operator()(Unary_Expression_Ptr x) = 0; + virtual T operator()(Function_Call_Ptr x) = 0; + virtual T operator()(Function_Call_Schema_Ptr x) = 0; + virtual T operator()(Custom_Warning_Ptr x) = 0; + virtual T operator()(Custom_Error_Ptr x) = 0; + virtual T operator()(Variable_Ptr x) = 0; + virtual T operator()(Textual_Ptr x) = 0; + virtual T operator()(Number_Ptr x) = 0; + virtual T operator()(Color_Ptr x) = 0; + virtual T operator()(Boolean_Ptr x) = 0; + virtual T operator()(String_Schema_Ptr x) = 0; + virtual T operator()(String_Quoted_Ptr x) = 0; + virtual T operator()(String_Constant_Ptr x) = 0; + virtual T operator()(Supports_Condition_Ptr x) = 0; + virtual T operator()(Supports_Operator_Ptr x) = 0; + virtual T operator()(Supports_Negation_Ptr x) = 0; + virtual T operator()(Supports_Declaration_Ptr x) = 0; + virtual T operator()(Supports_Interpolation_Ptr x) = 0; + virtual T operator()(Media_Query_Ptr x) = 0; + virtual T operator()(Media_Query_Expression_Ptr x) = 0; + virtual T operator()(At_Root_Query_Ptr x) = 0; + virtual T operator()(Null_Ptr x) = 0; + virtual T operator()(Parent_Selector_Ptr x) = 0; + // parameters and arguments + virtual T operator()(Parameter_Ptr x) = 0; + virtual T operator()(Parameters_Ptr x) = 0; + virtual T operator()(Argument_Ptr x) = 0; + virtual T operator()(Arguments_Ptr x) = 0; + // selectors + virtual T operator()(Selector_Schema_Ptr x) = 0; + virtual T operator()(Placeholder_Selector_Ptr x) = 0; + virtual T operator()(Element_Selector_Ptr x) = 0; + virtual T operator()(Class_Selector_Ptr x) = 0; + virtual T operator()(Id_Selector_Ptr x) = 0; + virtual T operator()(Attribute_Selector_Ptr x) = 0; + virtual T operator()(Pseudo_Selector_Ptr x) = 0; + virtual T operator()(Wrapped_Selector_Ptr x) = 0; + virtual T operator()(Compound_Selector_Ptr x)= 0; + virtual T operator()(Complex_Selector_Ptr x) = 0; + virtual T operator()(Selector_List_Ptr x) = 0; + + template + T fallback(U x) { return T(); } + }; + + template + class Operation_CRTP : public Operation { + public: + D& impl() { return static_cast(*this); } + public: + T operator()(AST_Node_Ptr x) { return static_cast(this)->fallback(x); } + // statements + T operator()(Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Ruleset_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Bubble_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Trace_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Media_Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Directive_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Keyframe_Rule_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Declaration_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Assignment_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Import_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Import_Stub_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Warning_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Error_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Debug_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Comment_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(If_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(For_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Each_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(While_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Return_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Content_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Extension_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Definition_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Mixin_Call_Ptr x) { return static_cast(this)->fallback(x); } + // expressions + T operator()(List_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Map_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Binary_Expression_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Unary_Expression_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Function_Call_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Function_Call_Schema_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Custom_Warning_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Custom_Error_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Variable_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Textual_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Number_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Color_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Boolean_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(String_Schema_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(String_Constant_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(String_Quoted_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Condition_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Operator_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Negation_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Declaration_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Interpolation_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Media_Query_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Media_Query_Expression_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Query_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Null_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Parent_Selector_Ptr x) { return static_cast(this)->fallback(x); } + // parameters and arguments + T operator()(Parameter_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Parameters_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Argument_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Arguments_Ptr x) { return static_cast(this)->fallback(x); } + // selectors + T operator()(Selector_Schema_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Placeholder_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Element_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Class_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Id_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Attribute_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Pseudo_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Wrapped_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Compound_Selector_Ptr x){ return static_cast(this)->fallback(x); } + T operator()(Complex_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Selector_List_Ptr x) { return static_cast(this)->fallback(x); } + + template + T fallback(U x) { return T(); } + }; + +} + +#endif diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp new file mode 100755 index 000000000..456020001 --- /dev/null +++ b/src/libsass/src/output.cpp @@ -0,0 +1,334 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "output.hpp" + +namespace Sass { + + Output::Output(Sass_Output_Options& opt) + : Inspect(Emitter(opt)), + charset(""), + top_nodes(0) + {} + + Output::~Output() { } + + void Output::fallback_impl(AST_Node_Ptr n) + { + return n->perform(this); + } + + void Output::operator()(Number_Ptr n) + { + // use values to_string facility + std::string res = n->to_string(opt); + // check for a valid unit here + // includes result for reporting + if (!n->is_valid_css_unit()) { + throw Exception::InvalidValue(*n); + } + // output the final token + append_token(res, n); + } + + void Output::operator()(Import_Ptr imp) + { + top_nodes.push_back(imp); + } + + void Output::operator()(Map_Ptr m) + { + std::string dbg(m->to_string(opt)); + error(dbg + " isn't a valid CSS value.", m->pstate()); + } + + OutputBuffer Output::get_buffer(void) + { + + Emitter emitter(opt); + Inspect inspect(emitter); + + size_t size_nodes = top_nodes.size(); + for (size_t i = 0; i < size_nodes; i++) { + top_nodes[i]->perform(&inspect); + inspect.append_mandatory_linefeed(); + } + + // flush scheduled outputs + // maybe omit semicolon if possible + inspect.finalize(wbuf.buffer.size() == 0); + // prepend buffer on top + prepend_output(inspect.output()); + // make sure we end with a linefeed + if (!ends_with(wbuf.buffer, opt.linefeed)) { + // if the output is not completely empty + if (!wbuf.buffer.empty()) append_string(opt.linefeed); + } + + // search for unicode char + for(const char& chr : wbuf.buffer) { + // skip all ascii chars + // static cast to unsigned to handle `char` being signed / unsigned + if (static_cast(chr) < 128) continue; + // declare the charset + if (output_style() != COMPRESSED) + charset = "@charset \"UTF-8\";" + + std::string(opt.linefeed); + else charset = "\xEF\xBB\xBF"; + // abort search + break; + } + + // add charset as first line, before comments and imports + if (!charset.empty()) prepend_string(charset); + + return wbuf; + + } + + void Output::operator()(Comment_Ptr c) + { + std::string txt = c->text()->to_string(opt); + // if (indentation && txt == "/**/") return; + bool important = c->is_important(); + if (output_style() != COMPRESSED || important) { + if (buffer().size() == 0) { + top_nodes.push_back(c); + } else { + in_comment = true; + append_indentation(); + c->text()->perform(this); + in_comment = false; + if (indentation == 0) { + append_mandatory_linefeed(); + } else { + append_optional_linefeed(); + } + } + } + } + + void Output::operator()(Ruleset_Ptr r) + { + Selector_Obj s = r->selector(); + Block_Obj b = r->block(); + + // Filter out rulesets that aren't printable (process its children though) + if (!Util::isPrintable(r, output_style())) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + const Statement_Obj& stm = b->at(i); + if (dynamic_cast(&stm)) { + if (!dynamic_cast(&stm)) { + stm->perform(this); + } + } + } + return; + } + + if (output_style() == NESTED) indentation += r->tabs(); + if (opt.source_comments) { + std::stringstream ss; + append_indentation(); + std::string path(File::abs2rel(r->pstate().path)); + ss << "/* line " << r->pstate().line + 1 << ", " << path << " */"; + append_string(ss.str()); + append_optional_linefeed(); + } + if (s) s->perform(this); + append_scope_opener(&b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + bool bPrintExpression = true; + // Check print conditions + if (Declaration_Ptr dec = SASS_MEMORY_CAST(Declaration, stm)) { + if (String_Constant_Ptr valConst = SASS_MEMORY_CAST(String_Constant, dec->value())) { + std::string val(valConst->value()); + if (String_Quoted_Ptr qstr = SASS_MEMORY_CAST_PTR(String_Quoted, valConst)) { + if (!qstr->quote_mark() && val.empty()) { + bPrintExpression = false; + } + } + } + else if (List_Ptr list = SASS_MEMORY_CAST(List, dec->value())) { + bool all_invisible = true; + for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { + Expression_Ptr item = &list->at(list_i); + if (!item->is_invisible()) all_invisible = false; + } + if (all_invisible) bPrintExpression = false; + } + } + // Print if OK + if (bPrintExpression) { + stm->perform(this); + } + } + if (output_style() == NESTED) indentation -= r->tabs(); + append_scope_closer(&b); + + } + void Output::operator()(Keyframe_Rule_Ptr r) + { + Block_Obj b = r->block(); + Selector_Obj v = r->name(); + + if (&v) { + v->perform(this); + } + + if (!b) { + append_colon_separator(); + return; + } + + append_scope_opener(); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + append_scope_closer(); + } + + void Output::operator()(Supports_Block_Ptr f) + { + if (f->is_invisible()) return; + + Supports_Condition_Obj c = f->condition(); + Block_Obj b = f->block(); + + // Filter out feature blocks that aren't printable (process its children though) + if (!Util::isPrintable(f, output_style())) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (dynamic_cast(&stm)) { + stm->perform(this); + } + } + return; + } + + if (output_style() == NESTED) indentation += f->tabs(); + append_indentation(); + append_token("@supports", f); + append_mandatory_space(); + c->perform(this); + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + + if (output_style() == NESTED) indentation -= f->tabs(); + + append_scope_closer(); + + } + + void Output::operator()(Media_Block_Ptr m) + { + if (m->is_invisible()) return; + + Block_Obj b = m->block(); + + // Filter out media blocks that aren't printable (process its children though) + if (!Util::isPrintable(m, output_style())) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (dynamic_cast(&stm)) { + stm->perform(this); + } + } + return; + } + if (output_style() == NESTED) indentation += m->tabs(); + append_indentation(); + append_token("@media", m); + append_mandatory_space(); + in_media_block = true; + m->media_queries()->perform(this); + in_media_block = false; + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->at(i)) { + Statement_Obj stm = b->at(i); + stm->perform(this); + } + if (i < L - 1) append_special_linefeed(); + } + + if (output_style() == NESTED) indentation -= m->tabs(); + append_scope_closer(); + } + + void Output::operator()(Directive_Ptr a) + { + std::string kwd = a->keyword(); + Selector_Obj s = a->selector(); + Expression_Obj v = a->value(); + Block_Obj b = a->block(); + + append_indentation(); + append_token(kwd, a); + if (s) { + append_mandatory_space(); + in_wrapped = true; + s->perform(this); + in_wrapped = false; + } + if (v) { + append_mandatory_space(); + // ruby sass bug? should use options? + append_token(v->to_string(/* opt */), &v); + } + if (!b) { + append_delimiter(); + return; + } + + if (b->is_invisible() || b->length() == 0) { + append_optional_space(); + return append_string("{}"); + } + + append_scope_opener(); + + bool format = kwd != "@font-face";; + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + if (i < L - 1 && format) append_special_linefeed(); + } + + append_scope_closer(); + } + + void Output::operator()(String_Quoted_Ptr s) + { + if (s->quote_mark()) { + append_token(quote(s->value(), s->quote_mark()), s); + } else if (!in_comment) { + append_token(string_to_output(s->value()), s); + } else { + append_token(s->value(), s); + } + } + + void Output::operator()(String_Constant_Ptr s) + { + std::string value(s->value()); + if (s->can_compress_whitespace() && output_style() == COMPRESSED) { + value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); + } + if (!in_comment) { + append_token(string_to_output(value), s); + } else { + append_token(value, s); + } + } + +} diff --git a/src/libsass/src/output.hpp b/src/libsass/src/output.hpp new file mode 100755 index 000000000..c460b13fe --- /dev/null +++ b/src/libsass/src/output.hpp @@ -0,0 +1,54 @@ +#ifndef SASS_OUTPUT_H +#define SASS_OUTPUT_H + +#include +#include + +#include "util.hpp" +#include "inspect.hpp" +#include "operation.hpp" + +namespace Sass { + class Context; + + // Refactor to make it generic to find linefeed (look behind) + inline bool ends_with(std::string const & value, std::string const & ending) + { + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + } + + class Output : public Inspect { + protected: + using Inspect::operator(); + + public: + Output(Sass_Output_Options& opt); + virtual ~Output(); + + protected: + std::string charset; + std::vector top_nodes; + + public: + OutputBuffer get_buffer(void); + + virtual void operator()(Map_Ptr); + virtual void operator()(Ruleset_Ptr); + virtual void operator()(Supports_Block_Ptr); + virtual void operator()(Media_Block_Ptr); + virtual void operator()(Directive_Ptr); + virtual void operator()(Keyframe_Rule_Ptr); + virtual void operator()(Import_Ptr); + virtual void operator()(Comment_Ptr); + virtual void operator()(Number_Ptr); + virtual void operator()(String_Quoted_Ptr); + virtual void operator()(String_Constant_Ptr); + + void fallback_impl(AST_Node_Ptr n); + + }; + +} + +#endif diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp new file mode 100755 index 000000000..89cdb9fe5 --- /dev/null +++ b/src/libsass/src/parser.cpp @@ -0,0 +1,2776 @@ +#include "sass.hpp" +#include +#include +#include +#include "parser.hpp" +#include "file.hpp" +#include "inspect.hpp" +#include "constants.hpp" +#include "util.hpp" +#include "prelexer.hpp" +#include "color_maps.hpp" +#include "sass/functions.h" +#include "error_handling.hpp" + +// Notes about delayed: some ast nodes can have delayed evaluation so +// they can preserve their original semantics if needed. This is most +// prominently exhibited by the division operation, since it is not +// only a valid operation, but also a valid css statement (i.e. for +// fonts, as in `16px/24px`). When parsing lists and expression we +// unwrap single items from lists and other operations. A nested list +// must not be delayed, only the items of the first level sometimes +// are delayed (as with argument lists). To achieve this we need to +// pass status to the list parser, so this can be set correctly. +// Another case with delayed values are colors. In compressed mode +// only processed values get compressed (other are left as written). + +#include +#include + +namespace Sass { + using namespace Constants; + using namespace Prelexer; + + Parser Parser::from_c_str(const char* beg, Context& ctx, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = end ? end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + void Parser::advanceToNextToken() { + lex < css_comments >(false); + // advance to position + pstate += pstate.offset; + pstate.offset.column = 0; + pstate.offset.line = 0; + } + + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, ParserState pstate, const char* source) + { + Parser p = Parser::from_c_str(beg, ctx, pstate, source); + // ToDo: ruby sass errors on parent references + // ToDo: remap the source-map entries somehow + return p.parse_selector_list(false); + } + + bool Parser::peek_newline(const char* start) + { + return peek_linefeed(start ? start : position) + && ! peek_css>(start); + } + + Parser Parser::from_token(Token t, Context& ctx, ParserState pstate, const char* source) + { + Parser p(ctx, pstate); + p.source = source ? source : t.begin; + p.position = t.begin ? t.begin : p.source; + p.end = t.end ? t.end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + /* main entry point to parse root block */ + Block_Obj Parser::parse() + { + bool is_root = false; + Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + read_bom(); + + // custom headers + if (ctx.resources.size() == 1) { + is_root = true; + ctx.apply_custom_headers(&root, path, pstate); + } + + block_stack.push_back(root); + /* bool rv = */ parse_block_nodes(is_root); + block_stack.pop_back(); + + // update for end position + root->update_pstate(pstate); + + if (position != end) { + css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); + } + + return root; + } + + + // convenience function for block parsing + // will create a new block ad-hoc for you + // this is the base block parsing function + Block_Obj Parser::parse_css_block(bool is_root) + { + + // parse comments before block + // lex < optional_css_comments >(); + + // lex mandatory opener or error out + if (!lex_css < exactly<'{'> >()) { + css_error("Invalid CSS", " after ", ": expected \"{\", was "); + } + // create new block and push to the selector stack + Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); + block_stack.push_back(block); + + if (!parse_block_nodes()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + + if (!lex_css < exactly<'}'> >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + + // update for end position + // this seems to be done somewhere else + // but that fixed selector schema issue + // block->update_pstate(pstate); + + // parse comments after block + // lex < optional_css_comments >(); + + block_stack.pop_back(); + + return █ + } + + // convenience function for block parsing + // will create a new block ad-hoc for you + // also updates the `in_at_root` flag + Block_Obj Parser::parse_block(bool is_root) + { + LOCAL_FLAG(in_at_root, is_root); + return parse_css_block(is_root); + } + + // the main block parsing function + // parses stuff between `{` and `}` + bool Parser::parse_block_nodes(bool is_root) + { + + // loop until end of string + while (position < end) { + + // we should be able to refactor this + parse_block_comments(); + lex < css_whitespace >(); + + if (lex < exactly<';'> >()) continue; + if (peek < end_of_file >()) return true; + if (peek < exactly<'}'> >()) return true; + + if (parse_block_node(is_root)) continue; + + parse_block_comments(); + + if (lex_css < exactly<';'> >()) continue; + if (peek_css < end_of_file >()) return true; + if (peek_css < exactly<'}'> >()) return true; + + // illegal sass + return false; + } + // return success + return true; + } + + // parser for a single node in a block + // semicolons must be lexed beforehand + bool Parser::parse_block_node(bool is_root) { + + Block_Obj block = block_stack.back(); + + parse_block_comments(); + + // throw away white-space + // includes line comments + lex < css_whitespace >(); + + Lookahead lookahead_result; + + // also parse block comments + + // first parse everything that is allowed in functions + if (lex < variable >(true)) { block->append(&parse_assignment()); } + else if (lex < kwd_err >(true)) { block->append(&parse_error()); } + else if (lex < kwd_dbg >(true)) { block->append(&parse_debug()); } + else if (lex < kwd_warn >(true)) { block->append(&parse_warning()); } + else if (lex < kwd_if_directive >(true)) { block->append(&parse_if_directive()); } + else if (lex < kwd_for_directive >(true)) { block->append(&parse_for_directive()); } + else if (lex < kwd_each_directive >(true)) { block->append(&parse_each_directive()); } + else if (lex < kwd_while_directive >(true)) { block->append(&parse_while_directive()); } + else if (lex < kwd_return_directive >(true)) { block->append(&parse_return_directive()); } + + // parse imports to process later + else if (lex < kwd_import >(true)) { + Scope parent = stack.empty() ? Scope::Rules : stack.back(); + if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { + if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 + error("Import directives may not be used within control directives or mixins.", pstate); + } + } + Import_Obj imp = parse_import(); + // if it is a url, we only add the statement + if (!imp->urls().empty()) block->append(&imp); + // process all resources now (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + else if (lex < kwd_extend >(true)) { + Lookahead lookahead = lookahead_for_include(position); + if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); + Selector_Obj target; + if (lookahead.has_interpolants) target = &parse_selector_schema(lookahead.found); + else target = &parse_selector_list(true); + block->append(SASS_MEMORY_NEW(Extension, pstate, &target)); + } + + // selector may contain interpolations which need delayed evaluation + else if (!(lookahead_result = lookahead_for_selector(position)).error) + { block->append(&parse_ruleset(lookahead_result, is_root)); } + + // parse multiple specific keyword directives + else if (lex < kwd_media >(true)) { block->append(&parse_media_block()); } + else if (lex < kwd_at_root >(true)) { block->append(&parse_at_root_block()); } + else if (lex < kwd_include_directive >(true)) { block->append(&parse_include_directive()); } + else if (lex < kwd_content_directive >(true)) { block->append(&parse_content_directive()); } + else if (lex < kwd_supports_directive >(true)) { block->append(&parse_supports_directive()); } + else if (lex < kwd_mixin >(true)) { block->append(&parse_definition(Definition::MIXIN)); } + else if (lex < kwd_function >(true)) { block->append(&parse_definition(Definition::FUNCTION)); } + + // ignore the @charset directive for now + else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } + + // generic at keyword (keep last) + else if (lex< re_special_directive >(true)) { block->append(&parse_special_directive()); } + else if (lex< re_prefixed_directive >(true)) { block->append(&parse_prefixed_directive()); } + else if (lex< at_keyword >(true)) { block->append(&parse_directive()); } + + else if (is_root /* && block->is_root() */) { + lex< css_whitespace >(); + if (position >= end) return true; + css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); + } + // parse a declaration + else + { + // ToDo: how does it handle parse errors? + // maybe we are expected to parse something? + Declaration_Obj decl = parse_declaration(); + decl->tabs(indentation); + block->append(&decl); + // maybe we have a "sub-block" + if (peek< exactly<'{'> >()) { + if (decl->is_indented()) ++ indentation; + // parse a propset that rides on the declaration's property + stack.push_back(Scope::Properties); + decl->block(parse_block()); + stack.pop_back(); + if (decl->is_indented()) -- indentation; + } + } + // something matched + return true; + } + // EO parse_block_nodes + + // parse imports inside the + Import_Obj Parser::parse_import() + { + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + std::vector> to_import; + bool first = true; + do { + while (lex< block_comment >()); + if (lex< quoted_string >()) { + to_import.push_back(std::pair(std::string(lexed), 0)); + } + else if (lex< uri_prefix >()) { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", &args); + + if (lex< quoted_string >()) { + Expression_Obj the_url = &parse_string(); + args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + } + else if (String_Obj the_url = parse_url_function_argument()) { + args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + } + else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { + Expression_Obj the_url = parse_list(); // parse_interpolated_chunk(lexed); + args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + } + else { + error("malformed URL", pstate); + } + if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate); + to_import.push_back(std::pair("", &result)); + } + else { + if (first) error("@import directive requires a url or quoted path", pstate); + else error("expecting another url or quoted path in @import list", pstate); + } + first = false; + } while (lex_css< exactly<','> >()); + + if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { + List_Obj import_queries = parse_media_queries(); + imp->import_queries(import_queries); + } + + for(auto location : to_import) { + if (location.second) { + imp->urls().push_back(&location.second); + } else if (!ctx.call_importers(unquote(location.first), path, pstate, &imp)) { + ctx.import_url(&imp, location.first, path); + } + } + + return imp; + } + + Definition_Obj Parser::parse_definition(Definition::Type which_type) + { + std::string which_str(lexed); + if (!lex< identifier >()) error("invalid name in " + which_str + " definition", pstate); + std::string name(Util::normalize_underscores(lexed)); + if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) + { error("Invalid function name \"" + name + "\".", pstate); } + ParserState source_position_of_def = pstate; + Parameters_Obj params = parse_parameters(); + if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); + else stack.push_back(Scope::Function); + Block_Obj body = parse_block(); + stack.pop_back(); + return SASS_MEMORY_NEW(Definition, source_position_of_def, name, ¶ms, &body, which_type); + } + + Parameters_Obj Parser::parse_parameters() + { + std::string name(lexed); + Position position = after_token; + Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do params->append(&parse_parameter()); + while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); + } + return params; + } + + Parameter_Obj Parser::parse_parameter() + { + while (lex< alternatives < spaces, block_comment > >()); + lex < variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState pos = pstate; + Expression_Obj val; + bool is_rest = false; + while (lex< alternatives < spaces, block_comment > >()); + if (lex< exactly<':'> >()) { // there's a default value + while (lex< block_comment >()); + val = parse_space_list(); + } + else if (lex< exactly< ellipsis > >()) { + is_rest = true; + } + return SASS_MEMORY_NEW(Parameter, pos, name, &val, is_rest); + } + + Arguments_Obj Parser::parse_arguments() + { + std::string name(lexed); + Position position = after_token; + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do args->append(&parse_argument()); + while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); + } + return args; + } + + Argument_Obj Parser::parse_argument() + { + if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { + position += 2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + Argument_Obj arg; + if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { + lex_css< variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState p = pstate; + lex_css< exactly<':'> >(); + Expression_Obj val = parse_space_list(); + arg = SASS_MEMORY_NEW(Argument, p, val, name); + } + else { + bool is_arglist = false; + bool is_keyword = false; + Expression_Obj val = parse_space_list(); + List_Ptr l = SASS_MEMORY_CAST(List, val); + if (lex_css< exactly< ellipsis > >()) { + if (val->concrete_type() == Expression::MAP || ( + (l != NULL && l->separator() == SASS_HASH) + )) is_keyword = true; + else is_arglist = true; + } + arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); + } + return arg; + } + + Assignment_Obj Parser::parse_assignment() + { + std::string name(Util::normalize_underscores(lexed)); + ParserState var_source_position = pstate; + if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement", pstate); + if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj val; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.has_interpolants && lookahead.found) { + val = &parse_value_schema(lookahead.found); + } else { + val = parse_list(); + } + bool is_default = false; + bool is_global = false; + while (peek< alternatives < default_flag, global_flag > >()) { + if (lex< default_flag >()) is_default = true; + else if (lex< global_flag >()) is_global = true; + } + return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); + } + + // a ruleset connects a selector and a block + Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead, bool is_root) + { + // make sure to move up the the last position + lex < optional_css_whitespace >(false, true); + // create the connector object (add parts later) + Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); + // parse selector static or as schema to be evaluated later + if (lookahead.parsable) ruleset->selector(&parse_selector_list(is_root)); + else ruleset->selector(&parse_selector_schema(lookahead.found)); + // then parse the inner block + stack.push_back(Scope::Rules); + ruleset->block(parse_block()); + stack.pop_back(); + // update for end position + ruleset->update_pstate(pstate); + ruleset->block()->update_pstate(pstate); + // inherit is_root from parent block + // need this info for sanity checks + ruleset->is_root(is_root); + // return AST Node + return ruleset; + } + + // parse a selector schema that will be evaluated in the eval stage + // uses a string schema internally to do the actual schema handling + // in the eval stage we will be re-parse it into an actual selector + Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector) + { + // move up to the start + lex< optional_spaces >(); + const char* i = position; + // selector schema re-uses string schema implementation + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + // the selector schema is pretty much just a wrapper for the string schema + Selector_Schema_Ptr selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + selector_schema->media_block(last_media_block); + + // process until end + while (i < end_of_selector) { + // try to parse mutliple interpolants + if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { + // accumulate the preceding segment if the position has advanced + if (i < p) { + std::string parsed(i, p); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + schema->append(&str); + } + + // check if the interpolation only contains white-space (error out) + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + // skip over all nested inner interpolations up to our own delimiter + const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); + // pass inner expression to the parser to resolve nested interpolations + pstate.add(p, p+2); + Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list(); + // set status on the list expression + interpolant->is_interpolant(true); + // schema->has_interpolants(true); + // add to the string schema + schema->append(&interpolant); + // advance parser state + pstate.add(p+2, j); + // advance position + i = j; + } + // no more interpolants have been found + // add the last segment if there is one + else { + // make sure to add the last bits of the string up to the end (if any) + if (i < end_of_selector) { + std::string parsed(i, end_of_selector); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + i = end_of_selector; + schema->append(&str); + } + // exit loop + } + } + // EO until eos + + // update position + position = i; + + // update for end position + selector_schema->update_pstate(pstate); + schema->update_pstate(pstate); + + after_token = before_token = pstate; + + // return parsed result + return selector_schema; + } + // EO parse_selector_schema + + void Parser::parse_charset_directive() + { + lex < + sequence < + quoted_string, + optional_spaces, + exactly <';'> + > + >(); + } + + // called after parsing `kwd_include_directive` + Mixin_Call_Obj Parser::parse_include_directive() + { + // lex identifier into `lexed` var + lex_identifier(); // may error out + // normalize underscores to hyphens + std::string name(Util::normalize_underscores(lexed)); + // create the initial mixin call object + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); + // parse mandatory arguments + call->arguments(parse_arguments()); + // parse optional block + if (peek < exactly <'{'> >()) { + call->block(parse_block()); + } + // return ast node + return call.detach(); + } + // EO parse_include_directive + + // parse a list of complex selectors + // this is the main entry point for most + Selector_List_Obj Parser::parse_selector_list(bool in_root) + { + bool reloop = true; + bool had_linefeed = false; + Complex_Selector_Obj sel; + Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); + group->media_block(last_media_block); + do { + reloop = false; + + had_linefeed = had_linefeed || peek_newline(); + + if (peek_css< class_char < selector_list_delims > >()) + break; // in case there are superfluous commas at the end + + + // now parse the complex selector + sel = parse_complex_selector(in_root); + + if (!sel) return group.detach(); + + sel->has_line_feed(had_linefeed); + + had_linefeed = false; + + while (peek_css< exactly<','> >()) + { + lex< css_comments >(false); + // consume everything up and including the comma separator + reloop = lex< exactly<','> >() != 0; + // remember line break (also between some commas) + had_linefeed = had_linefeed || peek_newline(); + // remember line break (also between some commas) + } + group->append(sel); + } + while (reloop); + while (lex_css< kwd_optional >()) { + group->is_optional(true); + } + // update for end position + group->update_pstate(pstate); + if (sel) sel->last()->has_line_break(false); + return group.detach(); + } + // EO parse_selector_list + + // a complex selector combines a compound selector with another + // complex selector, with one of four combinator operations. + // the compound selector (head) is optional, since the combinator + // can come first in the whole selector sequence (like `> DIV'). + Complex_Selector_Obj Parser::parse_complex_selector(bool in_root) + { + + String_Ptr reference = 0; + lex < block_comment >(); + advanceToNextToken(); + Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); + + // parse the left hand side + Compound_Selector_Obj lhs; + // special case if it starts with combinator ([+~>]) + if (!peek_css< class_char < selector_combinator_ops > >()) { + // parse the left hand side + lhs = parse_compound_selector(); + } + + // check for end of file condition + if (peek < end_of_file >()) return 0; + + // parse combinator between lhs and rhs + Complex_Selector::Combinator combinator; + if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; + else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; + else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; + else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { + // comments are allowed, but not spaces? + combinator = Complex_Selector::REFERENCE; + if (!lex < re_reference_combinator >()) return 0; + reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? + } + else /* if (lex< zero >()) */ combinator = Complex_Selector::ANCESTOR_OF; + + if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; + + // lex < block_comment >(); + sel->head(lhs); + sel->combinator(combinator); + sel->media_block(last_media_block); + + if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); + // has linfeed after combinator? + sel->has_line_break(peek_newline()); + // sel->has_line_feed(has_line_feed); + + // check if we got the abort condition (ToDo: optimize) + if (!peek_css< class_char < complex_selector_delims > >()) { + // parse next selector in sequence + sel->tail(parse_complex_selector(true)); + } + + // add a parent selector if we are not in a root + // also skip adding parent ref if we only have refs + if (!sel->has_parent_ref() && !in_at_root && !in_root) { + // create the objects to wrap parent selector reference + Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); + Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); + parent->media_block(last_media_block); + head->media_block(last_media_block); + // add simple selector + head->append(parent); + // selector may not have any head yet + if (!sel->head()) { sel->head(head); } + // otherwise we need to create a new complex selector and set the old one as its tail + else { + sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); + sel->media_block(last_media_block); + } + // peek for linefeed and remember result on head + // if (peek_newline()) head->has_line_break(true); + } + + sel->update_pstate(pstate); + // complex selector + return sel; + } + // EO parse_complex_selector + + // parse one compound selector, which is basically + // a list of simple selectors (directly adjacent) + // lex them exactly (without skipping white-space) + Compound_Selector_Obj Parser::parse_compound_selector() + { + // init an empty compound selector wrapper + Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); + seq->media_block(last_media_block); + + // skip initial white-space + lex< css_whitespace >(); + + // parse list + while (true) + { + // remove all block comments (don't skip white-space) + lex< delimited_by< slash_star, star_slash, false > >(false); + // parse functional + if (match < re_pseudo_selector >()) + { + seq->append(&parse_simple_selector()); + } + // parse parent selector + else if (lex< exactly<'&'> >(false)) + { + // this produces a linefeed!? + seq->has_parent_reference(true); + seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); + // parent selector only allowed at start + // upcoming Sass may allow also trailing + if (seq->length() > 1) { + ParserState state(pstate); + Simple_Selector_Obj cur = (*seq)[seq->length()-1]; + Simple_Selector_Obj prev = (*seq)[seq->length()-2]; + std::string sel(prev->to_string({ NESTED, 5 })); + std::string found(cur->to_string({ NESTED, 5 })); + if (lex < identifier >()) { found += std::string(lexed); } + error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" + "\"" + found + "\" may only be used at the beginning of a compound selector.", state); + } + } + // parse type selector + else if (lex< re_type_selector >(false)) + { + seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); + } + // peek for abort conditions + else if (peek< spaces >()) break; + else if (peek< end_of_file >()) { break; } + else if (peek_css < class_char < selector_combinator_ops > >()) break; + else if (peek_css < class_char < complex_selector_delims > >()) break; + // otherwise parse another simple selector + else { + Simple_Selector_Obj sel = parse_simple_selector(); + if (!sel) return 0; + seq->append(&sel); + } + } + + if (seq && !peek_css>()) { + seq->has_line_break(peek_newline()); + } + + // EO while true + return seq; + + } + // EO parse_compound_selector + + Simple_Selector_Obj Parser::parse_simple_selector() + { + lex < css_comments >(false); + if (lex< class_name >()) { + return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); + } + else if (lex< id_name >()) { + return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); + } + else if (lex< quoted_string >()) { + return SASS_MEMORY_NEW(Element_Selector, pstate, unquote(lexed)); + } + else if (lex< alternatives < variable, number, static_reference_combinator > >()) { + return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); + } + else if (peek< pseudo_not >()) { + return &parse_negated_selector(); + } + else if (peek< re_pseudo_selector >()) { + return &parse_pseudo_selector(); + } + else if (peek< exactly<':'> >()) { + return &parse_pseudo_selector(); + } + else if (lex < exactly<'['> >()) { + return &parse_attribute_selector(); + } + else if (lex< placeholder >()) { + Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); + sel->media_block(last_media_block); + return sel; + } + // failed + return 0; + } + + Wrapped_Selector_Obj Parser::parse_negated_selector() + { + lex< pseudo_not >(); + std::string name(lexed); + ParserState nsource_position = pstate; + Selector_List_Obj negated = parse_selector_list(true); + if (!lex< exactly<')'> >()) { + error("negated selector is missing ')'", pstate); + } + name.erase(name.size() - 1); + return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, &negated); + } + + // a pseudo selector often starts with one or two colons + // it can contain more selectors inside parentheses + Simple_Selector_Obj Parser::parse_pseudo_selector() { + if (lex< sequence< + optional < pseudo_prefix >, + // we keep the space within the name, strange enough + // ToDo: refactor output to schedule the space for it + // or do we really want to keep the real white-space? + sequence< identifier, optional < block_comment >, exactly<'('> > + > >()) + { + + std::string name(lexed); + name.erase(name.size() - 1); + ParserState p = pstate; + + // specially parse static stuff + // ToDo: really everything static? + if (peek_css < + sequence < + alternatives < + static_value, + binomial + >, + optional_css_whitespace, + exactly<')'> + > + >() + ) { + lex_css< alternatives < static_value, binomial > >(); + String_Constant_Ptr expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (expr && lex_css< exactly<')'> >()) { + expr->can_compress_whitespace(true); + return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); + } + } + else if (Selector_List_Obj wrapped = parse_selector_list(true)) { + if (wrapped && lex_css< exactly<')'> >()) { + return SASS_MEMORY_NEW(Wrapped_Selector, p, name, &wrapped); + } + } + + } + // EO if pseudo selector + + else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { + return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); + } + else if(lex < pseudo_prefix >()) { + css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + } + + css_error("Invalid CSS", " after ", ": expected \")\", was "); + + // unreachable statement + return 0; + } + + Attribute_Selector_Obj Parser::parse_attribute_selector() + { + ParserState p = pstate; + if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector", pstate); + std::string name(lexed); + if (lex_css< alternatives < exactly<']'>, exactly<'/'> > >()) return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0); + if (!lex_css< alternatives< exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match > >()) { + error("invalid operator in attribute selector for " + name, pstate); + } + std::string matcher(lexed); + + String_Obj value = 0; + if (lex_css< identifier >()) { + value = SASS_MEMORY_NEW(String_Constant, p, lexed); + } + else if (lex_css< quoted_string >()) { + value = &parse_interpolated_chunk(lexed, true); // needed! + } + else { + error("expected a string constant or identifier in attribute selector for " + name, pstate); + } + + if (!lex_css< alternatives < exactly<']'>, exactly<'/'> > >()) error("unterminated attribute selector for " + name, pstate); + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value); + } + + /* parse block comment and add to block */ + void Parser::parse_block_comments() + { + Block_Obj block = block_stack.back(); + + while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; + // flag on second param is to skip loosely over comments + String_Obj contents = parse_interpolated_chunk(lexed, true); + block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); + } + } + + Declaration_Obj Parser::parse_declaration() { + String_Obj prop; + if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { + prop = parse_identifier_schema(); + } + else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { + prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + else { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + bool is_indented = true; + const std::string property(lexed); + if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + property + "\" must be followed by a ':'", pstate); + lex < css_comments >(false); + if (peek_css< exactly<';'> >()) error("style declaration must contain a value", pstate); + if (peek_css< exactly<'{'> >()) is_indented = false; // don't indent if value is empty + if (peek_css< static_value >()) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, &parse_static_value()/*, lex()*/); + } + else { + Expression_Obj value; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.found) { + if (lookahead.has_interpolants) { + value = &parse_value_schema(lookahead.found); + } else { + value = &parse_list(DELAYED); + } + } + else { + value = &parse_list(DELAYED); + if (List_Ptr list = SASS_MEMORY_CAST(List, value)) { + if (list->length() == 0 && !peek< exactly <'{'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + } + lex < css_comments >(false); + Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); + decl->is_indented(is_indented); + decl->update_pstate(pstate); + return decl; + } + } + + // parse +/- and return false if negative + bool Parser::parse_number_prefix() + { + bool positive = true; + while(true) { + if (lex < block_comment >()) continue; + if (lex < number_prefix >()) continue; + if (lex < exactly < '-' > >()) { + positive = !positive; + continue; + } + break; + } + return positive; + } + + Expression_Obj Parser::parse_map() + { + Expression_Obj key = parse_list(); + List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); + + // it's not a map so return the lexed value as a list value + if (!lex_css< exactly<':'> >()) + { return key; } + + Expression_Obj value = parse_space_list(); + + map->append(key); + map->append(value); + + while (lex_css< exactly<','> >()) + { + // allow trailing commas - #495 + if (peek_css< exactly<')'> >(position)) + { break; } + + Expression_Obj key = parse_space_list(); + + if (!(lex< exactly<':'> >())) + { css_error("Invalid CSS", " after ", ": expected \":\", was "); } + + Expression_Obj value = parse_space_list(); + + map->append(key); + map->append(value); + } + + ParserState ps = map->pstate(); + ps.offset = pstate - ps + pstate.offset; + map->pstate(ps); + + return ↦ + } + + // parse list returns either a space separated list, + // a comma separated list or any bare expression found. + // so to speak: we unwrap items from lists if possible here! + Expression_Obj Parser::parse_list(bool delayed) + { + return parse_comma_list(delayed); + } + + // will return singletons unwrapped + Expression_Obj Parser::parse_comma_list(bool delayed) + { + // check if we have an empty list + // return the empty list as such + if (peek_css< alternatives < + // exactly<'!'>, + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + > >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0); + } + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + // set_delay doesn't apply to list children + // so this will only undelay single values + if (!delayed) list->set_delayed(false); + return list; + } + + // if we got so far, we actually do have a comma list + List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); + // wrap the first expression + comma_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< alternatives < + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + > >(position) + ) { break; } + // otherwise add another expression + comma_list->append(parse_space_list()); + } + // return the list + return &comma_list; + } + // EO parse_comma_list + + // will return singletons unwrapped + Expression_Obj Parser::parse_space_list() + { + Expression_Obj disj1 = parse_disjunction(); + // if it's a singleton, return it (don't wrap it) + if (peek_css< alternatives < + // exactly<'!'>, + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<','>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + > >(position) + ) { return disj1; } + + List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); + space_list->append(disj1); + + while (!(peek_css< alternatives < + // exactly<'!'>, + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<','>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + > >(position)) && peek_css< optional_css_whitespace >() != end + ) { + // the space is parsed implicitly? + space_list->append(parse_disjunction()); + } + // return the list + return &space_list; + } + // EO parse_space_list + + // parse logical OR operation + Expression_Obj Parser::parse_disjunction() + { + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side conjunction + Expression_Obj conj = parse_conjunction(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_or >()) + operands.push_back(parse_conjunction()); + // if it's a singleton, return it directly + if (operands.size() == 0) return conj; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_disjunction + + // parse logical AND operation + Expression_Obj Parser::parse_conjunction() + { + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side relation + Expression_Obj rel = parse_relation(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_and >()) { + operands.push_back(&parse_relation()); + } + // if it's a singleton, return it directly + if (operands.size() == 0) return rel; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_conjunction + + // parse comparison operations + Expression_Obj Parser::parse_relation() + { + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side expression + Expression_Obj lhs = parse_expression(); + std::vector operands; + std::vector operators; + // if it's a singleton, return it (don't wrap it) + while (peek< alternatives < + kwd_eq, + kwd_neq, + kwd_gte, + kwd_gt, + kwd_lte, + kwd_lt + > >(position)) + { + // is directly adjancent to expression? + bool left_ws = peek < css_comments >() != NULL; + // parse the operator + enum Sass_OP op + = lex() ? Sass_OP::EQ + : lex() ? Sass_OP::NEQ + : lex() ? Sass_OP::GTE + : lex() ? Sass_OP::LTE + : lex() ? Sass_OP::GT + : lex() ? Sass_OP::LT + // we checked the possibilities on top of fn + : Sass_OP::EQ; + // is directly adjacent to expression? + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ op, left_ws, right_ws }); + operands.push_back(&parse_expression()); + left_ws = peek < css_comments >() != NULL; + } + // we are called recursively for list, so we first + // fold inner binary expression which has delayed + // correctly set to zero. After folding we also unwrap + // single nested items. So we cannot set delay on the + // returned result here, as we have lost nestings ... + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // parse_relation + + // parse expression valid for operations + // called from parse_relation + // called from parse_for_directive + // called from parse_media_expression + // parse addition and subtraction operations + Expression_Obj Parser::parse_expression() + { + advanceToNextToken(); + ParserState state(pstate); + // parses multiple add and subtract operations + // NOTE: make sure that identifiers starting with + // NOTE: dashes do NOT count as subtract operation + Expression_Obj lhs = parse_operators(); + // if it's a singleton, return it (don't wrap it) + if (!(peek_css< exactly<'+'> >(position) || + // condition is a bit misterious, but some combinations should not be counted as operations + (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || + (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || + peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) + { return lhs; } + + std::vector operands; + std::vector operators; + bool left_ws = peek < css_comments >() != NULL; + while ( + lex_css< exactly<'+'> >() || + + ( + ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) + && lex_css< sequence< negate< digit >, exactly<'-'> > >() + ) + + ) { + + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); + operands.push_back(&parse_operators()); + left_ws = peek < css_comments >() != NULL; + } + + if (operands.size() == 0) return lhs; + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + + // parse addition and subtraction operations + Expression_Obj Parser::parse_operators() + { + advanceToNextToken(); + ParserState state(pstate); + Expression_Obj factor = parse_factor(); + // if it's a singleton, return it (don't wrap it) + std::vector operands; // factors + std::vector operators; // ops + // lex operations to apply to lhs + const char* left_ws = peek < css_comments >(); + while (lex_css< class_char< static_ops > >()) { + const char* right_ws = peek < css_comments >(); + switch(*lexed.begin) { + case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; + case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; + case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; + default: throw std::runtime_error("unknown static op parsed"); break; + } + operands.push_back(parse_factor()); + left_ws = peek < css_comments >(); + } + // operands and operators to binary expression + Expression_Obj ex = fold_operands(factor, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_operators + + + // called from parse_operators + // called from parse_value_schema + Expression_Obj Parser::parse_factor() + { + lex < css_comments >(false); + if (lex_css< exactly<'('> >()) { + // parse_map may return a list + Expression_Obj value = parse_map(); + // lex the expected closing parenthesis + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate); + // expression can be evaluated + return &value; + } + // string may be interpolated + // if (lex< quoted_string >()) { + // return &parse_string(); + // } + else if (peek< ie_property >()) { + return &parse_ie_property(); + } + else if (peek< ie_keyword_arg >()) { + return &parse_ie_keyword_arg(); + } + else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { + return &parse_calc_function(); + } + else if (lex < functional_schema >()) { + return &parse_function_call_schema(); + } + else if (lex< identifier_schema >()) { + String_Obj string = parse_identifier_schema(); + if (String_Schema_Ptr schema = SASS_MEMORY_CAST(String_Schema, string)) { + if (lex < exactly < '(' > >()) { + schema->append(&parse_list()); + lex < exactly < ')' > >(); + } + } + return &string; + } + else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { + return &parse_url_function_string(); + } + else if (peek< re_functional >()) { + return &parse_function_call(); + } + else if (lex< exactly<'+'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, &parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'-'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< sequence< kwd_not > >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, &parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { + if (parse_number_prefix()) return &parse_value(); // prefix is positive + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_value()); + if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else { + return parse_value(); + } + } + + // parse one value for a list + Expression_Obj Parser::parse_value() + { + lex< css_comments >(false); + if (lex< ampersand >()) + { + return SASS_MEMORY_NEW(Parent_Selector, pstate); } + + if (lex< kwd_important >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } + + // parse `10%4px` into separated items and not a schema + if (lex< sequence < percentage, lookahead < number > > >()) + { return SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); } + + if (lex< sequence < number, lookahead< sequence < op, number > > > >()) + { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); } + + // string may be interpolated + if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) + { return &parse_string(); } + + if (const char* stop = peek< value_schema >()) + { return &parse_value_schema(stop); } + + // string may be interpolated + if (lex< quoted_string >()) + { return &parse_string(); } + + if (lex< kwd_true >()) + { return SASS_MEMORY_NEW(Boolean, pstate, true); } + + if (lex< kwd_false >()) + { return SASS_MEMORY_NEW(Boolean, pstate, false); } + + if (lex< kwd_null >()) + { return SASS_MEMORY_NEW(Null, pstate); } + + if (lex< identifier >()) { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + + if (lex< percentage >()) + { return SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); } + + // match hex number first because 0x000 looks like a number followed by an identifier + if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) + { return SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, lexed); } + + if (lex< sequence < exactly <'#'>, identifier > >()) + { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } + + // also handle the 10em- foo special case + // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression + if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) + { return SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed); } + + if (lex< sequence< static_component, one_plus< strict_identifier > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + if (lex< number >()) + { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); } + + if (lex< variable >()) + { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } + + // Special case handling for `%` proceeding an interpolant. + if (lex< sequence< exactly<'%'>, optional< percentage > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + + // unreachable statement + return 0; + } + + // this parses interpolation inside other strings + // means the result should later be quoted again + String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant) + { + const char* i = chunk.begin; + // see if there any interpolants + const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + + if (!p) { + String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end)); + if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); + return str_quoted; + } + + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + schema->is_interpolant(true); + while (i < chunk.end) { + p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace + if (j) { --j; + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(&interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside string constant " + chunk.to_string(), pstate); + } + } + else { // no interpolants left; add the last segment if nonempty + // check if we need quotes here (was not sure after merge) + if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end))); + break; + } + ++ i; + } + + return schema; + } + + String_Constant_Obj Parser::parse_static_value() + { + lex< static_value >(); + Token str(lexed); + // static values always have trailing white- + // space and end delimiter (\s*[;]$) included + -- pstate.offset.column; + --str.end; + --position; + + String_Constant_Ptr str_node = SASS_MEMORY_NEW(String_Constant, pstate, str.time_wspace()); + return str_node; + } + + String_Obj Parser::parse_string() + { + return parse_interpolated_chunk(Token(lexed)); + } + + String_Obj Parser::parse_ie_property() + { + lex< ie_property >(); + Token str(lexed); + const char* i = str.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); + if (!p) { + return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); + } + + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < str.end) { + p = find_first_in_interval< exactly, block_comment >(i, str.end); + if (p) { + if (i < p) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty + } + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(&interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside IE function " + str.to_string(), pstate); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < str.end) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); + } + break; + } + } + return schema; + } + + String_Obj Parser::parse_ie_keyword_arg() + { + String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); + if (lex< variable >()) { + kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); + } else { + lex< alternatives< identifier_schema, identifier > >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + lex< exactly<'='> >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (peek< variable >()) kwd_arg->append(&parse_list()); + else if (lex< number >()) kwd_arg->append(SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, Util::normalize_decimals(lexed))); + else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(&parse_list()); } + return kwd_arg; + } + + String_Schema_Obj Parser::parse_value_schema(const char* stop) + { + // initialize the string schema object to add tokens + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + + if (peek>()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + const char* e = 0; + const char* ee = end; + end = stop; + size_t num_items = 0; + bool need_space = false; + while (position < stop) { + // parse space between tokens + if (lex< spaces >() && num_items) { + need_space = true; + } + if (need_space) { + need_space = false; + // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + } + if ((e = peek< re_functional >()) && e < stop) { + schema->append(&parse_function_call()); + } + // lex an interpolant /#{...}/ + else if (lex< exactly < hash_lbrace > >()) { + // Try to lex static expression first + if (peek< exactly< rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj ex = 0; + if (lex< re_static_expression >()) { + ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } else { + ex = parse_list(); + } + ex->is_interpolant(true); + schema->append(ex); + if (!lex < exactly < rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + } + // lex some string constants or other valid token + // Note: [-+] chars are left over from i.e. `#{3}+3` + else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + // lex a quoted string + else if (lex< quoted_string >()) { + // need_space = true; + // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + // else need_space = true; + schema->append(&parse_string()); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + if (peek < exactly < '-' > >()) break; + } + else if (lex< sequence < identifier > >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + } + // lex (normalized) variable + else if (lex< variable >()) { + std::string name(Util::normalize_underscores(lexed)); + schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); + } + // lex percentage value + else if (lex< percentage >()) { + schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed)); + } + // lex dimension value + else if (lex< dimension >()) { + schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed)); + } + // lex number value + else if (lex< number >()) { + schema->append( SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed)); + } + // lex hex color value + else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { + schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, lexed)); + } + else if (lex< sequence < exactly <'#'>, identifier > >()) { + schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); + } + // lex a value in parentheses + else if (peek< parenthese_scope >()) { + schema->append(&parse_factor()); + } + else { + break; + } + ++num_items; + } + if (position != stop) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); + position = stop; + } + end = ee; + return schema; + } + + // this parses interpolation outside other strings + // means the result must not be quoted again later + String_Obj Parser::parse_identifier_schema() + { + Token id(lexed); + const char* i = id.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); + if (!p) { + return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < id.end) { + p = find_first_in_interval< exactly, block_comment >(i, id.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + const char* o = position; position = i; + schema->append(&parse_value_schema(p)); + position = o; + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(DELAYED); + interp_node->is_interpolant(true); + schema->append(interp_node); + // schema->has_interpolants(true); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside interpolated identifier " + id.to_string(), pstate); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < end) { + const char* o = position; position = i; + schema->append(&parse_value_schema(id.end)); + position = o; + } + break; + } + } + return schema ? schema.detach() : 0; + } + + // calc functions should preserve arguments + Function_Call_Obj Parser::parse_calc_function() + { + lex< identifier >(); + std::string name(lexed); + ParserState call_pos = pstate; + lex< exactly<'('> >(); + ParserState arg_pos = pstate; + const char* arg_beg = position; + parse_list(); + const char* arg_end = position; + lex< skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > >(); + + Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, &parse_interpolated_chunk(Token(arg_beg, arg_end))); + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); + args->append(&arg); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + String_Obj Parser::parse_url_function_string() + { + std::string prefix(""); + if (lex< uri_prefix >()) { + prefix = std::string(lexed); + } + + lex < optional_spaces >(); + String_Obj url_string = parse_url_function_argument(); + + std::string suffix(""); + if (lex< real_uri_suffix >()) { + suffix = std::string(lexed); + } + + std::string uri(""); + if (&url_string) { + uri = url_string->to_string({ NESTED, 5 }); + } + + if (String_Schema_Ptr schema = dynamic_cast(&url_string)) { + String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); + res->append(schema); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); + return &res; + } else { + std::string res = prefix + uri + suffix; + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + } + + String_Obj Parser::parse_url_function_argument() + { + const char* p = position; + + std::string uri(""); + if (lex< real_uri_value >(false)) { + uri = lexed.to_string(); + } + + if (peek< exactly< hash_lbrace > >()) { + const char* pp = position; + // TODO: error checking for unclosed interpolants + while (pp && peek< exactly< hash_lbrace > >(pp)) { + pp = sequence< interpolant, real_uri_value >(pp); + } + position = pp; + return &parse_interpolated_chunk(Token(p, position)); + } + else if (uri != "") { + std::string res = Util::rtrim(uri); + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + + return 0; + } + + Function_Call_Obj Parser::parse_function_call() + { + lex< identifier >(); + std::string name(lexed); + + ParserState call_pos = pstate; + Arguments_Obj args = parse_arguments(); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + Function_Call_Schema_Obj Parser::parse_function_call_schema() + { + String_Obj name = parse_identifier_schema(); + ParserState source_position_of_call = pstate; + Arguments_Obj args = parse_arguments(); + + return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); + } + + Content_Obj Parser::parse_content_directive() + { + return SASS_MEMORY_NEW(Content, pstate); + } + + If_Obj Parser::parse_if_directive(bool else_if) + { + stack.push_back(Scope::Control); + ParserState if_source_position = pstate; + bool root = block_stack.back()->is_root(); + Expression_Obj predicate = parse_list(); + Block_Obj block = parse_block(root); + Block_Obj alternative = NULL; + + // only throw away comment if we parse a case + // we want all other comments to be parsed + if (lex_css< elseif_directive >()) { + alternative = SASS_MEMORY_NEW(Block, pstate); + alternative->append(&parse_if_directive(true)); + } + else if (lex_css< kwd_else_directive >()) { + alternative = &parse_block(root); + } + stack.pop_back(); + return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); + } + + For_Obj Parser::parse_for_directive() + { + stack.push_back(Scope::Control); + ParserState for_source_position = pstate; + bool root = block_stack.back()->is_root(); + lex_variable(); + std::string var(Util::normalize_underscores(lexed)); + if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive", pstate); + Expression_Obj lower_bound = parse_expression(); + bool inclusive = false; + if (lex< kwd_through >()) inclusive = true; + else if (lex< kwd_to >()) inclusive = false; + else error("expected 'through' or 'to' keyword in @for directive", pstate); + Expression_Obj upper_bound = parse_expression(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); + } + + // helper to parse a var token + Token Parser::lex_variable() + { + // peek for dollar sign first + if (!peek< exactly <'$'> >()) { + css_error("Invalid CSS", " after ", ": expected \"$\", was "); + } + // we expect a simple identifier as the call name + if (!lex< sequence < exactly <'$'>, identifier > >()) { + lex< exactly <'$'> >(); // move pstate and position up + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + // helper to parse identifier + Token Parser::lex_identifier() + { + // we expect a simple identifier as the call name + if (!lex< identifier >()) { // ToDo: pstate wrong? + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + + Each_Obj Parser::parse_each_directive() + { + stack.push_back(Scope::Control); + ParserState each_source_position = pstate; + bool root = block_stack.back()->is_root(); + std::vector vars; + lex_variable(); + vars.push_back(Util::normalize_underscores(lexed)); + while (lex< exactly<','> >()) { + if (!lex< variable >()) error("@each directive requires an iteration variable", pstate); + vars.push_back(Util::normalize_underscores(lexed)); + } + if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive", pstate); + Expression_Obj list = parse_list(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); + } + + // called after parsing `kwd_while_directive` + While_Obj Parser::parse_while_directive() + { + stack.push_back(Scope::Control); + bool root = block_stack.back()->is_root(); + // create the initial while call object + While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); + // parse mandatory predicate + Expression_Obj predicate = parse_list(); + call->predicate(predicate); + // parse mandatory block + call->block(parse_block(root)); + // return ast node + stack.pop_back(); + // return ast node + return call.detach(); + } + + // EO parse_while_directive + Media_Block_Obj Parser::parse_media_block() + { + stack.push_back(Scope::Media); + Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); + + media_block->media_queries(parse_media_queries()); + + Media_Block_Obj prev_media_block = last_media_block; + last_media_block = &media_block; + media_block->block(parse_css_block()); + last_media_block = &prev_media_block; + stack.pop_back(); + return media_block.detach(); + } + + List_Obj Parser::parse_media_queries() + { + advanceToNextToken(); + List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); + if (!peek_css < exactly <'{'> >()) queries->append(&parse_media_query()); + while (lex_css < exactly <','> >()) queries->append(&parse_media_query()); + queries->update_pstate(pstate); + return queries.detach(); + } + + // Expression_Ptr Parser::parse_media_query() + Media_Query_Obj Parser::parse_media_query() + { + advanceToNextToken(); + Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); + if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } + else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } + + if (lex < identifier_schema >()) media_query->media_type(&parse_identifier_schema()); + else if (lex < identifier >()) media_query->media_type(&parse_interpolated_chunk(lexed)); + else media_query->append(&parse_media_expression()); + + while (lex_css < kwd_and >()) media_query->append(&parse_media_expression()); + if (lex < identifier_schema >()) { + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + schema->append(&media_query->media_type()); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + schema->append(&parse_identifier_schema()); + media_query->media_type(schema); + } + while (lex_css < kwd_and >()) media_query->append(&parse_media_expression()); + + media_query->update_pstate(pstate); + + return media_query; + } + + Media_Query_Expression_Obj Parser::parse_media_expression() + { + if (lex < identifier_schema >()) { + String_Obj ss = parse_identifier_schema(); + return SASS_MEMORY_NEW(Media_Query_Expression, pstate, &ss, 0, true); + } + if (!lex_css< exactly<'('> >()) { + error("media query expression must begin with '('", pstate); + } + Expression_Obj feature = 0; + if (peek_css< exactly<')'> >()) { + error("media feature required in media query expression", pstate); + } + feature = &parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = &parse_list(DELAYED); + } + if (!lex_css< exactly<')'> >()) { + error("unclosed parenthesis in media query expression", pstate); + } + return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); + } + + // lexed after `kwd_supports_directive` + // these are very similar to media blocks + Supports_Block_Obj Parser::parse_supports_directive() + { + Supports_Condition_Obj cond = parse_supports_condition(); + // create the ast node object for the support queries + Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); + // additional block is mandatory + // parse inner block + query->block(parse_block()); + // return ast node + return query; + } + + // parse one query operation + // may encounter nested queries + Supports_Condition_Obj Parser::parse_supports_condition() + { + lex < css_whitespace >(); + Supports_Condition_Obj cond = 0; + if ((cond = parse_supports_negation())) return cond; + if ((cond = parse_supports_operator())) return cond; + if ((cond = parse_supports_interpolation())) return cond; + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_negation() + { + if (!lex < kwd_not >()) return 0; + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + return SASS_MEMORY_NEW(Supports_Negation, pstate, &cond); + } + + Supports_Condition_Obj Parser::parse_supports_operator() + { + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + if (!&cond) return 0; + + while (true) { + Supports_Operator::Operand op = Supports_Operator::OR; + if (lex < kwd_and >()) { op = Supports_Operator::AND; } + else if(!lex < kwd_or >()) { break; } + + lex < css_whitespace >(); + Supports_Condition_Obj right = parse_supports_condition_in_parens(); + + // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); + cond = SASS_MEMORY_NEW(Supports_Operator, pstate, &cond, &right, op); + } + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_interpolation() + { + if (!lex < interpolant >()) return 0; + + String_Obj interp = parse_interpolated_chunk(lexed); + if (!interp) return 0; + + return SASS_MEMORY_NEW(Supports_Interpolation, pstate, &interp); + } + + // TODO: This needs some major work. Although feature conditions + // look like declarations their semantics differ significantly + Supports_Condition_Obj Parser::parse_supports_declaration() + { + Supports_Condition_Ptr cond = 0; + // parse something declaration like + Declaration_Obj declaration = parse_declaration(); + if (!declaration) error("@supports condition expected declaration", pstate); + cond = SASS_MEMORY_NEW(Supports_Declaration, + declaration->pstate(), + &declaration->property(), + declaration->value()); + // ToDo: maybe we need an additional error condition? + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_condition_in_parens() + { + Supports_Condition_Obj interp = parse_supports_interpolation(); + if (&interp != 0) return interp; + + if (!lex < exactly <'('> >()) return 0; + lex < css_whitespace >(); + + Supports_Condition_Obj cond = parse_supports_condition(); + if (&cond != 0) { + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + } else { + cond = parse_supports_declaration(); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + } + lex < css_whitespace >(); + return cond; + } + + At_Root_Block_Obj Parser::parse_at_root_block() + { + ParserState at_source_position = pstate; + Block_Obj body = 0; + At_Root_Query_Obj expr; + Lookahead lookahead_result; + LOCAL_FLAG(in_at_root, true); + if (lex_css< exactly<'('> >()) { + expr = parse_at_root_query(); + } + if (peek_css < exactly<'{'> >()) { + lex (); + body = &parse_block(true); + } + else if ((lookahead_result = lookahead_for_selector(position)).found) { + Ruleset_Obj r = parse_ruleset(lookahead_result, false); + body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); + body->append(&r); + } + At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); + if (&expr) at_root->expression(&expr); + return at_root; + } + + At_Root_Query_Obj Parser::parse_at_root_query() + { + if (peek< exactly<')'> >()) error("at-root feature required in at-root expression", pstate); + + if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { + css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); + } + + Expression_Obj feature = parse_list(); + if (!lex_css< exactly<':'> >()) error("style declaration must contain a value", pstate); + Expression_Obj expression = parse_list(); + List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); + + if (expression->concrete_type() == Expression::LIST) { + value = SASS_MEMORY_CAST(List, expression); + } + else value->append(expression); + + At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, + value->pstate(), + feature, + &value); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression", pstate); + return cond; + } + + Directive_Obj Parser::parse_special_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + + Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(&parse_selector_list(true)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(&parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + Directive_Obj Parser::parse_prefixed_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + + Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(&parse_selector_list(true)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(&parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(&parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + + Directive_Obj Parser::parse_directive() + { + Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); + String_Schema_Obj val = parse_almost_any_value(); + // strip left and right if they are of type string + directive->value(&val); + if (peek< exactly<'{'> >()) { + directive->block(parse_block()); + } + return directive; + } + + Expression_Obj Parser::lex_interpolation() + { + if (lex < interpolant >(true) != NULL) { + return &parse_interpolated_chunk(lexed, true); + } + return 0; + } + + Expression_Obj Parser::lex_interp_uri() + { + // create a string schema by lexing optional interpolations + return lex_interp< re_string_uri_open, re_string_uri_close >(); + } + + Expression_Obj Parser::lex_interp_string() + { + Expression_Obj rv = 0; + if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; + if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; + return rv; + } + + Expression_Obj Parser::lex_almost_any_value_chars() + { + const char* match = + lex < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + sequence < + exactly < url_kwd >, + exactly <'('> + > + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + > + >(false); + if (match) { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + return NULL; + } + + Expression_Obj Parser::lex_almost_any_value_token() + { + Expression_Obj rv = 0; + if (*position == 0) return 0; + if ((rv = &lex_almost_any_value_chars())) return rv; + // if ((rv = lex_block_comment())) return rv; + // if ((rv = lex_single_line_comment())) return rv; + if ((rv = &lex_interp_string())) return rv; + if ((rv = &lex_interp_uri())) return rv; + if ((rv = &lex_interpolation())) return rv; + return rv; + } + + String_Schema_Obj Parser::parse_almost_any_value() + { + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if (*position == 0) return 0; + lex < spaces >(false); + Expression_Obj token = lex_almost_any_value_token(); + if (!token) return 0; + schema->append(token); + if (*position == 0) { + schema->rtrim(); + return schema.detach(); + } + + while ((token = &lex_almost_any_value_token())) { + schema->append(token); + } + + lex < css_whitespace >(); + + schema->rtrim(); + + return schema.detach(); + } + + Warning_Obj Parser::parse_warning() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + } + return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); + } + + Error_Obj Parser::parse_error() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + } + return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); + } + + Debug_Obj Parser::parse_debug() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + } + return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); + } + + Return_Obj Parser::parse_return_directive() + { + // check that we do not have an empty list (ToDo: check if we got all cases) + if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) + { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } + return SASS_MEMORY_NEW(Return, pstate, &parse_list()); + } + + Lookahead Parser::lookahead_for_selector(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + rv.error = p; + if (const char* q = + peek < + re_selector_list + >(p) + ) { + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + ++ p; + } + // store anyway } + + + // ToDo: remove + rv.error = q; + rv.position = q; + // check expected opening bracket + // only after successfull matching + if (peek < exactly<'{'> >(q)) rv.found = q; + else if (peek < exactly<'('> >(q)) rv.found = q; + // else if (peek < exactly<';'> >(q)) rv.found = q; + // else if (peek < exactly<'}'> >(q)) rv.found = q; + if (rv.found || *p == 0) rv.error = 0; + } + + rv.parsable = ! rv.has_interpolants; + + // return result + return rv; + + } + // EO lookahead_for_selector + + // used in parse_block_nodes and parse_special_directive + // ToDo: actual usage is still not really clear to me? + Lookahead Parser::lookahead_for_include(const char* start) + { + // we actually just lookahead for a selector + Lookahead rv = lookahead_for_selector(start); + // but the "found" rules are different + if (const char* p = rv.position) { + // check for additional abort condition + if (peek < exactly<';'> >(p)) rv.found = p; + else if (peek < exactly<'}'> >(p)) rv.found = p; + } + // return result + return rv; + } + // EO lookahead_for_include + + // look ahead for a token with interpolation in it + // we mostly use the result if there is an interpolation + // everything that passes here gets parsed as one schema + // meaning it will not be parsed as a space separated list + Lookahead Parser::lookahead_for_value(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + if (const char* q = + peek < + non_greedy < + alternatives < + // consume whitespace + block_comment, // spaces, + // main tokens + sequence < + interpolant, + optional < + quoted_string + > + >, + identifier, + variable, + // issue #442 + sequence < + parenthese_scope, + interpolant, + optional < + quoted_string + > + > + >, + sequence < + // optional_spaces, + alternatives < + exactly<'{'>, + exactly<'}'>, + exactly<';'> + > + > + > + >(p) + ) { + if (p == q) return rv; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + ++ p; + } + // store anyway + // ToDo: remove + rv.position = q; + // check expected opening bracket + // only after successful matching + if (peek < exactly<'{'> >(q)) rv.found = q; + else if (peek < exactly<';'> >(q)) rv.found = q; + else if (peek < exactly<'}'> >(q)) rv.found = q; + } + + // return result + return rv; + } + // EO lookahead_for_value + + void Parser::read_bom() + { + size_t skip = 0; + std::string encoding; + bool utf_8 = false; + switch ((unsigned char) source[0]) { + case 0xEF: + skip = check_bom_chars(source, end, utf_8_bom, 3); + encoding = "UTF-8"; + utf_8 = true; + break; + case 0xFE: + skip = check_bom_chars(source, end, utf_16_bom_be, 2); + encoding = "UTF-16 (big endian)"; + break; + case 0xFF: + skip = check_bom_chars(source, end, utf_16_bom_le, 2); + skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); + encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); + break; + case 0x00: + skip = check_bom_chars(source, end, utf_32_bom_be, 4); + encoding = "UTF-32 (big endian)"; + break; + case 0x2B: + skip = check_bom_chars(source, end, utf_7_bom_1, 4) + | check_bom_chars(source, end, utf_7_bom_2, 4) + | check_bom_chars(source, end, utf_7_bom_3, 4) + | check_bom_chars(source, end, utf_7_bom_4, 4) + | check_bom_chars(source, end, utf_7_bom_5, 5); + encoding = "UTF-7"; + break; + case 0xF7: + skip = check_bom_chars(source, end, utf_1_bom, 3); + encoding = "UTF-1"; + break; + case 0xDD: + skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); + encoding = "UTF-EBCDIC"; + break; + case 0x0E: + skip = check_bom_chars(source, end, scsu_bom, 3); + encoding = "SCSU"; + break; + case 0xFB: + skip = check_bom_chars(source, end, bocu_1_bom, 3); + encoding = "BOCU-1"; + break; + case 0x84: + skip = check_bom_chars(source, end, gb_18030_bom, 4); + encoding = "GB-18030"; + break; + } + if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding, pstate); + position += skip; + } + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) + { + size_t skip = 0; + if (src + len > end) return 0; + for (size_t i = 0; i < len; ++i, ++skip) { + if ((unsigned char) src[i] != bom[i]) return 0; + } + return skip; + } + + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) + { + for (size_t i = 0, S = operands.size(); i < S; ++i) { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, &base, operands[i]); + } + return base; + } + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) + { + if (String_Schema_Ptr schema = dynamic_cast(&base)) { + // return schema; + if (schema->has_interpolants()) { + if (i + 1 < operands.size() && ( + (ops[0].operand == Sass_OP::EQ) + || (ops[0].operand == Sass_OP::ADD) + || (ops[0].operand == Sass_OP::DIV) + || (ops[0].operand == Sass_OP::MUL) + || (ops[0].operand == Sass_OP::NEQ) + || (ops[0].operand == Sass_OP::LT) + || (ops[0].operand == Sass_OP::GT) + || (ops[0].operand == Sass_OP::LTE) + || (ops[0].operand == Sass_OP::GTE) + )) { + Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, &rhs); + return rhs; + } + // return schema; + } + } + + for (size_t S = operands.size(); i < S; ++i) { + if (String_Schema_Ptr schema = dynamic_cast(&operands[i])) { + if (schema->has_interpolants()) { + if (i + 1 < S) { + Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, &rhs); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, &rhs); + return base; + } + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + return base; + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + } + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + } + Binary_Expression_Ptr b = static_cast(&base); + if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { + base->is_delayed(true); + } + } + // nested binary expression are never to be delayed + if (Binary_Expression_Ptr b = dynamic_cast(&base)) { + if (SASS_MEMORY_CAST(Binary_Expression, b->left())) base->set_delayed(false); + if (SASS_MEMORY_CAST(Binary_Expression, b->right())) base->set_delayed(false); + } + return base; + } + + void Parser::error(std::string msg, Position pos) + { + throw Exception::InvalidSass(ParserState(path, source, pos.line ? pos : before_token, Offset(0, 0)), msg); + } + + // print a css parsing error with actual context information from parsed source + void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle) + { + int max_len = 18; + const char* end = this->end; + while (*end != 0) ++ end; + const char* pos = peek < optional_spaces >(); + + const char* last_pos(pos); + if (last_pos > source) { + utf8::prior(last_pos, source); + } + // backup position to last significant char + while (last_pos > source && last_pos < end) { + if (!Prelexer::is_space(*last_pos)) break; + utf8::prior(last_pos, source); + } + + bool ellipsis_left = false; + const char* pos_left(last_pos); + const char* end_left(last_pos); + + utf8::next(pos_left, end); + utf8::next(end_left, end); + while (pos_left > source) { + if (utf8::distance(pos_left, end_left) >= max_len) { + utf8::prior(pos_left, source); + ellipsis_left = *(pos_left) != '\n' && + *(pos_left) != '\r'; + utf8::next(pos_left, end); + break; + } + + const char* prev = pos_left; + utf8::prior(prev, source); + if (*prev == '\r') break; + if (*prev == '\n') break; + pos_left = prev; + } + if (pos_left < source) { + pos_left = source; + } + + bool ellipsis_right = false; + const char* end_right(pos); + const char* pos_right(pos); + while (end_right < end) { + if (utf8::distance(pos_right, end_right) > max_len) { + ellipsis_left = *(pos_right) != '\n' && + *(pos_right) != '\r'; + break; + } + if (*end_right == '\r') break; + if (*end_right == '\n') break; + utf8::next(end_right, end); + } + // if (*end_right == 0) end_right ++; + + std::string left(pos_left, end_left); + std::string right(pos_right, end_right); + size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; + size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; + if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); + if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; + // now pass new message to the more generic error function + error(msg + prefix + quote(left) + middle + quote(right), pstate); + } + +} diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp new file mode 100755 index 000000000..f4be97826 --- /dev/null +++ b/src/libsass/src/parser.hpp @@ -0,0 +1,365 @@ +#ifndef SASS_PARSER_H +#define SASS_PARSER_H + +#include +#include + +#include "ast.hpp" +#include "position.hpp" +#include "context.hpp" +#include "position.hpp" +#include "prelexer.hpp" + +struct Lookahead { + const char* found; + const char* error; + const char* position; + bool parsable; + bool has_interpolants; +}; + +namespace Sass { + + class Parser : public ParserState { + public: + + enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules }; + + Context& ctx; + std::vector block_stack; + std::vector stack; + Media_Block_Ptr last_media_block; + const char* source; + const char* position; + const char* end; + Position before_token; + Position after_token; + ParserState pstate; + int indentation; + + + Token lexed; + bool in_at_root; + + Parser(Context& ctx, const ParserState& pstate) + : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), + source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0) + { in_at_root = false; stack.push_back(Scope::Root); } + + // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); + static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + // special static parsers to convert strings into certain selectors + static Selector_List_Obj parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + +#ifdef __clang__ + + // lex and peak uses the template parameter to branch on the action, which + // triggers clangs tautological comparison on the single-comparison + // branches. This is not a bug, just a merging of behaviour into + // one function + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-compare" + +#endif + + + // skip current token and next whitespace + // moves ParserState right before next token + void advanceToNextToken(); + + bool peek_newline(const char* start = 0); + + // skip over spaces, tabs and line comments + template + const char* sneak(const char* start = 0) + { + using namespace Prelexer; + + // maybe use optional start position from arguments? + const char* it_position = start ? start : position; + + // skip white-space? + if (mx == spaces || + mx == no_spaces || + mx == css_comments || + mx == css_whitespace || + mx == optional_spaces || + mx == optional_css_comments || + mx == optional_css_whitespace + ) { + return it_position; + } + + // skip over spaces, tabs and sass line comments + const char* pos = optional_css_whitespace(it_position); + // always return a valid position + return pos ? pos : it_position; + + } + + // peek will only skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* match(const char* start = 0) + { + // match the given prelexer + return mx(position); + } + + // peek will only skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* peek(const char* start = 0) + { + + // sneak up to the actual token we want to lex + // this should skip over white-space if desired + const char* it_before_token = sneak < mx >(start); + + // match the given prelexer + const char* match = mx(it_before_token); + + // check if match is in valid range + return match <= end ? match : 0; + + } + + // white-space handling is built into the lexer + // this way you do not need to parse it yourself + // some matchers don't accept certain white-space + // we do not support start arg, since we manipulate + // sourcemap offset and we modify the position pointer! + // lex will only skip over space, tabs and line comment + template + const char* lex(bool lazy = true, bool force = false) + { + + if (*position == 0) return 0; + + // position considered before lexed token + // we can skip whitespace or comments for + // lazy developers (but we need control) + const char* it_before_token = position; + + // sneak up to the actual token we want to lex + // this should skip over white-space if desired + if (lazy) it_before_token = sneak < mx >(position); + + // now call matcher to get position after token + const char* it_after_token = mx(it_before_token); + + // check if match is in valid range + if (it_after_token > end) return 0; + + // maybe we want to update the parser state anyway? + if (force == false) { + // assertion that we got a valid match + if (it_after_token == 0) return 0; + // assertion that we actually lexed something + if (it_after_token == it_before_token) return 0; + } + + // create new lexed token object (holds the parse results) + lexed = Token(position, it_before_token, it_after_token); + + // advance position (add whitespace before current token) + before_token = after_token.add(position, it_before_token); + + // update after_token position for current token + after_token.add(it_before_token, it_after_token); + + // ToDo: could probably do this incremetal on original object (API wants offset?) + pstate = ParserState(path, source, lexed, before_token, after_token - before_token); + + // advance internal char iterator + return position = it_after_token; + + } + + // lex_css skips over space, tabs, line and block comment + // all block comments will be consumed and thrown away + // source-map position will point to token after the comment + template + const char* lex_css() + { + // copy old token + Token prev = lexed; + // store previous pointer + const char* oldpos = position; + Position bt = before_token; + Position at = after_token; + ParserState op = pstate; + // throw away comments + // update srcmap position + lex < Prelexer::css_comments >(); + // now lex a new token + const char* pos = lex< mx >(); + // maybe restore prev state + if (pos == 0) { + pstate = op; + lexed = prev; + position = oldpos; + after_token = at; + before_token = bt; + } + // return match + return pos; + } + + // all block comments will be skipped and thrown away + template + const char* peek_css(const char* start = 0) + { + // now peek a token (skip comments first) + return peek< mx >(peek < Prelexer::css_comments >(start)); + } + +#ifdef __clang__ + +#pragma clang diagnostic pop + +#endif + + void error(std::string msg, Position pos); + // generate message with given and expected sample + // text before and in the middle are configurable + void css_error(const std::string& msg, + const std::string& prefix = " after ", + const std::string& middle = ", was: "); + void read_bom(); + + Block_Obj parse(); + Import_Obj parse_import(); + Definition_Obj parse_definition(Definition::Type which_type); + Parameters_Obj parse_parameters(); + Parameter_Obj parse_parameter(); + Mixin_Call_Obj parse_include_directive(); + Arguments_Obj parse_arguments(); + Argument_Obj parse_argument(); + Assignment_Obj parse_assignment(); + Ruleset_Obj parse_ruleset(Lookahead lookahead, bool is_root = false); + Selector_Schema_Obj parse_selector_schema(const char* end_of_selector); + Selector_List_Obj parse_selector_list(bool at_root = false); + Complex_Selector_Obj parse_complex_selector(bool in_root = true); + Compound_Selector_Obj parse_compound_selector(); + Simple_Selector_Obj parse_simple_selector(); + Wrapped_Selector_Obj parse_negated_selector(); + Simple_Selector_Obj parse_pseudo_selector(); + Attribute_Selector_Obj parse_attribute_selector(); + Block_Obj parse_block(bool is_root = false); + Block_Obj parse_css_block(bool is_root = false); + bool parse_block_nodes(bool is_root = false); + bool parse_block_node(bool is_root = false); + + bool parse_number_prefix(); + Declaration_Obj parse_declaration(); + Expression_Obj parse_map(); + Expression_Obj parse_list(bool delayed = false); + Expression_Obj parse_comma_list(bool delayed = false); + Expression_Obj parse_space_list(); + Expression_Obj parse_disjunction(); + Expression_Obj parse_conjunction(); + Expression_Obj parse_relation(); + Expression_Obj parse_expression(); + Expression_Obj parse_operators(); + Expression_Obj parse_factor(); + Expression_Obj parse_value(); + Function_Call_Obj parse_calc_function(); + Function_Call_Obj parse_function_call(); + Function_Call_Schema_Obj parse_function_call_schema(); + String_Obj parse_url_function_string(); + String_Obj parse_url_function_argument(); + String_Obj parse_interpolated_chunk(Token, bool constant = false); + String_Obj parse_string(); + String_Constant_Obj parse_static_value(); + String_Obj parse_ie_property(); + String_Obj parse_ie_keyword_arg(); + String_Schema_Obj parse_value_schema(const char* stop); + String_Obj parse_identifier_schema(); + If_Obj parse_if_directive(bool else_if = false); + For_Obj parse_for_directive(); + Each_Obj parse_each_directive(); + While_Obj parse_while_directive(); + Return_Obj parse_return_directive(); + Content_Obj parse_content_directive(); + void parse_charset_directive(); + Media_Block_Obj parse_media_block(); + List_Obj parse_media_queries(); + Media_Query_Obj parse_media_query(); + Media_Query_Expression_Obj parse_media_expression(); + Supports_Block_Obj parse_supports_directive(); + Supports_Condition_Obj parse_supports_condition(); + Supports_Condition_Obj parse_supports_negation(); + Supports_Condition_Obj parse_supports_operator(); + Supports_Condition_Obj parse_supports_interpolation(); + Supports_Condition_Obj parse_supports_declaration(); + Supports_Condition_Obj parse_supports_condition_in_parens(); + At_Root_Block_Obj parse_at_root_block(); + At_Root_Query_Obj parse_at_root_query(); + String_Schema_Obj parse_almost_any_value(); + Directive_Obj parse_special_directive(); + Directive_Obj parse_prefixed_directive(); + Directive_Obj parse_directive(); + Warning_Obj parse_warning(); + Error_Obj parse_error(); + Debug_Obj parse_debug(); + + // be more like ruby sass + Expression_Obj lex_almost_any_value_token(); + Expression_Obj lex_almost_any_value_chars(); + Expression_Obj lex_interp_string(); + Expression_Obj lex_interp_uri(); + Expression_Obj lex_interpolation(); + + // these will throw errors + Token lex_variable(); + Token lex_identifier(); + + void parse_block_comments(); + + Lookahead lookahead_for_value(const char* start = 0); + Lookahead lookahead_for_selector(const char* start = 0); + Lookahead lookahead_for_include(const char* start = 0); + + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); + + void throw_syntax_error(std::string message, size_t ln = 0); + void throw_read_error(std::string message, size_t ln = 0); + + + template + Expression_Obj lex_interp() + { + if (lex < open >(false)) { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (&itpl) schema->append(&itpl); + while (lex < close >(false)) { + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (&itpl) schema->append(&itpl); + } else { + return &schema; + } + } + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + return 0; + } + }; + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); +} + +#endif diff --git a/src/libsass/src/paths.hpp b/src/libsass/src/paths.hpp new file mode 100755 index 000000000..aabab94ae --- /dev/null +++ b/src/libsass/src/paths.hpp @@ -0,0 +1,71 @@ +#ifndef SASS_PATHS_H +#define SASS_PATHS_H + +#include +#include +#include + + +template +std::string vector_to_string(std::vector v) +{ + std::stringstream buffer; + buffer << "["; + + if (!v.empty()) + { buffer << v[0]; } + else + { buffer << "]"; } + + if (v.size() == 1) + { buffer << "]"; } + else + { + for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; + buffer << "]"; + } + + return buffer.str(); +} + +namespace Sass { + + + template + std::vector > paths(std::vector > strata, size_t from_end = 0) + { + if (strata.empty()) { + return std::vector >(); + } + + size_t end = strata.size() - from_end; + if (end <= 1) { + std::vector > starting_points; + starting_points.reserve(strata[0].size()); + for (size_t i = 0, S = strata[0].size(); i < S; ++i) { + std::vector starting_point; + starting_point.push_back(strata[0][i]); + starting_points.push_back(starting_point); + } + return starting_points; + } + + std::vector > up_to_here = paths(strata, from_end + 1); + std::vector here = strata[end-1]; + + std::vector > branches; + branches.reserve(up_to_here.size() * here.size()); + for (size_t i = 0, S1 = up_to_here.size(); i < S1; ++i) { + for (size_t j = 0, S2 = here.size(); j < S2; ++j) { + std::vector branch = up_to_here[i]; + branch.push_back(here[j]); + branches.push_back(branch); + } + } + + return branches; + } + +} + +#endif diff --git a/src/libsass/src/plugins.cpp b/src/libsass/src/plugins.cpp new file mode 100755 index 000000000..bdd61a905 --- /dev/null +++ b/src/libsass/src/plugins.cpp @@ -0,0 +1,166 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +#include "sass.hpp" +#include +#include "output.hpp" +#include "plugins.hpp" + +namespace Sass { + + Plugins::Plugins(void) { } + Plugins::~Plugins(void) { } + + // check if plugin is compatible with this version + // plugins may be linked static against libsass + // we try to be compatible between major versions + inline bool compatibility(const char* their_version) + { +// const char* their_version = "3.1.2"; + // first check if anyone has an unknown version + const char* our_version = libsass_version(); + if (!strcmp(their_version, "[na]")) return false; + if (!strcmp(our_version, "[na]")) return false; + + // find the position of the second dot + size_t pos = std::string(our_version).find('.', 0); + if (pos != std::string::npos) pos = std::string(our_version).find('.', pos + 1); + + // if we do not have two dots we fallback to compare complete string + if (pos == std::string::npos) { return strcmp(their_version, our_version) ? 0 : 1; } + // otherwise only compare up to the second dot (major versions) + else { return strncmp(their_version, our_version, pos) ? 0 : 1; } + + } + + // load one specific plugin + bool Plugins::load_plugin (const std::string& path) + { + + typedef const char* (*__plugin_version__)(void); + typedef Sass_Function_List (*__plugin_load_fns__)(void); + typedef Sass_Importer_List (*__plugin_load_imps__)(void); + + if (LOAD_LIB(plugin, path)) + { + // try to load initial function to query libsass version suppor + if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version")) + { + // get the libsass version of the plugin + if (!compatibility(plugin_version())) return false; + // try to get import address for "libsass_load_functions" + if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) + { + Sass_Function_List fns = plugin_load_functions(); + while (fns && *fns) { functions.push_back(*fns); ++ fns; } + } + // try to get import address for "libsass_load_importers" + if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers")) + { + Sass_Importer_List imps = plugin_load_importers(); + while (imps && *imps) { importers.push_back(*imps); ++ imps; } + } + // try to get import address for "libsass_load_headers" + if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers")) + { + Sass_Importer_List imps = plugin_load_headers(); + while (imps && *imps) { headers.push_back(*imps); ++ imps; } + } + // success + return true; + } + else + { + // print debug message to stderr (should not happen) + std::cerr << "failed loading 'libsass_support' in <" << path << ">" << std::endl; + if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << std::endl; + CLOSE_LIB(plugin); + } + } + else + { + // print debug message to stderr (should not happen) + std::cerr << "failed loading plugin <" << path << ">" << std::endl; + if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << std::endl; + } + + return false; + + } + + size_t Plugins::load_plugins(const std::string& path) + { + + // count plugins + size_t loaded = 0; + + #ifdef _WIN32 + + try + { + + // use wchar (utf16) + WIN32_FIND_DATAW data; + // trailing slash is guaranteed + std::string globsrch(path + "*.dll"); + // convert to wide chars (utf16) for system call + std::wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); + HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); + // check if system called returned a result + // ToDo: maybe we should print a debug message + if (hFile == INVALID_HANDLE_VALUE) return -1; + + // read directory + while (true) + { + try + { + // the system will report the filenames with wide chars (utf16) + std::string entry = UTF_8::convert_from_utf16(data.cFileName); + // check if file ending matches exactly + if (!ends_with(entry, ".dll")) continue; + // load the plugin and increase counter + if (load_plugin(path + entry)) ++ loaded; + // check if there should be more entries + if (GetLastError() == ERROR_NO_MORE_FILES) break; + // load next entry (check for return type) + if (!FindNextFileW(hFile, &data)) break; + } + catch (...) + { + // report the error to the console (should not happen) + // seems like we got strange data from the system call? + std::cerr << "filename in plugin path has invalid utf8?" << std::endl; + } + } + } + catch (utf8::invalid_utf8) + { + // report the error to the console (should not happen) + // implementors should make sure to provide valid utf8 + std::cerr << "plugin path contains invalid utf8" << std::endl; + } + + #else + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(path.c_str())) == NULL) return -1; + while ((dirp = readdir(dp)) != NULL) { + if (!ends_with(dirp->d_name, ".so")) continue; + if (load_plugin(path + dirp->d_name)) ++ loaded; + } + closedir(dp); + + #endif + return loaded; + + } + +} diff --git a/src/libsass/src/plugins.hpp b/src/libsass/src/plugins.hpp new file mode 100755 index 000000000..fe4eed010 --- /dev/null +++ b/src/libsass/src/plugins.hpp @@ -0,0 +1,57 @@ +#ifndef SASS_PLUGINS_H +#define SASS_PLUGINS_H + +#include +#include +#include "utf8_string.hpp" +#include "sass/functions.h" + +#ifdef _WIN32 + + #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str()) + #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str()) + #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name) + #define CLOSE_LIB(var) FreeLibrary(var) + + #ifndef dlerror + #define dlerror() 0 + #endif + +#else + + #define LOAD_LIB(var, path) void* var = dlopen(path.c_str(), RTLD_LAZY) + #define LOAD_LIB_FN(type, var, name) type var = (type) dlsym(plugin, name) + #define CLOSE_LIB(var) dlclose(var) + +#endif + +namespace Sass { + + + class Plugins { + + public: // c-tor + Plugins(void); + ~Plugins(void); + + public: // methods + // load one specific plugin + bool load_plugin(const std::string& path); + // load all plugins from a directory + size_t load_plugins(const std::string& path); + + public: // public accessors + const std::vector get_headers(void) { return headers; } + const std::vector get_importers(void) { return importers; } + const std::vector get_functions(void) { return functions; } + + private: // private vars + std::vector headers; + std::vector importers; + std::vector functions; + + }; + +} + +#endif diff --git a/src/libsass/src/position.cpp b/src/libsass/src/position.cpp new file mode 100755 index 000000000..a8afe67d4 --- /dev/null +++ b/src/libsass/src/position.cpp @@ -0,0 +1,163 @@ +#include "sass.hpp" +#include "position.hpp" + +namespace Sass { + + + Offset::Offset(const char* string) + : line(0), column(0) + { + *this = inc(string, string + strlen(string)); + } + + Offset::Offset(const std::string& text) + : line(0), column(0) + { + *this = inc(text.c_str(), text.c_str() + text.size()); + } + + Offset::Offset(const size_t line, const size_t column) + : line(line), column(column) { } + + // init/create instance from const char substring + Offset Offset::init(const char* beg, const char* end) + { + Offset offset(0, 0); + if (end == 0) { + end += strlen(beg); + } + offset.add(beg, end); + return offset; + } + + // increase offset by given string (mostly called by lexer) + // increase line counter and count columns on the last line + // ToDo: make the col count utf8 aware + Offset Offset::add(const char* begin, const char* end) + { + if (end == 0) return *this; + while (begin < end && *begin) { + if (*begin == '\n') { + ++ line; + // start new line + column = 0; + } else { + ++ column; + } + ++begin; + } + return *this; + } + + // increase offset by given string (mostly called by lexer) + // increase line counter and count columns on the last line + Offset Offset::inc(const char* begin, const char* end) const + { + Offset offset(line, column); + offset.add(begin, end); + return offset; + } + + bool Offset::operator== (const Offset &pos) const + { + return line == pos.line && column == pos.column; + } + + bool Offset::operator!= (const Offset &pos) const + { + return line != pos.line || column != pos.column; + } + + void Offset::operator+= (const Offset &off) + { + *this = Offset(line + off.line, off.line > 0 ? off.column : column + off.column); + } + + Offset Offset::operator+ (const Offset &off) const + { + return Offset(line + off.line, off.line > 0 ? off.column : column + off.column); + } + + Offset Offset::operator- (const Offset &off) const + { + return Offset(line - off.line, off.line == line ? column - off.column : column); + } + + Position::Position(const size_t file) + : Offset(0, 0), file(file) { } + + Position::Position(const size_t file, const Offset& offset) + : Offset(offset), file(file) { } + + Position::Position(const size_t line, const size_t column) + : Offset(line, column), file(-1) { } + + Position::Position(const size_t file, const size_t line, const size_t column) + : Offset(line, column), file(file) { } + + + ParserState::ParserState(const char* path, const char* src, const size_t file) + : Position(file, 0, 0), path(path), src(src), offset(0, 0), token() { } + + ParserState::ParserState(const char* path, const char* src, const Position& position, Offset offset) + : Position(position), path(path), src(src), offset(offset), token() { } + + ParserState::ParserState(const char* path, const char* src, const Token& token, const Position& position, Offset offset) + : Position(position), path(path), src(src), offset(offset), token(token) { } + + Position Position::add(const char* begin, const char* end) + { + Offset::add(begin, end); + return *this; + } + + Position Position::inc(const char* begin, const char* end) const + { + Offset offset(line, column); + offset = offset.inc(begin, end); + return Position(file, offset); + } + + bool Position::operator== (const Position &pos) const + { + return file == pos.file && line == pos.line && column == pos.column; + } + + bool Position::operator!= (const Position &pos) const + { + return file == pos.file || line != pos.line || column != pos.column; + } + + void Position::operator+= (const Offset &off) + { + *this = Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); + } + + const Position Position::operator+ (const Offset &off) const + { + return Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); + } + + const Offset Position::operator- (const Offset &off) const + { + return Offset(line - off.line, off.line == line ? column - off.column : column); + } + + /* not used anymore - remove? + std::ostream& operator<<(std::ostream& strm, const Offset& off) + { + if (off.line == string::npos) strm << "-1:"; else strm << off.line << ":"; + if (off.column == string::npos) strm << "-1"; else strm << off.column; + return strm; + } */ + + /* not used anymore - remove? + std::ostream& operator<<(std::ostream& strm, const Position& pos) + { + if (pos.file != string::npos) strm << pos.file << ":"; + if (pos.line == string::npos) strm << "-1:"; else strm << pos.line << ":"; + if (pos.column == string::npos) strm << "-1"; else strm << pos.column; + return strm; + } */ + +} diff --git a/src/libsass/src/position.hpp b/src/libsass/src/position.hpp new file mode 100755 index 000000000..e4306d5a8 --- /dev/null +++ b/src/libsass/src/position.hpp @@ -0,0 +1,123 @@ +#ifndef SASS_POSITION_H +#define SASS_POSITION_H + +#include +#include +// #include + +namespace Sass { + + + class Offset { + + public: // c-tor + Offset(const char* string); + Offset(const std::string& text); + Offset(const size_t line, const size_t column); + + // return new position, incremented by the given string + Offset add(const char* begin, const char* end); + Offset inc(const char* begin, const char* end) const; + + // init/create instance from const char substring + static Offset init(const char* beg, const char* end); + + public: // overload operators for position + void operator+= (const Offset &pos); + bool operator== (const Offset &pos) const; + bool operator!= (const Offset &pos) const; + Offset operator+ (const Offset &off) const; + Offset operator- (const Offset &off) const; + + public: // overload output stream operator + // friend std::ostream& operator<<(std::ostream& strm, const Offset& off); + + public: + Offset off() { return *this; } + + public: + size_t line; + size_t column; + + }; + + class Position : public Offset { + + public: // c-tor + Position(const size_t file); // line(0), column(0) + Position(const size_t file, const Offset& offset); + Position(const size_t line, const size_t column); // file(-1) + Position(const size_t file, const size_t line, const size_t column); + + public: // overload operators for position + void operator+= (const Offset &off); + bool operator== (const Position &pos) const; + bool operator!= (const Position &pos) const; + const Position operator+ (const Offset &off) const; + const Offset operator- (const Offset &off) const; + // return new position, incremented by the given string + Position add(const char* begin, const char* end); + Position inc(const char* begin, const char* end) const; + + public: // overload output stream operator + // friend std::ostream& operator<<(std::ostream& strm, const Position& pos); + + public: + size_t file; + + }; + + // Token type for representing lexed chunks of text + class Token { + public: + const char* prefix; + const char* begin; + const char* end; + + Token() + : prefix(0), begin(0), end(0) { } + Token(const char* b, const char* e) + : prefix(b), begin(b), end(e) { } + Token(const char* str) + : prefix(str), begin(str), end(str + strlen(str)) { } + Token(const char* p, const char* b, const char* e) + : prefix(p), begin(b), end(e) { } + + size_t length() const { return end - begin; } + std::string ws_before() const { return std::string(prefix, begin); } + std::string to_string() const { return std::string(begin, end); } + std::string time_wspace() const { + std::string str(to_string()); + std::string whitespaces(" \t\f\v\n\r"); + return str.erase(str.find_last_not_of(whitespaces)+1); + } + + operator bool() { return begin && end && begin >= end; } + operator std::string() { return to_string(); } + + bool operator==(Token t) { return to_string() == t.to_string(); } + }; + + class ParserState : public Position { + + public: // c-tor + ParserState(const char* path, const char* src = 0, const size_t file = std::string::npos); + ParserState(const char* path, const char* src, const Position& position, Offset offset = Offset(0, 0)); + ParserState(const char* path, const char* src, const Token& token, const Position& position, Offset offset = Offset(0, 0)); + + public: // down casts + Offset off() { return *this; } + Position pos() { return *this; } + ParserState pstate() { return *this; } + + public: + const char* path; + const char* src; + Offset offset; + Token token; + + }; + +} + +#endif diff --git a/src/libsass/src/prelexer.cpp b/src/libsass/src/prelexer.cpp new file mode 100755 index 000000000..cab812bba --- /dev/null +++ b/src/libsass/src/prelexer.cpp @@ -0,0 +1,1689 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include "util.hpp" +#include "position.hpp" +#include "prelexer.hpp" +#include "constants.hpp" + + +namespace Sass { + // using namespace Lexer; + using namespace Constants; + + namespace Prelexer { + + + /* + + def string_re(open, close) + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + end + end + + # A hash of regular expressions that are used for tokenizing strings. + # + # The key is a `[Symbol, Boolean]` pair. + # The symbol represents which style of quotation to use, + # while the boolean represents whether or not the string + # is following an interpolated segment. + STRING_REGULAR_EXPRESSIONS = { + :double => { + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + false => string_re('"', '"'), + true => string_re('', '"') + }, + :single => { + false => string_re("'", "'"), + true => string_re('', "'") + }, + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a + # non-standard version of http://www.w3.org/TR/css3-conditional/ + :url_prefix => { + false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + :domain => { + false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + } + } + */ + + /* + /#{open} + ( + \\. + | + \# (?!\{) + | + [^#{close}\\#] + )* + (#{close}|#\{) + /m + false => string_re('"', '"'), + true => string_re('', '"') + */ + extern const char string_double_negates[] = "\"\\#"; + const char* re_string_double_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_double_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'"'>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + extern const char string_single_negates[] = "'\\#"; + const char* re_string_single_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_single_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'\''>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + /* + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + */ + const char* re_string_uri_close(const char* src) + { + return sequence < + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < optional < W >, exactly <')'> >, + lookahead < exactly< hash_lbrace > > + > + >, + optional < + sequence < optional < W >, exactly <')'> > + > + >(src); + } + + const char* re_string_uri_open(const char* src) + { + return sequence < + exactly <'u'>, + exactly <'r'>, + exactly <'l'>, + exactly <'('>, + W, + alternatives< + quoted_string, + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < W, exactly <')'> >, + exactly< hash_lbrace > + > + > + > + >(src); + } + + // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. + const char* line_comment(const char* src) + { + return sequence< + exactly < + slash_slash + >, + non_greedy< + any_char, + end_of_line + > + >(src); + } + + // Match a block comment. + const char* block_comment(const char* src) + { + return sequence< + delimited_by< + slash_star, + star_slash, + false + > + >(src); + } + /* not use anymore - remove? + const char* block_comment_prefix(const char* src) { + return exactly(src); + } + // Match either comment. + const char* comment(const char* src) { + return line_comment(src); + } + */ + + // Match zero plus white-space or line_comments + const char* optional_css_whitespace(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_whitespace(const char* src) { + return one_plus< alternatives >(src); + } + // Match optional_css_whitepace plus block_comments + const char* optional_css_comments(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_comments(const char* src) { + return one_plus< alternatives >(src); + } + + // Match one backslash escaped char /\\./ + const char* escape_seq(const char* src) + { + return sequence< + exactly<'\\'>, + alternatives < + minmax_range< + 1, 3, xdigit + >, + any_char + >, + optional < + exactly <' '> + > + >(src); + } + + // Match identifier start + const char* identifier_alpha(const char* src) + { + return alternatives< + unicode_seq, + alpha, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match identifier after start + const char* identifier_alnum(const char* src) + { + return alternatives< + unicode_seq, + alnum, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match CSS identifiers. + const char* strict_identifier(const char* src) + { + return sequence< + one_plus < strict_identifier_alpha >, + zero_plus < strict_identifier_alnum > + // word_boundary not needed + >(src); + } + + // Match CSS identifiers. + const char* identifier(const char* src) + { + return sequence< + zero_plus< exactly<'-'> >, + one_plus < identifier_alpha >, + zero_plus < identifier_alnum > + // word_boundary not needed + >(src); + } + + const char* strict_identifier_alpha(const char* src) + { + return alternatives < + alpha, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + const char* strict_identifier_alnum(const char* src) + { + return alternatives < + alnum, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + // Match a single CSS unit + const char* one_unit(const char* src) + { + return sequence < + optional < exactly <'-'> >, + strict_identifier_alpha, + zero_plus < alternatives< + strict_identifier_alnum, + sequence < + one_plus < exactly<'-'> >, + strict_identifier_alpha + > + > > + >(src); + } + + // Match numerator/denominator CSS units + const char* multiple_units(const char* src) + { + return + sequence < + one_unit, + zero_plus < + sequence < + exactly <'*'>, + one_unit + > + > + >(src); + } + + // Match complex CSS unit identifiers + const char* unit_identifier(const char* src) + { + return sequence < + multiple_units, + optional < + sequence < + exactly <'/'>, + multiple_units + > > + >(src); + } + + const char* identifier_alnums(const char* src) + { + return one_plus< identifier_alnum >(src); + } + + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src) { + return alternatives < + exactly < '+' >, + sequence < + exactly < '-' >, + optional_css_whitespace, + exactly< '-' > + > + >(src); + } + + // Match interpolant schemas + const char* identifier_schema(const char* src) { + + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + sequence < + optional < + exactly <'$'> + >, + identifier + >, + exactly <'-'> + > + >, + interpolant, + zero_plus < + alternatives < + digits, + sequence < + optional < + exactly <'$'> + >, + identifier + >, + quoted_string, + exactly<'-'> + > + > + > + >, + negate < + exactly<'%'> + > + > (src); + } + + // interpolants can be recursive/nested + const char* interpolant(const char* src) { + return recursive_scopes< exactly, exactly >(src); + } + + // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ + const char* single_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'\''>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '\'' > + > + >, + exactly <'\''> + >(src); + } + + // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ + const char* double_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'"'>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '"' > + > + >, + exactly <'"'> + >(src); + } + + // $re_quoted = /(?:$re_squote|$re_dquote)/ + const char* quoted_string(const char* src) { + // match a quoted string, while skipping interpolants + return alternatives< + single_quoted_string, + double_quoted_string + >(src); + } + + const char* sass_value(const char* src) { + return alternatives < + quoted_string, + identifier, + percentage, + hex, + dimension, + number + >(src); + } + + // this is basically `one_plus < sass_value >` + // takes care to not parse invalid combinations + const char* value_combinations(const char* src) { + // `2px-2px` is invalid combo + bool was_number = false; + const char* pos = src; + while (src) { + if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { + was_number = false; + src = pos; + } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { + was_number = true; + src = pos; + } else { + break; + } + } + return src; + } + + // must be at least one interpolant + // can be surrounded by sass values + // make sure to never parse (dim)(dim) + // since this wrongly consumes `2px-1px` + // `2px1px` is valid number (unit `px1px`) + const char* value_schema(const char* src) + { + return sequence < + one_plus < + sequence < + optional < value_combinations >, + interpolant, + optional < value_combinations > + > + > + >(src); + } + + // Match CSS '@' keywords. + const char* at_keyword(const char* src) { + return sequence, identifier>(src); + } + + /* + tok(%r{ + ( + \\. + | + (?!url\() + [^"'/\#!;\{\}] # " + | + /(?![\*\/]) + | + \#(?!\{) + | + !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. + )+ + }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || + interpolation(:warn_for_color) + */ + const char* re_almost_any_value_token(const char* src) { + + return alternatives < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + sequence < + exactly < url_kwd >, + exactly <'('> + > + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + >, + block_comment, + line_comment, + interpolant, + space, + sequence < + exactly<'u'>, + exactly<'r'>, + exactly<'l'>, + exactly<'('>, + zero_plus < + alternatives < + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + > + >, + // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + exactly<')'> + > + >(src); + } + + /* + DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, + :each, :while, :if, :else, :extend, :import, :media, :charset, :content, + :_moz_document, :at_root, :error] + */ + const char* re_special_directive(const char* src) { + return alternatives < + word < mixin_kwd >, + word < include_kwd >, + word < function_kwd >, + word < return_kwd >, + word < debug_kwd >, + word < warn_kwd >, + word < for_kwd >, + word < each_kwd >, + word < while_kwd >, + word < if_kwd >, + word < else_kwd >, + word < extend_kwd >, + word < import_kwd >, + word < media_kwd >, + word < charset_kwd >, + word < content_kwd >, + // exactly < moz_document_kwd >, + word < at_root_kwd >, + word < error_kwd > + >(src); + } + + const char* re_prefixed_directive(const char* src) { + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < alnum >, + exactly <'-'> + > + >, + exactly < supports_kwd > + >(src); + } + + const char* re_reference_combinator(const char* src) { + return sequence < + optional < + sequence < + zero_plus < + exactly <'-'> + >, + identifier, + exactly <'|'> + > + >, + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* static_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + re_reference_combinator, + exactly <'/'> + >(src); + } + + const char* schema_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + optional < + sequence < + css_ip_identifier, + exactly <'|'> + > + >, + css_ip_identifier, + exactly <'/'> + > (src); + } + + const char* kwd_import(const char* src) { + return word(src); + } + + const char* kwd_at_root(const char* src) { + return word(src); + } + + const char* kwd_with_directive(const char* src) { + return word(src); + } + + const char* kwd_without_directive(const char* src) { + return word(src); + } + + const char* kwd_media(const char* src) { + return word(src); + } + + const char* kwd_supports_directive(const char* src) { + return word(src); + } + + const char* kwd_mixin(const char* src) { + return word(src); + } + + const char* kwd_function(const char* src) { + return word(src); + } + + const char* kwd_return_directive(const char* src) { + return word(src); + } + + const char* kwd_include_directive(const char* src) { + return word(src); + } + + const char* kwd_content_directive(const char* src) { + return word(src); + } + + const char* kwd_charset_directive(const char* src) { + return word(src); + } + + const char* kwd_extend(const char* src) { + return word(src); + } + + + const char* kwd_if_directive(const char* src) { + return word(src); + } + + const char* kwd_else_directive(const char* src) { + return word(src); + } + const char* elseif_directive(const char* src) { + return sequence< exactly< else_kwd >, + optional_css_comments, + word< if_after_else_kwd > >(src); + } + + const char* kwd_for_directive(const char* src) { + return word(src); + } + + const char* kwd_from(const char* src) { + return word(src); + } + + const char* kwd_to(const char* src) { + return word(src); + } + + const char* kwd_through(const char* src) { + return word(src); + } + + const char* kwd_each_directive(const char* src) { + return word(src); + } + + const char* kwd_in(const char* src) { + return word(src); + } + + const char* kwd_while_directive(const char* src) { + return word(src); + } + + const char* name(const char* src) { + return one_plus< alternatives< alnum, + exactly<'-'>, + exactly<'_'>, + escape_seq > >(src); + } + + const char* kwd_warn(const char* src) { + return word(src); + } + + const char* kwd_err(const char* src) { + return word(src); + } + + const char* kwd_dbg(const char* src) { + return word(src); + } + + /* not used anymore - remove? + const char* directive(const char* src) { + return sequence< exactly<'@'>, identifier >(src); + } */ + + const char* kwd_null(const char* src) { + return word(src); + } + + const char* css_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* css_ip_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + alternatives < + identifier, + interpolant + > + >(src); + } + + // Match CSS type selectors + const char* namespace_prefix(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_identifier + > + >, + exactly <'|'>, + negate < + exactly <'='> + > + >(src); + } + + // Match CSS type selectors + const char* namespace_schema(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_ip_identifier + > + >, + exactly<'|'>, + negate < + exactly <'='> + > + >(src); + } + + const char* hyphens_and_identifier(const char* src) { + return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); + } + const char* hyphens_and_name(const char* src) { + return sequence< zero_plus< exactly< '-' > >, name >(src); + } + const char* universal(const char* src) { + return sequence< optional, exactly<'*'> >(src); + } + // Match CSS id names. + const char* id_name(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS class names. + const char* class_name(const char* src) { + return sequence, identifier >(src); + } + // Attribute name in an attribute selector. + const char* attribute_name(const char* src) { + return alternatives< sequence< optional, identifier>, + identifier >(src); + } + // match placeholder selectors + const char* placeholder(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS numeric constants. + + const char* op(const char* src) { + return class_char(src); + } + const char* sign(const char* src) { + return class_char(src); + } + const char* unsigned_number(const char* src) { + return alternatives, + exactly<'.'>, + one_plus >, + digits>(src); + } + const char* number(const char* src) { + return sequence< optional, unsigned_number>(src); + } + const char* coefficient(const char* src) { + return alternatives< sequence< optional, digits >, + sign >(src); + } + const char* binomial(const char* src) { + return sequence < + optional < sign >, + optional < digits >, + exactly <'n'>, + zero_plus < sequence < + optional_css_whitespace, sign, + optional_css_whitespace, digits + > > + >(src); + } + const char* percentage(const char* src) { + return sequence< number, exactly<'%'> >(src); + } + const char* ampersand(const char* src) { + return exactly<'&'>(src); + } + + /* not used anymore - remove? + const char* em(const char* src) { + return sequence< number, exactly >(src); + } */ + const char* dimension(const char* src) { + return sequence(src); + } + const char* hex(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 4 && len != 7) ? 0 : p; + } + const char* hexa(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 4 && len != 7 && len != 9) ? 0 : p; + } + const char* hex0(const char* src) { + const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 8) ? 0 : p; + } + + /* no longer used - remove? + const char* rgb_prefix(const char* src) { + return word(src); + }*/ + // Match CSS uri specifiers. + + const char* uri_prefix(const char* src) { + return sequence < + exactly < + url_kwd + >, + zero_plus < + sequence < + exactly <'-'>, + one_plus < + alpha + > + > + >, + exactly <'('> + >(src); + } + + // TODO: rename the following two functions + /* no longer used - remove? + const char* uri(const char* src) { + return sequence< exactly, + optional, + quoted_string, + optional, + exactly<')'> >(src); + }*/ + /* no longer used - remove? + const char* url_value(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename + optional< exactly<'/'> > >(src); + }*/ + /* no longer used - remove? + const char* url_schema(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + filename_schema >(src); // optional trailing slash + }*/ + // Match CSS "!important" keyword. + const char* kwd_important(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS "!optional" keyword. + const char* kwd_optional(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!default" keyword. + const char* default_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!global" keyword. + const char* global_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS pseudo-class/element prefixes. + const char* pseudo_prefix(const char* src) { + return sequence< exactly<':'>, optional< exactly<':'> > >(src); + } + // Match CSS function call openers. + const char* functional_schema(const char* src) { + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + identifier, + exactly <'-'> + > + >, + one_plus < + sequence < + interpolant, + alternatives < + digits, + identifier, + exactly<'+'>, + exactly<'-'> + > + > + > + > + >, + negate < + exactly <'%'> + >, + lookahead < + exactly <'('> + > + > (src); + } + + const char* re_nothing(const char* src) { + return src; + } + + const char* re_functional(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + const char* re_pseudo_selector(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + // Match the CSS negation pseudo-class. + const char* pseudo_not(const char* src) { + return word< pseudo_not_kwd >(src); + } + // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. + const char* even(const char* src) { + return word(src); + } + const char* odd(const char* src) { + return word(src); + } + // Match CSS attribute-matching operators. + const char* exact_match(const char* src) { return exactly<'='>(src); } + const char* class_match(const char* src) { return exactly(src); } + const char* dash_match(const char* src) { return exactly(src); } + const char* prefix_match(const char* src) { return exactly(src); } + const char* suffix_match(const char* src) { return exactly(src); } + const char* substring_match(const char* src) { return exactly(src); } + // Match CSS combinators. + /* not used anymore - remove? + const char* adjacent_to(const char* src) { + return sequence< optional_spaces, exactly<'+'> >(src); + } + const char* precedes(const char* src) { + return sequence< optional_spaces, exactly<'~'> >(src); + } + const char* parent_of(const char* src) { + return sequence< optional_spaces, exactly<'>'> >(src); + } + const char* ancestor_of(const char* src) { + return sequence< spaces, negate< exactly<'{'> > >(src); + }*/ + + // Match SCSS variable names. + const char* variable(const char* src) { + return sequence, identifier>(src); + } + + // parse `calc`, `-a-calc` and `--b-c-calc` + // but do not parse `foocalc` or `foo-calc` + const char* calc_fn_call(const char* src) { + return sequence < + optional < sequence < + hyphens, + one_plus < sequence < + strict_identifier, + hyphens + > > + > >, + exactly < calc_fn_kwd >, + word_boundary + >(src); + } + + // Match Sass boolean keywords. + const char* kwd_true(const char* src) { + return word(src); + } + const char* kwd_false(const char* src) { + return word(src); + } + const char* kwd_only(const char* src) { + return keyword < only_kwd >(src); + } + const char* kwd_and(const char* src) { + return keyword < and_kwd >(src); + } + const char* kwd_or(const char* src) { + return keyword < or_kwd >(src); + } + const char* kwd_not(const char* src) { + return keyword < not_kwd >(src); + } + const char* kwd_eq(const char* src) { + return exactly(src); + } + const char* kwd_neq(const char* src) { + return exactly(src); + } + const char* kwd_gt(const char* src) { + return exactly(src); + } + const char* kwd_gte(const char* src) { + return exactly(src); + } + const char* kwd_lt(const char* src) { + return exactly(src); + } + const char* kwd_lte(const char* src) { + return exactly(src); + } + + // match specific IE syntax + const char* ie_progid(const char* src) { + return sequence < + word, + exactly<':'>, + alternatives< identifier_schema, identifier >, + zero_plus< sequence< + exactly<'.'>, + alternatives< identifier_schema, identifier > + > >, + zero_plus < sequence< + exactly<'('>, + optional_css_whitespace, + optional < sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >, + zero_plus< sequence< + optional_css_whitespace, + exactly<','>, + optional_css_whitespace, + sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa > + > + > > + > >, + optional_css_whitespace, + exactly<')'> + > > + >(src); + } + const char* ie_expression(const char* src) { + return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); + } + const char* ie_property(const char* src) { + return alternatives < ie_expression, ie_progid >(src); + } + + // const char* ie_args(const char* src) { + // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, + // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); + // } + + const char* ie_keyword_arg_property(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier + >(src); + } + const char* ie_keyword_arg_value(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier, + quoted_string, + number, + hexa, + sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + > + >(src); + } + + const char* ie_keyword_arg(const char* src) { + return sequence < + ie_keyword_arg_property, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + ie_keyword_arg_value + >(src); + } + + // Path matching functions. + /* not used anymore - remove? + const char* folder(const char* src) { + return sequence< zero_plus< any_char_except<'/'> >, + exactly<'/'> >(src); + } + const char* folders(const char* src) { + return zero_plus< folder >(src); + }*/ + /* not used anymore - remove? + const char* chunk(const char* src) { + char inside_str = 0; + const char* p = src; + size_t depth = 0; + while (true) { + if (!*p) { + return 0; + } + else if (!inside_str && (*p == '"' || *p == '\'')) { + inside_str = *p; + } + else if (*p == inside_str && *(p-1) != '\\') { + inside_str = 0; + } + else if (*p == '(' && !inside_str) { + ++depth; + } + else if (*p == ')' && !inside_str) { + if (depth == 0) return p; + else --depth; + } + ++p; + } + // unreachable + return 0; + } + */ + + // follow the CSS spec more closely and see if this helps us scan URLs correctly + /* not used anymore - remove? + const char* NL(const char* src) { + return alternatives< exactly<'\n'>, + sequence< exactly<'\r'>, exactly<'\n'> >, + exactly<'\r'>, + exactly<'\f'> >(src); + }*/ + + const char* H(const char* src) { + return std::isxdigit(*src) ? src+1 : 0; + } + + const char* W(const char* src) { + return zero_plus< alternatives< + space, + exactly< '\t' >, + exactly< '\r' >, + exactly< '\n' >, + exactly< '\f' > + > >(src); + } + + const char* UUNICODE(const char* src) { + return sequence< exactly<'\\'>, + between, + optional< W > + >(src); + } + + const char* NONASCII(const char* src) { + return nonascii(src); + } + + const char* ESCAPE(const char* src) { + return alternatives< + UUNICODE, + sequence< + exactly<'\\'>, + alternatives< + NONASCII, + escapable_character + > + > + >(src); + } + + + // const char* real_uri_prefix(const char* src) { + // return alternatives< + // exactly< url_kwd >, + // exactly< url_prefix_kwd > + // >(src); + // } + + const char* real_uri_suffix(const char* src) { + return sequence< W, exactly< ')' > >(src); + } + + const char* real_uri_value(const char* src) { + return + sequence< + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + real_uri_suffix, + exactly< hash_lbrace > + > + > + > + (src); + } + + const char* static_string(const char* src) { + const char* pos = src; + const char * s = quoted_string(pos); + Token t(pos, s); + const unsigned int p = count_interval< interpolant >(t.begin, t.end); + return (p == 0) ? t.end : 0; + } + + const char* unicode_seq(const char* src) { + return sequence < + alternatives < + exactly< 'U' >, + exactly< 'u' > + >, + exactly< '+' >, + padded_token < + 6, xdigit, + exactly < '?' > + > + >(src); + } + + const char* static_component(const char* src) { + return alternatives< identifier, + static_string, + percentage, + hex, + exactly<'|'>, + // exactly<'+'>, + sequence < number, unit_identifier >, + number, + sequence< exactly<'!'>, word > + >(src); + } + + const char* static_property(const char* src) { + return + sequence < + zero_plus< + sequence < + optional_css_comments, + alternatives < + exactly<','>, + exactly<'('>, + exactly<')'>, + kwd_optional, + quoted_string, + interpolant, + identifier, + percentage, + dimension, + variable, + alnum, + sequence < + exactly <'\\'>, + any_char + > + > + > + >, + lookahead < + sequence < + optional_css_comments, + alternatives < + exactly <';'>, + exactly <'}'>, + end_of_file + > + > + > + >(src); + } + + const char* static_value(const char* src) { + return sequence< sequence< + static_component, + zero_plus< identifier > + >, + zero_plus < sequence< + alternatives< + sequence< optional_spaces, alternatives< + exactly < '/' >, + exactly < ',' >, + exactly < ' ' > + >, optional_spaces >, + spaces + >, + static_component + > >, + zero_plus < spaces >, + alternatives< exactly<';'>, exactly<'}'> > + >(src); + } + + const char* parenthese_scope(const char* src) { + return sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + >(src); + } + + const char* re_selector_list(const char* src) { + return alternatives < + // partial bem selector + sequence < + ampersand, + one_plus < + exactly < '-' > + >, + word_boundary, + optional_spaces + >, + // main selector matching + one_plus < + alternatives < + // consume whitespace and comments + spaces, block_comment, line_comment, + // match `/deep/` selector (pass-trough) + // there is no functionality for it yet + schema_reference_combinator, + // match selector ops /[*&%,\[\]]/ + class_char < selector_lookahead_ops >, + // match selector combinators /[>+~]/ + class_char < selector_combinator_ops >, + // match attribute compare operators + sequence < + exactly <'('>, + optional_spaces, + optional , + optional_spaces, + exactly <')'> + >, + alternatives < + exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match + >, + // main selector match + sequence < + // allow namespace prefix + optional < namespace_schema >, + // modifiers prefixes + alternatives < + sequence < + exactly <'#'>, + // not for interpolation + negate < exactly <'{'> > + >, + // class match + exactly <'.'>, + // single or double colon + optional < pseudo_prefix > + >, + // accept hypens in token + one_plus < sequence < + // can start with hyphens + zero_plus < exactly<'-'> >, + // now the main token + alternatives < + kwd_optional, + exactly <'*'>, + quoted_string, + interpolant, + identifier, + variable, + percentage, + binomial, + dimension, + alnum + > + > >, + // can also end with hyphens + zero_plus < exactly<'-'> > + > + > + > + >(src); + } + + const char* type_selector(const char* src) { + return sequence< optional, identifier>(src); + } + const char* re_type_selector(const char* src) { + return alternatives< type_selector, universal, quoted_string, dimension, percentage, number, identifier_alnums >(src); + } + const char* re_type_selector2(const char* src) { + return alternatives< type_selector, universal, quoted_string, dimension, percentage, number, identifier_alnums >(src); + } + const char* re_static_expression(const char* src) { + return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); + } + + // lexer special_fn: these functions cannot be overloaded + // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) + const char* re_special_fun(const char* src) { + + // match this first as we test prefix hyphens + if (const char* calc = calc_fn_call(src)) { + return calc; + } + + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < + alternatives < + alpha, + exactly <'+'>, + exactly <'-'> + > + > + > + >, + alternatives < + word < expression_kwd >, + sequence < + sequence < + exactly < progid_kwd >, + exactly <':'> + >, + zero_plus < + alternatives < + char_range <'a', 'z'>, + exactly <'.'> + > + > + > + > + >(src); + } + + } +} diff --git a/src/libsass/src/prelexer.hpp b/src/libsass/src/prelexer.hpp new file mode 100755 index 000000000..69c404323 --- /dev/null +++ b/src/libsass/src/prelexer.hpp @@ -0,0 +1,477 @@ +#ifndef SASS_PRELEXER_H +#define SASS_PRELEXER_H + +#include +#include "lexer.hpp" + +namespace Sass { + // using namespace Lexer; + namespace Prelexer { + + //#################################### + // KEYWORD "REGEX" MATCHERS + //#################################### + + // Match Sass boolean keywords. + const char* kwd_true(const char* src); + const char* kwd_false(const char* src); + const char* kwd_only(const char* src); + const char* kwd_and(const char* src); + const char* kwd_or(const char* src); + const char* kwd_not(const char* src); + const char* kwd_eq(const char* src); + const char* kwd_neq(const char* src); + const char* kwd_gt(const char* src); + const char* kwd_gte(const char* src); + const char* kwd_lt(const char* src); + const char* kwd_lte(const char* src); + + // Match standard control chars + const char* kwd_at(const char* src); + const char* kwd_dot(const char* src); + const char* kwd_comma(const char* src); + const char* kwd_colon(const char* src); + const char* kwd_slash(const char* src); + const char* kwd_star(const char* src); + const char* kwd_plus(const char* src); + const char* kwd_minus(const char* src); + + //#################################### + // SPECIAL "REGEX" CONSTRUCTS + //#################################### + + // Match a sequence of characters delimited by the supplied chars. + template + const char* delimited_by(const char* src) { + src = exactly(src); + if (!src) return 0; + const char* stop; + while (1) { + if (!*src) return 0; + stop = exactly(src); + if (stop && (!esc || *(src - 1) != '\\')) return stop; + src = stop ? stop : src + 1; + } + } + + // skip to delimiter (mx) inside given range + // this will savely skip over all quoted strings + // recursive skip stuff delimited by start/stop + // first start/opener must be consumed already! + template + const char* skip_over_scopes(const char* src, const char* end) { + + size_t level = 0; + bool in_squote = false; + bool in_dquote = false; + // bool in_braces = false; + + while (*src) { + + // check for abort condition + if (end && src >= end) break; + + // has escaped sequence? + if (*src == '\\') { + ++ src; // skip this (and next) + } + else if (*src == '"') { + in_dquote = ! in_dquote; + } + else if (*src == '\'') { + in_squote = ! in_squote; + } + else if (in_dquote || in_squote) { + // take everything literally + } + + // find another opener inside? + else if (const char* pos = start(src)) { + ++ level; // increase counter + src = pos - 1; // advance position + } + + // look for the closer (maybe final, maybe not) + else if (const char* final = stop(src)) { + // only close one level? + if (level > 0) -- level; + // return position at end of stop + // delimiter may be multiple chars + else return final; + // advance position + src = final - 1; + } + + // next + ++ src; + } + + return 0; + } + + // skip to a skip delimited by parentheses + // uses smart `skip_over_scopes` internally + const char* parenthese_scope(const char* src); + + // skip to delimiter (mx) inside given range + // this will savely skip over all quoted strings + // recursive skip stuff delimited by start/stop + // first start/opener must be consumed already! + template + const char* skip_over_scopes(const char* src) { + return skip_over_scopes(src, 0); + } + + // Match a sequence of characters delimited by the supplied chars. + template + const char* recursive_scopes(const char* src) { + // parse opener + src = start(src); + // abort if not found + if (!src) return 0; + // parse the rest until final closer + return skip_over_scopes(src); + } + + // Match a sequence of characters delimited by the supplied strings. + template + const char* delimited_by(const char* src) { + src = exactly(src); + if (!src) return 0; + const char* stop; + while (1) { + if (!*src) return 0; + stop = exactly(src); + if (stop && (!esc || *(src - 1) != '\\')) return stop; + src = stop ? stop : src + 1; + } + } + + // Tries to match a certain number of times (between the supplied interval). + template + const char* between(const char* src) { + for (size_t i = 0; i < lo; ++i) { + src = mx(src); + if (!src) return 0; + } + for (size_t i = lo; i <= hi; ++i) { + const char* new_src = mx(src); + if (!new_src) return src; + src = new_src; + } + return src; + } + + // equivalent of STRING_REGULAR_EXPRESSIONS + const char* re_string_double_open(const char* src); + const char* re_string_double_close(const char* src); + const char* re_string_single_open(const char* src); + const char* re_string_single_close(const char* src); + const char* re_string_uri_open(const char* src); + const char* re_string_uri_close(const char* src); + + // Match a line comment. + const char* line_comment(const char* src); + + // Match a block comment. + const char* block_comment(const char* src); + // Match either. + const char* comment(const char* src); + // Match double- and single-quoted strings. + const char* double_quoted_string(const char* src); + const char* single_quoted_string(const char* src); + const char* quoted_string(const char* src); + // Match interpolants. + const char* interpolant(const char* src); + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src); + + // Match zero plus white-space or line_comments + const char* optional_css_whitespace(const char* src); + const char* css_whitespace(const char* src); + // Match optional_css_whitepace plus block_comments + const char* optional_css_comments(const char* src); + const char* css_comments(const char* src); + + // Match one backslash escaped char + const char* escape_seq(const char* src); + + // Match CSS css variables. + const char* custom_property_name(const char* src); + // Match a CSS identifier. + const char* identifier(const char* src); + const char* identifier_alpha(const char* src); + const char* identifier_alnum(const char* src); + const char* strict_identifier(const char* src); + const char* strict_identifier_alpha(const char* src); + const char* strict_identifier_alnum(const char* src); + // Match a CSS unit identifier. + const char* one_unit(const char* src); + const char* multiple_units(const char* src); + const char* unit_identifier(const char* src); + // const char* strict_identifier_alnums(const char* src); + // Match reference selector. + const char* re_reference_combinator(const char* src); + const char* static_reference_combinator(const char* src); + const char* schema_reference_combinator(const char* src); + + // Match interpolant schemas + const char* identifier_schema(const char* src); + const char* value_schema(const char* src); + const char* sass_value(const char* src); + // const char* filename(const char* src); + // const char* filename_schema(const char* src); + // const char* url_schema(const char* src); + // const char* url_value(const char* src); + const char* vendor_prefix(const char* src); + + const char* re_special_directive(const char* src); + const char* re_prefixed_directive(const char* src); + const char* re_almost_any_value_token(const char* src); + + // Match CSS '@' keywords. + const char* at_keyword(const char* src); + const char* kwd_import(const char* src); + const char* kwd_at_root(const char* src); + const char* kwd_with_directive(const char* src); + const char* kwd_without_directive(const char* src); + const char* kwd_media(const char* src); + const char* kwd_supports_directive(const char* src); + // const char* keyframes(const char* src); + // const char* keyf(const char* src); + const char* kwd_mixin(const char* src); + const char* kwd_function(const char* src); + const char* kwd_return_directive(const char* src); + const char* kwd_include_directive(const char* src); + const char* kwd_content_directive(const char* src); + const char* kwd_charset_directive(const char* src); + const char* kwd_extend(const char* src); + + const char* unicode_seq(const char* src); + + const char* kwd_if_directive(const char* src); + const char* kwd_else_directive(const char* src); + const char* elseif_directive(const char* src); + + const char* kwd_for_directive(const char* src); + const char* kwd_from(const char* src); + const char* kwd_to(const char* src); + const char* kwd_through(const char* src); + + const char* kwd_each_directive(const char* src); + const char* kwd_in(const char* src); + + const char* kwd_while_directive(const char* src); + + const char* re_nothing(const char* src); + const char* re_type_selector2(const char* src); + + const char* re_special_fun(const char* src); + + const char* kwd_warn(const char* src); + const char* kwd_err(const char* src); + const char* kwd_dbg(const char* src); + + const char* kwd_null(const char* src); + + const char* re_selector_list(const char* src); + const char* re_type_selector(const char* src); + const char* re_static_expression(const char* src); + + // identifier that can start with hyphens + const char* css_identifier(const char* src); + const char* css_ip_identifier(const char* src); + + // Match CSS type selectors + const char* namespace_schema(const char* src); + const char* namespace_prefix(const char* src); + const char* type_selector(const char* src); + const char* hyphens_and_identifier(const char* src); + const char* hyphens_and_name(const char* src); + const char* universal(const char* src); + // Match CSS id names. + const char* id_name(const char* src); + // Match CSS class names. + const char* class_name(const char* src); + // Attribute name in an attribute selector + const char* attribute_name(const char* src); + // Match placeholder selectors. + const char* placeholder(const char* src); + // Match CSS numeric constants. + const char* op(const char* src); + const char* sign(const char* src); + const char* unsigned_number(const char* src); + const char* number(const char* src); + const char* coefficient(const char* src); + const char* binomial(const char* src); + const char* percentage(const char* src); + const char* ampersand(const char* src); + const char* dimension(const char* src); + const char* hex(const char* src); + const char* hexa(const char* src); + const char* hex0(const char* src); + // const char* rgb_prefix(const char* src); + // Match CSS uri specifiers. + const char* uri_prefix(const char* src); + // Match CSS "!important" keyword. + const char* kwd_important(const char* src); + // Match CSS "!optional" keyword. + const char* kwd_optional(const char* src); + // Match Sass "!default" keyword. + const char* default_flag(const char* src); + const char* global_flag(const char* src); + // Match CSS pseudo-class/element prefixes + const char* pseudo_prefix(const char* src); + // Match CSS function call openers. + const char* re_functional(const char* src); + const char* re_pseudo_selector(const char* src); + const char* functional_schema(const char* src); + const char* pseudo_not(const char* src); + // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. + const char* even(const char* src); + const char* odd(const char* src); + // Match CSS attribute-matching operators. + const char* exact_match(const char* src); + const char* class_match(const char* src); + const char* dash_match(const char* src); + const char* prefix_match(const char* src); + const char* suffix_match(const char* src); + const char* substring_match(const char* src); + // Match CSS combinators. + // const char* adjacent_to(const char* src); + // const char* precedes(const char* src); + // const char* parent_of(const char* src); + // const char* ancestor_of(const char* src); + + // Match SCSS variable names. + const char* variable(const char* src); + const char* calc_fn_call(const char* src); + + // IE stuff + const char* ie_progid(const char* src); + const char* ie_expression(const char* src); + const char* ie_property(const char* src); + const char* ie_keyword_arg(const char* src); + const char* ie_keyword_arg_value(const char* src); + const char* ie_keyword_arg_property(const char* src); + + // match url() + const char* H(const char* src); + const char* W(const char* src); + // `UNICODE` makes VS sad + const char* UUNICODE(const char* src); + const char* NONASCII(const char* src); + const char* ESCAPE(const char* src); + const char* real_uri_suffix(const char* src); + // const char* real_uri_prefix(const char* src); + const char* real_uri_value(const char* src); + + // Path matching functions. + // const char* folder(const char* src); + // const char* folders(const char* src); + + + const char* static_string(const char* src); + const char* static_component(const char* src); + const char* static_property(const char* src); + const char* static_value(const char* src); + + // Utility functions for finding and counting characters in a string. + template + const char* find_first(const char* src) { + while (*src && *src != c) ++src; + return *src ? src : 0; + } + template + const char* find_first(const char* src) { + while (*src && !mx(src)) ++src; + return *src ? src : 0; + } + template + const char* find_first_in_interval(const char* beg, const char* end) { + bool esc = false; + while ((beg < end) && *beg) { + if (esc) esc = false; + else if (*beg == '\\') esc = true; + else if (mx(beg)) return beg; + ++beg; + } + return 0; + } + template + const char* find_first_in_interval(const char* beg, const char* end) { + bool esc = false; + while ((beg < end) && *beg) { + if (esc) esc = false; + else if (*beg == '\\') esc = true; + else if (const char* pos = skip(beg)) beg = pos; + else if (mx(beg)) return beg; + ++beg; + } + return 0; + } + template + unsigned int count_interval(const char* beg, const char* end) { + unsigned int counter = 0; + bool esc = false; + while (beg < end && *beg) { + const char* p; + if (esc) { + esc = false; + ++beg; + } else if (*beg == '\\') { + esc = true; + ++beg; + } else if ((p = mx(beg))) { + ++counter; + beg = p; + } + else { + ++beg; + } + } + return counter; + } + + template + const char* padded_token(const char* src) + { + size_t got = 0; + const char* pos = src; + while (got < size) { + if (!mx(pos)) break; + ++ pos; ++ got; + } + while (got < size) { + if (!pad(pos)) break; + ++ pos; ++ got; + } + return got ? pos : 0; + } + + template + const char* minmax_range(const char* src) + { + size_t got = 0; + const char* pos = src; + while (got < max) { + if (!mx(pos)) break; + ++ pos; ++ got; + } + if (got < min) return 0; + if (got > max) return 0; + return pos; + } + + template + const char* char_range(const char* src) + { + if (*src < min) return 0; + if (*src > max) return 0; + return src + 1; + } + + } +} + +#endif diff --git a/src/libsass/src/remove_placeholders.cpp b/src/libsass/src/remove_placeholders.cpp new file mode 100755 index 000000000..0fce4ed7e --- /dev/null +++ b/src/libsass/src/remove_placeholders.cpp @@ -0,0 +1,85 @@ +#include "sass.hpp" +#include "remove_placeholders.hpp" +#include "context.hpp" +#include "inspect.hpp" +#include + +namespace Sass { + + Remove_Placeholders::Remove_Placeholders(Context& ctx) + : ctx(ctx) + { } + + void Remove_Placeholders::operator()(Block_Ptr b) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr st = &b->at(i); + st->perform(this); + } + } + + Selector_List_Ptr Remove_Placeholders::remove_placeholders(Selector_List_Ptr sl) + { + Selector_List_Ptr new_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); + + for (size_t i = 0, L = sl->length(); i < L; ++i) { + if (!sl->at(i)->contains_placeholder()) { + new_sl->append(sl->at(i)); + } + } + + return new_sl; + + } + + + void Remove_Placeholders::operator()(Ruleset_Ptr r) { + // Create a new selector group without placeholders + Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, r->selector()); + + if (sl) { + // Set the new placeholder selector list + r->selector(remove_placeholders(&sl)); + // Remove placeholders in wrapped selectors + for (Complex_Selector_Obj cs : sl->elements()) { + while (cs) { + if (cs->head()) { + for (Simple_Selector_Obj& ss : cs->head()->elements()) { + if (Wrapped_Selector_Ptr ws = SASS_MEMORY_CAST(Wrapped_Selector, ss)) { + if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) { + Selector_List_Ptr clean = remove_placeholders(sl); + // also clean superflous parent selectors + // probably not really the correct place + clean->remove_parent_selectors(); + ws->selector(clean); + } + } + } + } + cs = cs->tail(); + } + } + } + + // Iterate into child blocks + Block_Obj b = r->block(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->at(i)) { + Statement_Obj st = b->at(i); + st->perform(this); + } + } + } + + void Remove_Placeholders::operator()(Media_Block_Ptr m) { + operator()(&m->block()); + } + void Remove_Placeholders::operator()(Supports_Block_Ptr m) { + operator()(&m->block()); + } + + void Remove_Placeholders::operator()(Directive_Ptr a) { + if (a->block()) a->block()->perform(this); + } + +} diff --git a/src/libsass/src/remove_placeholders.hpp b/src/libsass/src/remove_placeholders.hpp new file mode 100755 index 000000000..a3f81dffc --- /dev/null +++ b/src/libsass/src/remove_placeholders.hpp @@ -0,0 +1,39 @@ +#ifndef SASS_REMOVE_PLACEHOLDERS_H +#define SASS_REMOVE_PLACEHOLDERS_H + +#pragma once + +#include "ast.hpp" +#include "operation.hpp" + +namespace Sass { + + + class Context; + + class Remove_Placeholders : public Operation_CRTP { + + Context& ctx; + + void fallback_impl(AST_Node_Ptr n) {} + + public: + Selector_List_Ptr remove_placeholders(Selector_List_Ptr); + + public: + Remove_Placeholders(Context&); + ~Remove_Placeholders() { } + + void operator()(Block_Ptr); + void operator()(Ruleset_Ptr); + void operator()(Media_Block_Ptr); + void operator()(Supports_Block_Ptr); + void operator()(Directive_Ptr); + + template + void fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/libsass/src/sass.cpp b/src/libsass/src/sass.cpp new file mode 100755 index 000000000..3f6af2703 --- /dev/null +++ b/src/libsass/src/sass.cpp @@ -0,0 +1,93 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "sass.h" +#include "file.hpp" +#include "util.hpp" + +extern "C" { + using namespace Sass; + + // Allocate libsass heap memory + // Don't forget string termination! + void* ADDCALL sass_alloc_memory(size_t size) + { + void* ptr = malloc(size); + if (ptr == NULL) + out_of_memory(); + return ptr; + } + + char* ADDCALL sass_copy_c_string(const char* str) + { + size_t len = strlen(str) + 1; + char* cpy = (char*) sass_alloc_memory(len); + std::memcpy(cpy, str, len); + return cpy; + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_memory(void* ptr) + { + if (ptr) free (ptr); + } + + // caller must free the returned memory + char* ADDCALL sass_string_quote (const char *str, const char quote_mark) + { + std::string quoted = quote(str, quote_mark); + return sass_copy_c_string(quoted.c_str()); + } + + // caller must free the returned memory + char* ADDCALL sass_string_unquote (const char *str) + { + std::string unquoted = unquote(str); + return sass_copy_c_string(unquoted.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + char* ADDCALL sass_resolve_file (const char* file, const char* paths[]) + { + std::string resolved(File::find_file(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + // Get compiled libsass version + const char* ADDCALL libsass_version(void) + { + return LIBSASS_VERSION; + } + + // Get compiled libsass version + const char* ADDCALL libsass_language_version(void) + { + return LIBSASS_LANGUAGE_VERSION; + } + +} + +namespace Sass { + + // helper to aid dreaded MSVC debug mode + char* sass_copy_string(std::string str) + { + // In MSVC the following can lead to segfault: + // sass_copy_c_string(stream.str().c_str()); + // Reason is that the string returned by str() is disposed before + // sass_copy_c_string is invoked. The string is actually a stack + // object, so indeed nobody is holding on to it. So it seems + // perfectly fair to release it right away. So the const char* + // by c_str will point to invalid memory. I'm not sure if this is + // the behavior for all compiler, but I'm pretty sure we would + // have gotten more issues reported if that would be the case. + // Wrapping it in a functions seems the cleanest approach as the + // function must hold on to the stack variable until it's done. + return sass_copy_c_string(str.c_str()); + } + +} \ No newline at end of file diff --git a/src/libsass/src/sass.hpp b/src/libsass/src/sass.hpp new file mode 100755 index 000000000..1f4c88b6e --- /dev/null +++ b/src/libsass/src/sass.hpp @@ -0,0 +1,136 @@ +// must be the first include in all compile units +#ifndef SASS_SASS_H +#define SASS_SASS_H + +// undefine extensions macro to tell sys includes +// that we do not want any macros to be exported +// mainly fixes an issue on SmartOS (SEC macro) +#undef __EXTENSIONS__ + +#ifdef _MSC_VER +#pragma warning(disable : 4005) +#endif + +// aplies to MSVC and MinGW +#ifdef _WIN32 +// we do not want the ERROR macro +# define NOGDI +// we do not want the min/max macro +# define NOMINMAX +// we do not want the IN/OUT macro +# define _NO_W32_PSEUDO_MODIFIERS +#endif + + +// should we be case insensitive +// when dealing with files or paths +#ifndef FS_CASE_SENSITIVE +# ifdef _WIN32 +# define FS_CASE_SENSITIVE 0 +# else +# define FS_CASE_SENSITIVE 1 +# endif +#endif + +// path separation char +#ifndef PATH_SEP +# ifdef _WIN32 +# define PATH_SEP ';' +# else +# define PATH_SEP ':' +# endif +#endif + + +// include C-API header +#include "sass/base.h" + +// For C++ helper +#include + +// output behaviours +namespace Sass { + + // create some C++ aliases for the most used options + const static Sass_Output_Style NESTED = SASS_STYLE_NESTED; + const static Sass_Output_Style COMPACT = SASS_STYLE_COMPACT; + const static Sass_Output_Style EXPANDED = SASS_STYLE_EXPANDED; + const static Sass_Output_Style COMPRESSED = SASS_STYLE_COMPRESSED; + // only used internal to trigger ruby inspect behavior + const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; + const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; + + // helper to aid dreaded MSVC debug mode + // see implementation for more details + char* sass_copy_string(std::string str); + +} + +// input behaviours +enum Sass_Input_Style { + SASS_CONTEXT_NULL, + SASS_CONTEXT_FILE, + SASS_CONTEXT_DATA, + SASS_CONTEXT_FOLDER +}; + +// simple linked list +struct string_list { + string_list* next; + char* string; +}; + +// sass config options structure +struct Sass_Inspect_Options { + + // Output style for the generated css code + // A value from above SASS_STYLE_* constants + enum Sass_Output_Style output_style; + + // Precision for fractional numbers + int precision; + + // initialization list (constructor with defaults) + Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, + int precision = 5) + : output_style(style), precision(precision) + { } + +}; + +// sass config options structure +struct Sass_Output_Options : Sass_Inspect_Options { + + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + + // Emit comments in the generated CSS indicating + // the corresponding source line. + bool source_comments; + + // initialization list (constructor with defaults) + Sass_Output_Options(struct Sass_Inspect_Options opt, + const char* indent = " ", + const char* linefeed = "\n", + bool source_comments = false) + : Sass_Inspect_Options(opt), + indent(indent), linefeed(linefeed), + source_comments(source_comments) + { } + + // initialization list (constructor with defaults) + Sass_Output_Options(Sass_Output_Style style = Sass::NESTED, + int precision = 5, + const char* indent = " ", + const char* linefeed = "\n", + bool source_comments = false) + : Sass_Inspect_Options(style, precision), + indent(indent), linefeed(linefeed), + source_comments(source_comments) + { } + +}; + +#endif diff --git a/src/libsass/src/sass2scss.cpp b/src/libsass/src/sass2scss.cpp new file mode 100755 index 000000000..56333b38e --- /dev/null +++ b/src/libsass/src/sass2scss.cpp @@ -0,0 +1,864 @@ +/** + * sass2scss + * Licensed under the MIT License + * Copyright (c) Marcel Greter + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +// include library +#include +#include +#include +#include +#include +#include +#include + +///* +// +// src comments: comments in sass syntax (staring with //) +// css comments: multiline comments in css syntax (starting with /*) +// +// KEEP_COMMENT: keep src comments in the resulting css code +// STRIP_COMMENT: strip out all comments (either src or css) +// CONVERT_COMMENT: convert all src comments to css comments +// +//*/ + +// our own header +#include "sass2scss.h" + +// add namespace for c++ +namespace Sass +{ + + // return the actual prettify value from options + #define PRETTIFY(converter) (converter.options - (converter.options & 248)) + // query the options integer to check if the option is enables + #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) + #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) + #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) + + // some makros to access the indentation stack + #define INDENT(converter) (converter.indents.top()) + + // some makros to query comment parser status + #define IS_PARSING(converter) (converter.comment == "") + #define IS_COMMENT(converter) (converter.comment != "") + #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) + #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) + + // pretty printer helper function + static std::string closer (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " }" : + PRETTIFY(converter) <= 1 ? " }" : + "\n" + INDENT(converter) + "}"; + } + + // pretty printer helper function + static std::string opener (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " { " : + PRETTIFY(converter) <= 2 ? " {" : + "\n" + INDENT(converter) + "{"; + } + + // check if the given string is a pseudo selector + // needed to differentiate from sass property syntax + static bool isPseudoSelector (std::string& sel) + { + + size_t len = sel.length(); + if (len < 1) return false; + size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); + if (pos != std::string::npos) sel.erase(pos, std::string::npos); + size_t i = sel.length(); + while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } + + // CSS Level 1 - Recommendation + if (sel == ":link") return true; + if (sel == ":visited") return true; + if (sel == ":active") return true; + + // CSS Level 2 (Revision 1) - Recommendation + if (sel == ":lang") return true; + if (sel == ":first-child") return true; + if (sel == ":hover") return true; + if (sel == ":focus") return true; + // disabled - also valid properties + // if (sel == ":left") return true; + // if (sel == ":right") return true; + if (sel == ":first") return true; + + // Selectors Level 3 - Recommendation + if (sel == ":target") return true; + if (sel == ":root") return true; + if (sel == ":nth-child") return true; + if (sel == ":nth-last-of-child") return true; + if (sel == ":nth-of-type") return true; + if (sel == ":nth-last-of-type") return true; + if (sel == ":last-child") return true; + if (sel == ":first-of-type") return true; + if (sel == ":last-of-type") return true; + if (sel == ":only-child") return true; + if (sel == ":only-of-type") return true; + if (sel == ":empty") return true; + if (sel == ":not") return true; + + // CSS Basic User Interface Module Level 3 - Working Draft + if (sel == ":default") return true; + if (sel == ":valid") return true; + if (sel == ":invalid") return true; + if (sel == ":in-range") return true; + if (sel == ":out-of-range") return true; + if (sel == ":required") return true; + if (sel == ":optional") return true; + if (sel == ":read-only") return true; + if (sel == ":read-write") return true; + if (sel == ":dir") return true; + if (sel == ":enabled") return true; + if (sel == ":disabled") return true; + if (sel == ":checked") return true; + if (sel == ":indeterminate") return true; + if (sel == ":nth-last-child") return true; + + // Selectors Level 4 - Working Draft + if (sel == ":any-link") return true; + if (sel == ":local-link") return true; + if (sel == ":scope") return true; + if (sel == ":active-drop-target") return true; + if (sel == ":valid-drop-target") return true; + if (sel == ":invalid-drop-target") return true; + if (sel == ":current") return true; + if (sel == ":past") return true; + if (sel == ":future") return true; + if (sel == ":placeholder-shown") return true; + if (sel == ":user-error") return true; + if (sel == ":blank") return true; + if (sel == ":nth-match") return true; + if (sel == ":nth-last-match") return true; + if (sel == ":nth-column") return true; + if (sel == ":nth-last-column") return true; + if (sel == ":matches") return true; + + // Fullscreen API - Living Standard + if (sel == ":fullscreen") return true; + + // not a pseudo selector + return false; + + } + + // check if there is some char data + // will ignore everything in comments + static bool hasCharData (std::string& sass) + { + + size_t col_pos = 0; + + while (true) + { + + // try to find some meaningfull char + col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); + + // there was no meaningfull char found + if (col_pos == std::string::npos) return false; + + // found a multiline comment opener + if (sass.substr(col_pos, 2) == "/*") + { + // find the multiline comment closer + col_pos = sass.find("*/", col_pos); + // maybe we did not find the closer here + if (col_pos == std::string::npos) return false; + // skip closer + col_pos += 2; + } + else + { + return true; + } + + } + + } + // EO hasCharData + + // find src comment opener + // correctly skips quoted strings + static size_t findCommentOpener (std::string& sass) + { + + size_t col_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + size_t brackets = 0; + + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*()", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + if (character == '(') + { + if (!quoted && !apoed) brackets ++; + } + else if (character == ')') + { + if (!quoted && !apoed) brackets --; + } + else if (character == '\"') + { + // invert quote bool + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + // invert quote bool + if (!quoted && !comment) apoed = !apoed; + } + else if (col_pos > 0 && character == '/') + { + if (sass.at(col_pos - 1) == '*') + { + comment = false; + } + // next needs to be a slash too + else if (sass.at(col_pos - 1) == '/') + { + // only found if not in single or double quote, bracket or comment + if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; + } + } + else if (character == '\\') + { + // skip next char if in quote + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (col_pos > 0 && character == '*') + { + // opening a multiline comment + if (sass.at(col_pos - 1) == '/') + { + // we are now in a comment + if (!quoted && !apoed) comment = true; + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + return col_pos; + + } + // EO findCommentOpener + + // remove multiline comments from sass string + // correctly skips quoted strings + static std::string removeMultilineComment (std::string &sass) + { + + std::string clean = ""; + size_t col_pos = 0; + size_t open_pos = 0; + size_t close_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + + // process sass til string end + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + // found quoted string delimiter + if (character == '\"') + { + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + if (!quoted && !comment) apoed = !apoed; + } + // found possible comment closer + else if (character == '/') + { + // look back to see if it is actually a closer + if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') + { + close_pos = col_pos + 1; comment = false; + } + } + else if (character == '\\') + { + // skip escaped char + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (character == '*') + { + // look back to see if it is actually an opener + if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') + { + comment = true; open_pos = col_pos - 1; + clean += sass.substr(close_pos, open_pos - close_pos); + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + // add final parts (add half open comment text) + if (comment) clean += sass.substr(open_pos); + else clean += sass.substr(close_pos); + + // return string + return clean; + + } + // EO removeMultilineComment + + // right trim a given string + std::string rtrim(const std::string &sass) + { + std::string trimmed = sass; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) + { trimmed.erase(pos_ws + 1); } + else { trimmed.clear(); } + return trimmed; + } + // EO rtrim + + // flush whitespace and print additional text, but + // only print additional chars and buffer whitespace + std::string flush (std::string& sass, converter& converter) + { + + // return flushed + std::string scss = ""; + + // print whitespace buffer + scss += PRETTIFY(converter) > 0 ? + converter.whitespace : ""; + // reset whitespace buffer + converter.whitespace = ""; + + // remove possible newlines from string + size_t pos_right = sass.find_last_not_of("\n\r"); + if (pos_right == std::string::npos) return scss; + + // get the linefeeds from the string + std::string lfs = sass.substr(pos_right + 1); + sass = sass.substr(0, pos_right + 1); + + // find some source comment opener + size_t comment_pos = findCommentOpener(sass); + // check if there was a source comment + if (comment_pos != std::string::npos) + { + // convert comment (but only outside other coments) + if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) + { + // convert to multiline comment + sass.at(comment_pos + 1) = '*'; + // add comment node to the whitespace + sass += " */"; + } + // not at line start + if (comment_pos > 0) + { + // also include whitespace before the actual comment opener + size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); + comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; + } + if (!STRIP_COMMENT(converter)) + { + // add comment node to the whitespace + converter.whitespace += sass.substr(comment_pos); + } + else + { + // sass = removeMultilineComments(sass); + } + // update the actual sass code + sass = sass.substr(0, comment_pos); + } + + // add newline as getline discharged it + converter.whitespace += lfs + "\n"; + + // maybe remove any leading whitespace + if (PRETTIFY(converter) == 0) + { + // remove leading whitespace and update string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + if (pos_left != std::string::npos) sass = sass.substr(pos_left); + } + + // add flushed data + scss += sass; + + // return string + return scss; + + } + // EO flush + + // process a line of the sass text + std::string process (std::string& sass, converter& converter) + { + + // resulting string + std::string scss = ""; + + // strip multi line comments + if (STRIP_COMMENT(converter)) + { + sass = removeMultilineComment(sass); + } + + // right trim input + sass = rtrim(sass); + + // get postion of first meaningfull character in string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + + // special case for final run + if (converter.end_of_file) pos_left = 0; + + // maybe has only whitespace + if (pos_left == std::string::npos) + { + // just add complete whitespace + converter.whitespace += sass + "\n"; + } + // have meaningfull first char + else + { + + // extract and store indentation string + std::string indent = sass.substr(0, pos_left); + + // check if current line starts a comment + std::string open = sass.substr(pos_left, 2); + + // line has less or same indentation + // finalize previous open parser context + if (indent.length() <= INDENT(converter).length()) + { + + // close multilinie comment + if (IS_CSS_COMMENT(converter)) + { + // check if comments will be stripped anyway + if (!STRIP_COMMENT(converter)) scss += " */"; + } + // close src comment comment + else if (IS_SRC_COMMENT(converter)) + { + // add a newline to avoid closer on same line + // this would put the bracket in the comment node + // no longer needed since we parse them correctly + // if (KEEP_COMMENT(converter)) scss += "\n"; + } + // close css properties + else if (converter.property) + { + // add closer unless in concat mode + if (!converter.comma) + { + // if there was no colon we have a selector + // looks like there were no inner properties + if (converter.selector) scss += " {}"; + // add final semicolon + else if (!converter.semicolon) scss += ";"; + } + } + + // reset comment state + converter.comment = ""; + + } + + // make sure we close every "higher" block + while (indent.length() < INDENT(converter).length()) + { + // pop stacked context + converter.indents.pop(); + // print close bracket + if (IS_PARSING(converter)) + { scss += closer(converter); } + else { scss += " */"; } + // reset comment state + converter.comment = ""; + } + + // reset converter state + converter.selector = false; + + // looks like some undocumented behavior ... + // https://github.com/mgreter/sass2scss/issues/29 + if (sass.substr(pos_left, 1) == "\\") { + converter.selector = true; + sass[pos_left] = ' '; + } + + // check if we have sass property syntax + if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") + { + + // default to a selector + // change back if property found + converter.selector = true; + // get postion of first whitespace char + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + // assertion check for valid result + if (pos_wspace != std::string::npos) + { + // get the possible pseudo selector + std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); + // get position of the first real property value char + // pseudo selectors get this far, but have no actual value + size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); + // assertion check for valid result + if (pos_value != std::string::npos) + { + // only process if not (fallowed by a semicolon or is a pseudo selector) + if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) + { + // create new string by interchanging the colon sign for property and value + sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); + // try to find a colon in the current line, but only ... + size_t pos_colon = sass.find_first_not_of(":", pos_left); + // assertion for valid result + if (pos_colon != std::string::npos) + { + // ... after the first word (skip begining colons) + pos_colon = sass.find_first_of(":", pos_colon); + // it is a selector if there was no colon found + converter.selector = pos_colon == std::string::npos; + } + } + } + } + + // check if we have a BEM property (one colon and no selector) + if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; + } + + } + + // terminate some statements immediately + else if ( + sass.substr(pos_left, 5) == "@warn" || + sass.substr(pos_left, 6) == "@debug" || + sass.substr(pos_left, 6) == "@error" || + sass.substr(pos_left, 8) == "@charset" || + sass.substr(pos_left, 10) == "@namespace" + ) { sass = indent + sass.substr(pos_left); } + // replace some specific sass shorthand directives (if not fallowed by a white space character) + else if (sass.substr(pos_left, 1) == "=") + { sass = indent + "@mixin " + sass.substr(pos_left + 1); } + else if (sass.substr(pos_left, 1) == "+") + { + // must be followed by a mixin call (no whitespace afterwards or at ending directly) + if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { + sass = indent + "@include " + sass.substr(pos_left + 1); + } + } + + // add quotes for import if needed + else if (sass.substr(pos_left, 7) == "@import") + { + // get positions for the actual import url + size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); + size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); + // leave proper urls untouched + if (sass.substr(pos_quote, 4) != "url(") + { + // check if the url appears to be already quoted + if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'") + { + // get position of the last char on the line + size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + // assertion check for valid result + if (pos_end != std::string::npos) + { + // add quotes around the full line after the import statement + sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\""; + } + } + } + + } + else if ( + sass.substr(pos_left, 7) != "@return" && + sass.substr(pos_left, 7) != "@extend" && + sass.substr(pos_left, 8) != "@include" && + sass.substr(pos_left, 8) != "@content" + ) { + + // probably a selector anyway + converter.selector = true; + // try to find first colon in the current line + size_t pos_colon = sass.find_first_of(":", pos_left); + // assertion that we have a colon + if (pos_colon != std::string::npos) + { + // it is not a selector if we have a space after a colon + if (sass[pos_colon+1] == ' ') converter.selector = false; + if (sass[pos_colon+1] == ' ') converter.selector = false; + } + + } + + // current line has more indentation + if (indent.length() >= INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // has meaningfull chars + if (hasCharData(sass)) + { + // is probably a property + // also true for selectors + converter.property = true; + } + } + } + // current line has more indentation + if (indent.length() > INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // had meaningfull chars + if (converter.property) + { + // print block opener + scss += opener(converter); + // push new stack context + converter.indents.push(""); + // store block indentation + INDENT(converter) = indent; + } + } + // is and will be a src comment + else if (!IS_CSS_COMMENT(converter)) + { + // scss does not allow multiline src comments + // therefore add forward slashes to all lines + sass.at(INDENT(converter).length()+0) = '/'; + // there is an edge case here if indentation + // is minimal (will overwrite the fist char) + sass.at(INDENT(converter).length()+1) = '/'; + // could code around that, but I dont' think + // this will ever be the cause for any trouble + } + } + + // line is opening a new comment + if (open == "/*" || open == "//") + { + // reset the property state + converter.property = false; + // close previous comment + if (IS_CSS_COMMENT(converter) && open != "") + { + if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; + } + // force single line comments + // into a correct css comment + if (CONVERT_COMMENT(converter)) + { + if (IS_PARSING(converter)) + { sass.at(pos_left + 1) = '*'; } + } + // set comment flag + converter.comment = open; + + } + + // flush data only under certain conditions + if (!( + // strip css and src comments if option is set + (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || + // strip src comment even if strip option is not set + // but only if the keep src comment option is not set + (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) + )) + { + // flush data and buffer whitespace + scss += flush(sass, converter); + } + + // get postion of last meaningfull char + size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + + // check for invalid result + if (pos_right != std::string::npos) + { + + // get the last meaningfull char + std::string close = sass.substr(pos_right, 1); + + // check if next line should be concatenated (list mode) + converter.comma = IS_PARSING(converter) && close == ","; + converter.semicolon = IS_PARSING(converter) && close == ";"; + + // check if we have more than + // one meaningfull char + if (pos_right > 0) + { + + // get the last two chars from string + std::string close = sass.substr(pos_right - 1, 2); + // update parser status for expicitly closed comment + if (close == "*/") converter.comment = ""; + + } + + } + // EO have meaningfull chars from end + + } + // EO have meaningfull chars from start + + // return scss + return scss; + + } + // EO process + + // read line with either CR, LF or CR LF format + // http://stackoverflow.com/a/6089413/1550314 + static std::istream& safeGetline(std::istream& is, std::string& t) + { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf* sb = is.rdbuf(); + + for(;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if(sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if(t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char)c; + } + } + } + + // the main converter function for c++ + char* sass2scss (const std::string& sass, const int options) + { + + // local variables + std::string line; + std::string scss = ""; + std::stringstream stream(sass); + + // create converter variable + converter converter; + // initialise all options + converter.comma = false; + converter.property = false; + converter.selector = false; + converter.semicolon = false; + converter.end_of_file = false; + converter.comment = ""; + converter.whitespace = ""; + converter.indents.push(""); + converter.options = options; + + // read line by line and process them + while(safeGetline(stream, line) && !stream.eof()) + { scss += process(line, converter); } + + // create mutable string + std::string closer = ""; + // set the end of file flag + converter.end_of_file = true; + // process to close all open blocks + scss += process(closer, converter); + + // allocate new memory on the heap + // caller has to free it after use + char * cstr = (char*) malloc (scss.length() + 1); + // create a copy of the string + strcpy (cstr, scss.c_str()); + // return pointer + return &cstr[0]; + + } + // EO sass2scss + +} +// EO namespace + +// implement for c +extern "C" +{ + + char* ADDCALL sass2scss (const char* sass, const int options) + { + return Sass::sass2scss(sass, options); + } + + // Get compiled sass2scss version + const char* ADDCALL sass2scss_version(void) { + return SASS2SCSS_VERSION; + } + +} diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp new file mode 100755 index 000000000..8cf7e2cf7 --- /dev/null +++ b/src/libsass/src/sass_context.cpp @@ -0,0 +1,761 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include + +#include "sass.h" +#include "ast.hpp" +#include "file.hpp" +#include "json.hpp" +#include "util.hpp" +#include "context.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" +#include "ast_fwd_decl.hpp" +#include "error_handling.hpp" + +#define LFEED "\n" + +// C++ helper +namespace Sass { + // see sass_copy_c_string(std::string str) + static inline JsonNode* json_mkstream(const std::stringstream& stream) + { + // hold on to string on stack! + std::string str(stream.str()); + return json_mkstring(str.c_str()); + } + + static int handle_error(Sass_Context* c_ctx) { + try { + throw; + } + catch (Exception::Base& e) { + std::stringstream msg_stream; + std::string cwd(Sass::File::get_cwd()); + + std::string msg_prefix(e.errtype()); + bool got_newline = false; + msg_stream << msg_prefix << ": "; + const char* msg = e.what(); + while (msg && *msg) { + if (*msg == '\r') { + got_newline = true; + } + else if (*msg == '\n') { + got_newline = true; + } + else if (got_newline) { + msg_stream << std::string(msg_prefix.size() + 2, ' '); + got_newline = false; + } + msg_stream << *msg; + ++msg; + } + if (!got_newline) msg_stream << "\n"; + if (e.import_stack) { + for (size_t i = 1; i < e.import_stack->size() - 1; ++i) { + std::string path((*e.import_stack)[i]->imp_path); + std::string rel_path(Sass::File::abs2rel(path, cwd, cwd)); + msg_stream << std::string(msg_prefix.size() + 2, ' '); + msg_stream << (i == 1 ? " on line " : " from line "); + msg_stream << e.pstate.line + 1 << " of " << rel_path << "\n"; + } + } + else { + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << std::string(msg_prefix.size() + 2, ' '); + msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; + } + + // now create the code trace (ToDo: maybe have util functions?) + if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { + size_t line = e.pstate.line; + const char* line_beg = e.pstate.src; + while (line_beg && *line_beg && line) { + if (*line_beg == '\n') --line; + ++line_beg; + } + const char* line_end = line_beg; + while (line_end && *line_end && *line_end != '\n') { + if (*line_end == '\n') break; + if (*line_end == '\r') break; + line_end++; + } + size_t max_left = 42; size_t max_right = 78; + size_t move_in = e.pstate.column > max_left ? e.pstate.column - max_left : 0; + size_t shorten = (line_end - line_beg) - move_in > max_right ? + (line_end - line_beg) - move_in - max_right : 0; + msg_stream << ">> " << std::string(line_beg + move_in, line_end - shorten) << "\n"; + msg_stream << " " << std::string(e.pstate.column - move_in, '-') << "^\n"; + } + + JsonNode* json_err = json_mkobject(); + json_append_member(json_err, "status", json_mknumber(1)); + json_append_member(json_err, "file", json_mkstring(e.pstate.path)); + json_append_member(json_err, "line", json_mknumber((double)(e.pstate.line + 1))); + json_append_member(json_err, "column", json_mknumber((double)(e.pstate.column + 1))); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 1; + c_ctx->error_file = sass_copy_c_string(e.pstate.path); + c_ctx->error_line = e.pstate.line + 1; + c_ctx->error_column = e.pstate.column + 1; + c_ctx->error_src = e.pstate.src; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::bad_alloc& ba) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(2)); + json_append_member(json_err, "message", json_mkstring(ba.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(ba.what()); + c_ctx->error_status = 2; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::exception& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(3)); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 3; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::string& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e.c_str())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.c_str()); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (const char* e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e)); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (...) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unknown error occurred" << std::endl; + json_append_member(json_err, "status", json_mknumber(5)); + json_append_member(json_err, "message", json_mkstring("unknown")); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string("unknown"); + c_ctx->error_status = 5; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + return c_ctx->error_status; + } + + // allow one error handler to throw another error + // this can happen with invalid utf8 and json lib + static int handle_errors(Sass_Context* c_ctx) { + try { return handle_error(c_ctx); } + catch (...) { return handle_error(c_ctx); } + return c_ctx->error_status; + } + + static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() + { + + // assert valid pointer + if (compiler == 0) return 0; + // The cpp context must be set by now + Context* cpp_ctx = compiler->cpp_ctx; + Sass_Context* c_ctx = compiler->c_ctx; + // We will take care to wire up the rest + compiler->cpp_ctx->c_compiler = compiler; + compiler->state = SASS_COMPILER_PARSED; + + try { + + // get input/output path from options + std::string input_path = safe_str(c_ctx->input_path); + std::string output_path = safe_str(c_ctx->output_path); + + // maybe skip some entries of included files + // we do not include stdin for data contexts + bool skip = c_ctx->type == SASS_CONTEXT_DATA; + + // dispatch parse call + Block_Obj root(cpp_ctx->parse()); + // abort on errors + if (!root) return 0; + + // skip all prefixed files? (ToDo: check srcmap) + // IMO source-maps should point to headers already + // therefore don't skip it for now. re-enable or + // remove completely once this is tested + size_t headers = cpp_ctx->head_imports; + + // copy the included files on to the context (dont forget to free later) + if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) + throw(std::bad_alloc()); + + // return parsed block + return root; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + +} + +extern "C" { + using namespace Sass; + + static void sass_clear_options (struct Sass_Options* options); + static void sass_reset_options (struct Sass_Options* options); + static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { + // do not overwrite ourself + if (to == from) return; + // free assigned memory + sass_clear_options(to); + // move memory + *to = *from; + // Reset pointers on source + sass_reset_options(from); + } + + #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } + #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ + { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } + + #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ + type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } + #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ + type sass_context_take_##option (struct Sass_Context* ctx) \ + { type foo = ctx->option; ctx->option = 0; return foo; } + + + // generic compilation function (not exported, use file/data compile instead) + static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() + { + try { + // register our custom functions + if (c_ctx->c_functions) { + auto this_func_data = c_ctx->c_functions; + while (this_func_data && *this_func_data) { + cpp_ctx->add_c_function(*this_func_data); + ++this_func_data; + } + } + + // register our custom headers + if (c_ctx->c_headers) { + auto this_head_data = c_ctx->c_headers; + while (this_head_data && *this_head_data) { + cpp_ctx->add_c_header(*this_head_data); + ++this_head_data; + } + } + + // register our custom importers + if (c_ctx->c_importers) { + auto this_imp_data = c_ctx->c_importers; + while (this_imp_data && *this_imp_data) { + cpp_ctx->add_c_importer(*this_imp_data); + ++this_imp_data; + } + } + + // reset error status + c_ctx->error_json = 0; + c_ctx->error_text = 0; + c_ctx->error_message = 0; + c_ctx->error_status = 0; + // reset error position + c_ctx->error_src = 0; + c_ctx->error_file = 0; + c_ctx->error_line = std::string::npos; + c_ctx->error_column = std::string::npos; + + // allocate a new compiler instance + Sass_Compiler* compiler = (struct Sass_Compiler*) calloc(1, sizeof(struct Sass_Compiler)); + compiler->state = SASS_COMPILER_CREATED; + + // store in sass compiler + compiler->c_ctx = c_ctx; + compiler->cpp_ctx = cpp_ctx; + cpp_ctx->c_compiler = compiler; + + // use to parse block + return compiler; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + + // generic compilation function (not exported, use file/data compile instead) + static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) + { + + // prepare sass compiler with context and options + Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); + + try { + // call each compiler step + sass_compiler_parse(compiler); + sass_compiler_execute(compiler); + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + sass_delete_compiler(compiler); + + return c_ctx->error_status; + } + + inline void init_options (struct Sass_Options* options) + { + options->precision = 5; + options->indent = " "; + options->linefeed = LFEED; + } + + Sass_Options* ADDCALL sass_make_options (void) + { + struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); + if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } + init_options(options); + return options; + } + + Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) + { + SharedObj::setTaint(true); // needed for static colors + struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_FILE; + init_options(ctx); + try { + if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } + if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } + sass_option_set_input_path(ctx, input_path); + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) + { + struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_DATA; + init_options(ctx); + try { + if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } + if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } + ctx->source_string = source_string; + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 0; + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_prepare_context(data_ctx, cpp_ctx); + } + + struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 0; + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_prepare_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 1; + if (data_ctx->error_status) + return data_ctx->error_status; + try { + if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } + // empty source string is a valid case, even if not really usefull (different than with file context) + // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } + } + catch (...) { return handle_errors(data_ctx) | 1; } + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_compile_context(data_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 1; + if (file_ctx->error_status) + return file_ctx->error_status; + try { + if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } + if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } + } + catch (...) { return handle_errors(file_ctx) | 1; } + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_compile_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_PARSED) return 0; + if (compiler->state != SASS_COMPILER_CREATED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + // parse the context we have set up (file or data) + compiler->root = &sass_parse_block(compiler); + // success + return 0; + } + + int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_EXECUTED) return 0; + if (compiler->state != SASS_COMPILER_PARSED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->root.isNull()) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + compiler->state = SASS_COMPILER_EXECUTED; + Context* cpp_ctx = compiler->cpp_ctx; + Block_Obj root = compiler->root; + // compile the parsed root block + try { compiler->c_ctx->output_string = cpp_ctx->render(root); } + // pass catched errors to generic error handler + catch (...) { return handle_errors(compiler->c_ctx) | 1; } + // generate source map json and store on context + compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); + // success + return 0; + } + + // helper function, not exported, only accessible locally + static void sass_reset_options (struct Sass_Options* options) + { + // free pointer before + // or copy/move them + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + } + + // helper function, not exported, only accessible locally + static void sass_clear_options (struct Sass_Options* options) + { + if (options == 0) return; + // Deallocate custom functions + if (options->c_functions) { + Sass_Function_List this_func_data = options->c_functions; + while (this_func_data && *this_func_data) { + free(*this_func_data); + ++this_func_data; + } + } + // Deallocate custom headers + if (options->c_headers) { + Sass_Importer_List this_head_data = options->c_headers; + while (this_head_data && *this_head_data) { + free(*this_head_data); + ++this_head_data; + } + } + // Deallocate custom importers + if (options->c_importers) { + Sass_Importer_List this_imp_data = options->c_importers; + while (this_imp_data && *this_imp_data) { + free(*this_imp_data); + ++this_imp_data; + } + } + // Deallocate inc paths + if (options->plugin_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->plugin_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Deallocate inc paths + if (options->include_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->include_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Free options strings + free(options->input_path); + free(options->output_path); + free(options->plugin_path); + free(options->include_path); + free(options->source_map_file); + free(options->source_map_root); + // Free custom functions + free(options->c_functions); + // Free custom importers + free(options->c_importers); + free(options->c_headers); + // Reset our pointers + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + } + + // helper function, not exported, only accessible locally + // sass_free_context is also defined in old sass_interface + static void sass_clear_context (struct Sass_Context* ctx) + { + if (ctx == 0) return; + // release the allocated memory (mostly via sass_copy_c_string) + if (ctx->output_string) free(ctx->output_string); + if (ctx->source_map_string) free(ctx->source_map_string); + if (ctx->error_message) free(ctx->error_message); + if (ctx->error_text) free(ctx->error_text); + if (ctx->error_json) free(ctx->error_json); + if (ctx->error_file) free(ctx->error_file); + free_string_array(ctx->included_files); + // play safe and reset properties + ctx->output_string = 0; + ctx->source_map_string = 0; + ctx->error_message = 0; + ctx->error_text = 0; + ctx->error_json = 0; + ctx->error_file = 0; + ctx->included_files = 0; + // debug leaked memory + #ifdef DEBUG_SHARED_PTR + SharedObj::dumpMemLeaks(); + #endif + // now clear the options + sass_clear_options(ctx); + } + + void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) + { + if (compiler == 0) { + return; + } + Context* cpp_ctx = compiler->cpp_ctx; + if (cpp_ctx) delete(cpp_ctx); + compiler->cpp_ctx = NULL; + compiler->c_ctx = NULL; + compiler->root = NULL; + free(compiler); + } + + void ADDCALL sass_delete_options (struct Sass_Options* options) + { + sass_clear_options(options); free(options); + } + + // Deallocate all associated memory with file context + void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) + { + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + // Deallocate all associated memory with data context + void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) + { + // clean the source string if it was not passed + // we reset this member once we start parsing + if (ctx->source_string) free(ctx->source_string); + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + + // Getters for sass context from specific implementations + struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } + + // Getters for context options from Sass_Context + struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } + void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + + // Getters for Sass_Compiler options (get conected sass context) + enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } + struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + // Getters for Sass_Compiler options (query import stack) + size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } + Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } + Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } + + // Calculate the size of the stored null terminated array + size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) + { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } + + // Create getter and setters for options + IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); + IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, plugin_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, include_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); + + // Create getter and setters for context + IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); + IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); + IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); + IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); + + // Take ownership of memory (value on context is set to 0) + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); + IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); + + // Push function for include paths (no manipulation support for now) + void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) + { + + struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (include_path == 0) return; + include_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->include_paths; + if (!options->include_paths) { + options->include_paths = include_path; + } else { + while (last->next) + last = last->next; + last->next = include_path; + } + + } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) + { + + struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (plugin_path == 0) return; + plugin_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->plugin_paths; + if (!options->plugin_paths) { + options->plugin_paths = plugin_path; + } else { + while (last->next) + last = last->next; + last->next = plugin_path; + } + + } + +} diff --git a/src/libsass/src/sass_context.hpp b/src/libsass/src/sass_context.hpp new file mode 100755 index 000000000..3a78d3ad8 --- /dev/null +++ b/src/libsass/src/sass_context.hpp @@ -0,0 +1,130 @@ +#ifndef SASS_SASS_CONTEXT_H +#define SASS_SASS_CONTEXT_H + +#include "sass.h" +#include "sass.hpp" +#include "context.hpp" +#include "ast_fwd_decl.hpp" + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. Libsass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Maybe use array interface instead? + char* include_path; + char* plugin_path; + + // Include paths (linked string list) + struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; + + // Path to source map file + // Enables source map generation + // Used to create sourceMappingUrl + char* source_map_file; + + // Directly inserted in source maps + char* source_map_root; + + // Custom functions that can be called from sccs code + Sass_Function_List c_functions; + + // List of custom importers + Sass_Importer_List c_importers; + + // List of custom headers + Sass_Importer_List c_headers; + +}; + + +// base for all contexts +struct Sass_Context : Sass_Options +{ + + // store context type info + enum Sass_Input_Style type; + + // generated output data + char* output_string; + + // generated source map json + char* source_map_string; + + // error status + int error_status; + char* error_json; + char* error_text; + char* error_message; + // error position + char* error_file; + size_t error_line; + size_t error_column; + const char* error_src; + + // report imported files + char** included_files; + +}; + +// struct for file compilation +struct Sass_File_Context : Sass_Context { + + // no additional fields required + // input_path is already on options + +}; + +// struct for data compilation +struct Sass_Data_Context : Sass_Context { + + // provided source string + char* source_string; + char* srcmap_string; + +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + Sass::Context* cpp_ctx; + // Sass::Block + Sass::Block_Obj root; +}; + +#endif \ No newline at end of file diff --git a/src/libsass/src/sass_functions.cpp b/src/libsass/src/sass_functions.cpp new file mode 100755 index 000000000..db153d603 --- /dev/null +++ b/src/libsass/src/sass_functions.cpp @@ -0,0 +1,145 @@ +#include "sass.hpp" +#include +#include "util.hpp" +#include "context.hpp" +#include "sass/functions.h" +#include "sass_functions.hpp" + +extern "C" { + using namespace Sass; + + Sass_Function_List ADDCALL sass_make_function_list(size_t length) + { + return (Sass_Function_List) calloc(length + 1, sizeof(Sass_Function_Entry)); + } + + Sass_Function_Entry ADDCALL sass_make_function(const char* signature, Sass_Function_Fn function, void* cookie) + { + Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); + if (cb == 0) return 0; + cb->signature = signature; + cb->function = function; + cb->cookie = cookie; + return cb; + } + + // Setters and getters for callbacks on function lists + Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; } + void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; } + + const char* ADDCALL sass_function_get_signature(Sass_Function_Entry cb) { return cb->signature; } + Sass_Function_Fn ADDCALL sass_function_get_function(Sass_Function_Entry cb) { return cb->function; } + void* ADDCALL sass_function_get_cookie(Sass_Function_Entry cb) { return cb->cookie; } + + Sass_Importer_Entry ADDCALL sass_make_importer(Sass_Importer_Fn importer, double priority, void* cookie) + { + Sass_Importer_Entry cb = (Sass_Importer_Entry) calloc(1, sizeof(Sass_Importer)); + if (cb == 0) return 0; + cb->importer = importer; + cb->priority = priority; + cb->cookie = cookie; + return cb; + } + + Sass_Importer_Fn ADDCALL sass_importer_get_function(Sass_Importer_Entry cb) { return cb->importer; } + double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb) { return cb->priority; } + void* ADDCALL sass_importer_get_cookie(Sass_Importer_Entry cb) { return cb->cookie; } + + // Just in case we have some stray import structs + void ADDCALL sass_delete_importer (Sass_Importer_Entry cb) + { + free(cb); + } + + // Creator for sass custom importer function list + Sass_Importer_List ADDCALL sass_make_importer_list(size_t length) + { + return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry)); + } + + Sass_Importer_Entry ADDCALL sass_importer_get_list_entry(Sass_Importer_List list, size_t idx) { return list[idx]; } + void ADDCALL sass_importer_set_list_entry(Sass_Importer_List list, size_t idx, Sass_Importer_Entry cb) { list[idx] = cb; } + + // Creator for sass custom importer return argument list + Sass_Import_List ADDCALL sass_make_import_list(size_t length) + { + return (Sass_Import**) calloc(length + 1, sizeof(Sass_Import*)); + } + + // Creator for a single import entry returned by the custom importer inside the list + // We take ownership of the memory for source and srcmap (freed when context is destroyd) + Sass_Import_Entry ADDCALL sass_make_import(const char* imp_path, const char* abs_path, char* source, char* srcmap) + { + Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import)); + if (v == 0) return 0; + v->imp_path = imp_path ? sass_copy_c_string(imp_path) : 0; + v->abs_path = abs_path ? sass_copy_c_string(abs_path) : 0; + v->source = source; + v->srcmap = srcmap; + v->error = 0; + v->line = -1; + v->column = -1; + return v; + } + + // Older style, but somehow still valid - keep around or deprecate? + Sass_Import_Entry ADDCALL sass_make_import_entry(const char* path, char* source, char* srcmap) + { + return sass_make_import(path, path, source, srcmap); + } + + // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting) + Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* error, size_t line, size_t col) + { + if (import == 0) return 0; + if (import->error) free(import->error); + import->error = error ? sass_copy_c_string(error) : 0; + import->line = line ? line : -1; + import->column = col ? col : -1; + return import; + } + + // Setters and getters for entries on the import list + void ADDCALL sass_import_set_list_entry(Sass_Import_List list, size_t idx, Sass_Import_Entry entry) { list[idx] = entry; } + Sass_Import_Entry ADDCALL sass_import_get_list_entry(Sass_Import_List list, size_t idx) { return list[idx]; } + + // Deallocator for the allocated memory + void ADDCALL sass_delete_import_list(Sass_Import_List list) + { + Sass_Import_List it = list; + if (list == 0) return; + while(*list) { + sass_delete_import(*list); + ++list; + } + free(it); + } + + // Just in case we have some stray import structs + void ADDCALL sass_delete_import(Sass_Import_Entry import) + { + free(import->imp_path); + free(import->abs_path); + free(import->source); + free(import->srcmap); + free(import->error); + free(import); + } + + // Getter for import entry + const char* ADDCALL sass_import_get_imp_path(Sass_Import_Entry entry) { return entry->imp_path; } + const char* ADDCALL sass_import_get_abs_path(Sass_Import_Entry entry) { return entry->abs_path; } + const char* ADDCALL sass_import_get_source(Sass_Import_Entry entry) { return entry->source; } + const char* ADDCALL sass_import_get_srcmap(Sass_Import_Entry entry) { return entry->srcmap; } + + // Getter for import error entry + size_t ADDCALL sass_import_get_error_line(Sass_Import_Entry entry) { return entry->line; } + size_t ADDCALL sass_import_get_error_column(Sass_Import_Entry entry) { return entry->column; } + const char* ADDCALL sass_import_get_error_message(Sass_Import_Entry entry) { return entry->error; } + + // Explicit functions to take ownership of the memory + // Resets our own property since we do not know if it is still alive + char* ADDCALL sass_import_take_source(Sass_Import_Entry entry) { char* ptr = entry->source; entry->source = 0; return ptr; } + char* ADDCALL sass_import_take_srcmap(Sass_Import_Entry entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; } + +} diff --git a/src/libsass/src/sass_functions.hpp b/src/libsass/src/sass_functions.hpp new file mode 100755 index 000000000..5a7865d77 --- /dev/null +++ b/src/libsass/src/sass_functions.hpp @@ -0,0 +1,32 @@ +#ifndef SASS_SASS_FUNCTIONS_H +#define SASS_SASS_FUNCTIONS_H + +#include "sass.h" + +// Struct to hold custom function callback +struct Sass_Function { + const char* signature; + Sass_Function_Fn function; + void* cookie; +}; + +// External import entry +struct Sass_Import { + char* imp_path; // path as found in the import statement + char *abs_path; // path after importer has resolved it + char* source; + char* srcmap; + // error handling + char* error; + size_t line; + size_t column; +}; + +// Struct to hold importer callback +struct Sass_Importer { + Sass_Importer_Fn importer; + double priority; + void* cookie; +}; + +#endif \ No newline at end of file diff --git a/src/libsass/src/sass_util.cpp b/src/libsass/src/sass_util.cpp new file mode 100755 index 000000000..f3dd81f2b --- /dev/null +++ b/src/libsass/src/sass_util.cpp @@ -0,0 +1,149 @@ +#include "sass.hpp" +#include "node.hpp" + +namespace Sass { + + + /* + # This is the equivalent of ruby's Sass::Util.paths. + # + # Return an array of all possible paths through the given arrays. + # + # @param arrs [NodeCollection>] + # @return [NodeCollection>] + # + # @example + # paths([[1, 2], [3, 4], [5]]) #=> + # # [[1, 3, 5], + # # [2, 3, 5], + # # [1, 4, 5], + # # [2, 4, 5]] + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def paths(arrs) + // I changed the inject and maps to an iterative approach to make it easier to implement in C++ + loopStart = [[]] + + for arr in arrs do + permutations = [] + for e in arr do + for path in loopStart do + permutations.push(path + [e]) + end + end + loopStart = permutations + end + end + */ + Node paths(const Node& arrs, Context& ctx) { + + Node loopStart = Node::createCollection(); + loopStart.collection()->push_back(Node::createCollection()); + + for (NodeDeque::iterator arrsIter = arrs.collection()->begin(), arrsEndIter = arrs.collection()->end(); + arrsIter != arrsEndIter; ++arrsIter) { + + Node& arr = *arrsIter; + + Node permutations = Node::createCollection(); + + for (NodeDeque::iterator arrIter = arr.collection()->begin(), arrIterEnd = arr.collection()->end(); + arrIter != arrIterEnd; ++arrIter) { + + Node& e = *arrIter; + + for (NodeDeque::iterator loopStartIter = loopStart.collection()->begin(), loopStartIterEnd = loopStart.collection()->end(); + loopStartIter != loopStartIterEnd; ++loopStartIter) { + + Node& path = *loopStartIter; + + Node newPermutation = Node::createCollection(); + newPermutation.got_line_feed = arr.got_line_feed; + newPermutation.plus(path); + newPermutation.collection()->push_back(e); + + permutations.collection()->push_back(newPermutation); + } + } + + loopStart = permutations; + } + + return loopStart; + } + + + /* + This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. + Sass::Util.flatten requires the number of levels to flatten, while + [].flatten doesn't and will flatten the entire array. This function + supports both. + + # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened + # + # @param arr [NodeCollection] The array to flatten + # @param n [int] The number of levels to flatten + # @return [NodeCollection] The flattened array + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def flatten(arr, n = -1) + if n != -1 and n == 0 then + return arr + end + + flattened = [] + + for e in arr do + if e.is_a?(Array) then + flattened.concat(flatten(e, n - 1)) + else + flattened << e + end + end + + return flattened + end + */ + Node flatten(Node& arr, Context& ctx, int n) { + if (n != -1 && n == 0) { + return arr; + } + + Node flattened = Node::createCollection(); + if (arr.got_line_feed) flattened.got_line_feed = true; + + for (NodeDeque::iterator iter = arr.collection()->begin(), iterEnd = arr.collection()->end(); + iter != iterEnd; iter++) { + Node& e = *iter; + + // e has the lf set + if (e.isCollection()) { + + // e.collection().got_line_feed = e.got_line_feed; + Node recurseFlattened = flatten(e, ctx, n - 1); + + if(e.got_line_feed) { + flattened.got_line_feed = e.got_line_feed; + recurseFlattened.got_line_feed = e.got_line_feed; + } + + for(auto i : (*recurseFlattened.collection())) { + if (recurseFlattened.got_line_feed) { + + i.got_line_feed = true; + } + flattened.collection()->push_back(i); + } + + } else { + flattened.collection()->push_back(e); + } + } + + return flattened; + } +} diff --git a/src/libsass/src/sass_util.hpp b/src/libsass/src/sass_util.hpp new file mode 100755 index 000000000..ca2819aaa --- /dev/null +++ b/src/libsass/src/sass_util.hpp @@ -0,0 +1,256 @@ +#ifndef SASS_SASS_UTIL_H +#define SASS_SASS_UTIL_H + +#include "ast.hpp" +#include "node.hpp" +#include "debug.hpp" + +namespace Sass { + + + + + /* + This is for ports of functions in the Sass:Util module. + */ + + + /* + # Return a Node collection of all possible paths through the given Node collection of Node collections. + # + # @param arrs [NodeCollection>] + # @return [NodeCollection>] + # + # @example + # paths([[1, 2], [3, 4], [5]]) #=> + # # [[1, 3, 5], + # # [2, 3, 5], + # # [1, 4, 5], + # # [2, 4, 5]] + */ + Node paths(const Node& arrs, Context& ctx); + + + /* + This class is a default implementation of a Node comparator that can be passed to the lcs function below. + It uses operator== for equality comparision. It then returns one if the Nodes are equal. + */ + class DefaultLcsComparator { + public: + bool operator()(const Node& one, const Node& two, Node& out) const { + // TODO: Is this the correct C++ interpretation? + // block ||= proc {|a, b| a == b && a} + if (nodesEqual(one, two, true)) { + out = one; + return true; + } + + return false; + } + }; + + + typedef std::vector > LCSTable; + + + /* + This is the equivalent of ruby's Sass::Util.lcs_backtrace. + + # Computes a single longest common subsequence for arrays x and y. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS + */ + template + Node lcs_backtrace(const LCSTable& c, const Node& x, const Node& y, int i, int j, const ComparatorType& comparator) { + DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) + + if (i == 0 || j == 0) { + DEBUG_PRINTLN(LCS, "RETURNING EMPTY") + return Node::createCollection(); + } + + NodeDeque& xChildren = *(x.collection()); + NodeDeque& yChildren = *(y.collection()); + + Node compareOut = Node::createNil(); + if (comparator(xChildren[i], yChildren[j], compareOut)) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") + Node result = lcs_backtrace(c, x, y, i - 1, j - 1, comparator); + result.collection()->push_back(compareOut); + return result; + } + + if (c[i][j - 1] > c[i - 1][j]) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") + return lcs_backtrace(c, x, y, i, j - 1, comparator); + } + + DEBUG_PRINTLN(LCS, "FINAL RETURN") + return lcs_backtrace(c, x, y, i - 1, j, comparator); + } + + + /* + This is the equivalent of ruby's Sass::Util.lcs_table. + + # Calculates the memoization table for the Least Common Subsequence algorithm. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS + */ + template + void lcs_table(const Node& x, const Node& y, const ComparatorType& comparator, LCSTable& out) { + DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) + + NodeDeque& xChildren = *(x.collection()); + NodeDeque& yChildren = *(y.collection()); + + LCSTable c(xChildren.size(), std::vector(yChildren.size())); + + // These shouldn't be necessary since the vector will be initialized to 0 already. + // x.size.times {|i| c[i][0] = 0} + // y.size.times {|j| c[0][j] = 0} + + for (size_t i = 1; i < xChildren.size(); i++) { + for (size_t j = 1; j < yChildren.size(); j++) { + Node compareOut = Node::createNil(); + + if (comparator(xChildren[i], yChildren[j], compareOut)) { + c[i][j] = c[i - 1][j - 1] + 1; + } else { + c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); + } + } + } + + out = c; + } + + + /* + This is the equivalent of ruby's Sass::Util.lcs. + + # Computes a single longest common subsequence for `x` and `y`. + # If there are more than one longest common subsequences, + # the one returned is that which starts first in `x`. + + # @param x [NodeCollection] + # @param y [NodeCollection] + # @comparator An equality check between elements of `x` and `y`. + # @return [NodeCollection] The LCS + + http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + */ + template + Node lcs(Node& x, Node& y, const ComparatorType& comparator, Context& ctx) { + DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) + + Node newX = Node::createCollection(); + newX.collection()->push_back(Node::createNil()); + newX.plus(x); + + Node newY = Node::createCollection(); + newY.collection()->push_back(Node::createNil()); + newY.plus(y); + + LCSTable table; + lcs_table(newX, newY, comparator, table); + + return lcs_backtrace(table, newX, newY, static_cast(newX.collection()->size()) - 1, static_cast(newY.collection()->size()) - 1, comparator); + } + + + /* + This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. + Sass::Util.flatten requires the number of levels to flatten, while + [].flatten doesn't and will flatten the entire array. This function + supports both. + + # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened + # + # @param arr [NodeCollection] The array to flatten + # @param n [int] The number of levels to flatten + # @return [NodeCollection] The flattened array + */ + Node flatten(Node& arr, Context& ctx, int n = -1); + + + /* + This is the equivalent of ruby's Sass::Util.group_by_to_a. + + # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed + # order. Unlike [#hash_to_a], the resulting order isn't sorted key order; + # instead, it's the same order as `#group_by` has under Ruby 1.9 (key + # appearance order). + # + # @param enum [Enumerable] + # @return [Array<[Object, Array]>] An array of pairs. + + TODO: update @param and @return once I know what those are. + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def group_by_to_a(enum, &block) + order = {} + + arr = [] + + grouped = {} + + for e in enum do + key = block[e] + unless order.include?(key) + order[key] = order.size + end + + if not grouped.has_key?(key) then + grouped[key] = [e] + else + grouped[key].push(e) + end + end + + grouped.each do |key, vals| + arr[order[key]] = [key, vals] + end + + arr + end + + */ + template + void group_by_to_a(std::vector& enumeration, KeyFunctorType& keyFunc, std::vector > >& arr /*out*/) { + + std::map order; + + std::map > grouped; + + for (typename std::vector::iterator enumIter = enumeration.begin(), enumIterEnd = enumeration.end(); enumIter != enumIterEnd; enumIter++) { + EnumType& e = *enumIter; + + KeyType key = keyFunc(e); + + if (grouped.find(key->hash()) == grouped.end()) { + order.insert(std::make_pair((unsigned int)order.size(), key)); + + std::vector newCollection; + newCollection.push_back(e); + grouped.insert(std::make_pair(key->hash(), newCollection)); + } else { + std::vector& collection = grouped.at(key->hash()); + collection.push_back(e); + } + } + + for (unsigned int index = 0; index < order.size(); index++) { + KeyType& key = order.at(index); + std::vector& values = grouped.at(key->hash()); + + std::pair > grouping = std::make_pair(key, values); + + arr.push_back(grouping); + } + } + + +} + +#endif diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp new file mode 100755 index 000000000..efdd8dd62 --- /dev/null +++ b/src/libsass/src/sass_values.cpp @@ -0,0 +1,354 @@ +#include "sass.hpp" +#include +#include +#include "util.hpp" +#include "eval.hpp" +#include "values.hpp" +#include "sass/values.h" +#include "sass_values.hpp" + +extern "C" { + using namespace Sass; + + // Return the sass tag for a generic sass value + enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; } + + // Check value for specified type + bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; } + bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; } + bool ADDCALL sass_value_is_string(const union Sass_Value* v) { return v->unknown.tag == SASS_STRING; } + bool ADDCALL sass_value_is_boolean(const union Sass_Value* v) { return v->unknown.tag == SASS_BOOLEAN; } + bool ADDCALL sass_value_is_color(const union Sass_Value* v) { return v->unknown.tag == SASS_COLOR; } + bool ADDCALL sass_value_is_list(const union Sass_Value* v) { return v->unknown.tag == SASS_LIST; } + bool ADDCALL sass_value_is_map(const union Sass_Value* v) { return v->unknown.tag == SASS_MAP; } + bool ADDCALL sass_value_is_error(const union Sass_Value* v) { return v->unknown.tag == SASS_ERROR; } + bool ADDCALL sass_value_is_warning(const union Sass_Value* v) { return v->unknown.tag == SASS_WARNING; } + + // Getters and setters for Sass_Number + double ADDCALL sass_number_get_value(const union Sass_Value* v) { return v->number.value; } + void ADDCALL sass_number_set_value(union Sass_Value* v, double value) { v->number.value = value; } + const char* ADDCALL sass_number_get_unit(const union Sass_Value* v) { return v->number.unit; } + void ADDCALL sass_number_set_unit(union Sass_Value* v, char* unit) { v->number.unit = unit; } + + // Getters and setters for Sass_String + const char* ADDCALL sass_string_get_value(const union Sass_Value* v) { return v->string.value; } + void ADDCALL sass_string_set_value(union Sass_Value* v, char* value) { v->string.value = value; } + bool ADDCALL sass_string_is_quoted(const union Sass_Value* v) { return v->string.quoted; } + void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted) { v->string.quoted = quoted; } + + // Getters and setters for Sass_Boolean + bool ADDCALL sass_boolean_get_value(const union Sass_Value* v) { return v->boolean.value; } + void ADDCALL sass_boolean_set_value(union Sass_Value* v, bool value) { v->boolean.value = value; } + + // Getters and setters for Sass_Color + double ADDCALL sass_color_get_r(const union Sass_Value* v) { return v->color.r; } + void ADDCALL sass_color_set_r(union Sass_Value* v, double r) { v->color.r = r; } + double ADDCALL sass_color_get_g(const union Sass_Value* v) { return v->color.g; } + void ADDCALL sass_color_set_g(union Sass_Value* v, double g) { v->color.g = g; } + double ADDCALL sass_color_get_b(const union Sass_Value* v) { return v->color.b; } + void ADDCALL sass_color_set_b(union Sass_Value* v, double b) { v->color.b = b; } + double ADDCALL sass_color_get_a(const union Sass_Value* v) { return v->color.a; } + void ADDCALL sass_color_set_a(union Sass_Value* v, double a) { v->color.a = a; } + + // Getters and setters for Sass_List + size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; } + enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; } + void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; } + // Getters and setters for Sass_List values + union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; } + void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; } + + // Getters and setters for Sass_Map + size_t ADDCALL sass_map_get_length(const union Sass_Value* v) { return v->map.length; } + // Getters and setters for Sass_List keys and values + union Sass_Value* ADDCALL sass_map_get_key(const union Sass_Value* v, size_t i) { return v->map.pairs[i].key; } + union Sass_Value* ADDCALL sass_map_get_value(const union Sass_Value* v, size_t i) { return v->map.pairs[i].value; } + void ADDCALL sass_map_set_key(union Sass_Value* v, size_t i, union Sass_Value* key) { v->map.pairs[i].key = key; } + void ADDCALL sass_map_set_value(union Sass_Value* v, size_t i, union Sass_Value* val) { v->map.pairs[i].value = val; } + + // Getters and setters for Sass_Error + char* ADDCALL sass_error_get_message(const union Sass_Value* v) { return v->error.message; }; + void ADDCALL sass_error_set_message(union Sass_Value* v, char* msg) { v->error.message = msg; }; + + // Getters and setters for Sass_Warning + char* ADDCALL sass_warning_get_message(const union Sass_Value* v) { return v->warning.message; }; + void ADDCALL sass_warning_set_message(union Sass_Value* v, char* msg) { v->warning.message = msg; }; + + // Creator functions for all value types + + union Sass_Value* ADDCALL sass_make_boolean(bool val) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->boolean.tag = SASS_BOOLEAN; + v->boolean.value = val; + return v; + } + + union Sass_Value* ADDCALL sass_make_number(double val, const char* unit) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->number.tag = SASS_NUMBER; + v->number.value = val; + v->number.unit = unit ? sass_copy_c_string(unit) : 0; + if (v->number.unit == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_color(double r, double g, double b, double a) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->color.tag = SASS_COLOR; + v->color.r = r; + v->color.g = g; + v->color.b = b; + v->color.a = a; + return v; + } + + union Sass_Value* ADDCALL sass_make_string(const char* val) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->string.quoted = false; + v->string.tag = SASS_STRING; + v->string.value = val ? sass_copy_c_string(val) : 0; + if (v->string.value == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_qstring(const char* val) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->string.quoted = true; + v->string.tag = SASS_STRING; + v->string.value = val ? sass_copy_c_string(val) : 0; + if (v->string.value == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->list.tag = SASS_LIST; + v->list.length = len; + v->list.separator = sep; + v->list.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value*)); + if (v->list.values == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_map(size_t len) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->map.tag = SASS_MAP; + v->map.length = len; + v->map.pairs = (struct Sass_MapPair*) calloc(len, sizeof(struct Sass_MapPair)); + if (v->map.pairs == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_null(void) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->null.tag = SASS_NULL; + return v; + } + + union Sass_Value* ADDCALL sass_make_error(const char* msg) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->error.tag = SASS_ERROR; + v->error.message = msg ? sass_copy_c_string(msg) : 0; + if (v->error.message == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_warning(const char* msg) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->warning.tag = SASS_WARNING; + v->warning.message = msg ? sass_copy_c_string(msg) : 0; + if (v->warning.message == 0) { free(v); return 0; } + return v; + } + + // will free all associated sass values + void ADDCALL sass_delete_value(union Sass_Value* val) { + + size_t i; + if (val == 0) return; + switch(val->unknown.tag) { + case SASS_NULL: { + } break; + case SASS_BOOLEAN: { + } break; + case SASS_NUMBER: { + free(val->number.unit); + } break; + case SASS_COLOR: { + } break; + case SASS_STRING: { + free(val->string.value); + } break; + case SASS_LIST: { + for (i=0; ilist.length; i++) { + sass_delete_value(val->list.values[i]); + } + free(val->list.values); + } break; + case SASS_MAP: { + for (i=0; imap.length; i++) { + sass_delete_value(val->map.pairs[i].key); + sass_delete_value(val->map.pairs[i].value); + } + free(val->map.pairs); + } break; + case SASS_ERROR: { + free(val->error.message); + } break; + case SASS_WARNING: { + free(val->error.message); + } break; + } + + free(val); + + } + + // Make a deep cloned copy of the given sass value + union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val) + { + + size_t i; + if (val == 0) return 0; + switch(val->unknown.tag) { + case SASS_NULL: { + return sass_make_null(); + } break; + case SASS_BOOLEAN: { + return sass_make_boolean(val->boolean.value); + } break; + case SASS_NUMBER: { + return sass_make_number(val->number.value, val->number.unit); + } break; + case SASS_COLOR: { + return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a); + } break; + case SASS_STRING: { + return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); + } break; + case SASS_LIST: { + union Sass_Value* list = sass_make_list(val->list.length, val->list.separator); + for (i = 0; i < list->list.length; i++) { + list->list.values[i] = sass_clone_value(val->list.values[i]); + } + return list; + } break; + case SASS_MAP: { + union Sass_Value* map = sass_make_map(val->map.length); + for (i = 0; i < val->map.length; i++) { + map->map.pairs[i].key = sass_clone_value(val->map.pairs[i].key); + map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value); + } + return map; + } break; + case SASS_ERROR: { + return sass_make_error(val->error.message); + } break; + case SASS_WARNING: { + return sass_make_warning(val->warning.message); + } break; + } + + return 0; + + } + + union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* v, bool compressed, int precision) + { + Value_Obj val = sass_value_to_ast_node(v); + Sass_Inspect_Options options(compressed ? COMPRESSED : NESTED, precision); + std::string str(val->to_string(options)); + return sass_make_qstring(str.c_str()); + } + + union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) + { + + Sass::Value_Ptr rv = 0; + + try { + + Value_Obj lhs = sass_value_to_ast_node(a); + Value_Obj rhs = sass_value_to_ast_node(b); + struct Sass_Inspect_Options options(NESTED, 5); + + // see if it's a relational expression + switch(op) { + case Sass_OP::EQ: return sass_make_boolean(Eval::eq(&lhs, &rhs)); + case Sass_OP::NEQ: return sass_make_boolean(!Eval::eq(&lhs, &rhs)); + case Sass_OP::GT: return sass_make_boolean(!Eval::lt(&lhs, &rhs, "gt") && !Eval::eq(&lhs, &rhs)); + case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(&lhs, &rhs, "gte")); + case Sass_OP::LT: return sass_make_boolean(Eval::lt(&lhs, &rhs, "lt")); + case Sass_OP::LTE: return sass_make_boolean(Eval::lt(&lhs, &rhs, "lte") || Eval::eq(&lhs, &rhs)); + case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? &lhs : &rhs); + case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? &rhs : &lhs); + default: break; + } + + if (sass_value_is_number(a) && sass_value_is_number(b)) { + Number_Ptr_Const l_n = SASS_MEMORY_CAST(Number, lhs); + Number_Ptr_Const r_n = SASS_MEMORY_CAST(Number, rhs); + rv = Eval::op_numbers(op, *l_n, *r_n, options); + } + else if (sass_value_is_number(a) && sass_value_is_color(a)) { + Number_Ptr_Const l_n = SASS_MEMORY_CAST(Number, lhs); + Color_Ptr_Const r_c = SASS_MEMORY_CAST(Color, rhs); + rv = Eval::op_number_color(op, *l_n, *r_c, options); + } + else if (sass_value_is_color(a) && sass_value_is_number(b)) { + Color_Ptr_Const l_c = SASS_MEMORY_CAST(Color, lhs); + Number_Ptr_Const r_n = SASS_MEMORY_CAST(Number, rhs); + rv = Eval::op_color_number(op, *l_c, *r_n, options); + } + else if (sass_value_is_color(a) && sass_value_is_color(b)) { + Color_Ptr_Const l_c = SASS_MEMORY_CAST(Color, lhs); + Color_Ptr_Const r_c = SASS_MEMORY_CAST(Color, rhs); + rv = Eval::op_colors(op, *l_c, *r_c, options); + } + else /* convert other stuff to string and apply operation */ { + Value_Ptr l_v = SASS_MEMORY_CAST(Value, lhs); + Value_Ptr r_v = SASS_MEMORY_CAST(Value, rhs); + rv = Eval::op_strings(op, *l_v, *r_v, options); + } + + // ToDo: maybe we should should return null value? + if (!rv) return sass_make_error("invalid return value"); + + // convert result back to ast node + return ast_node_to_sass_value(rv); + + } + + // simply pass the error message back to the caller for now + catch (Exception::InvalidSass& e) { return sass_make_error(e.what()); } + catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } + catch (std::exception& e) { return sass_make_error(e.what()); } + catch (std::string& e) { return sass_make_error(e.c_str()); } + catch (const char* e) { return sass_make_error(e); } + catch (...) { return sass_make_error("unknown"); } + + return 0; + + } + +} diff --git a/src/libsass/src/sass_values.hpp b/src/libsass/src/sass_values.hpp new file mode 100755 index 000000000..b9e9ebfcc --- /dev/null +++ b/src/libsass/src/sass_values.hpp @@ -0,0 +1,81 @@ +#ifndef SASS_SASS_VALUES_H +#define SASS_SASS_VALUES_H + +#include "sass.h" + +struct Sass_Unknown { + enum Sass_Tag tag; +}; + +struct Sass_Boolean { + enum Sass_Tag tag; + bool value; +}; + +struct Sass_Number { + enum Sass_Tag tag; + double value; + char* unit; +}; + +struct Sass_Color { + enum Sass_Tag tag; + double r; + double g; + double b; + double a; +}; + +struct Sass_String { + enum Sass_Tag tag; + bool quoted; + char* value; +}; + +struct Sass_List { + enum Sass_Tag tag; + enum Sass_Separator separator; + size_t length; + // null terminated "array" + union Sass_Value** values; +}; + +struct Sass_Map { + enum Sass_Tag tag; + size_t length; + struct Sass_MapPair* pairs; +}; + +struct Sass_Null { + enum Sass_Tag tag; +}; + +struct Sass_Error { + enum Sass_Tag tag; + char* message; +}; + +struct Sass_Warning { + enum Sass_Tag tag; + char* message; +}; + +union Sass_Value { + struct Sass_Unknown unknown; + struct Sass_Boolean boolean; + struct Sass_Number number; + struct Sass_Color color; + struct Sass_String string; + struct Sass_List list; + struct Sass_Map map; + struct Sass_Null null; + struct Sass_Error error; + struct Sass_Warning warning; +}; + +struct Sass_MapPair { + union Sass_Value* key; + union Sass_Value* value; +}; + +#endif \ No newline at end of file diff --git a/src/libsass/src/source_map.cpp b/src/libsass/src/source_map.cpp new file mode 100755 index 000000000..0f496f6fb --- /dev/null +++ b/src/libsass/src/source_map.cpp @@ -0,0 +1,197 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "json.hpp" +#include "context.hpp" +#include "position.hpp" +#include "source_map.hpp" + +namespace Sass { + SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin") { } + SourceMap::SourceMap(const std::string& file) : current_position(0, 0, 0), file(file) { } + + std::string SourceMap::render_srcmap(Context &ctx) { + + const bool include_sources = ctx.c_options.source_map_contents; + const std::vector links = ctx.srcmap_links; + const std::vector& sources(ctx.resources); + + JsonNode* json_srcmap = json_mkobject(); + + json_append_member(json_srcmap, "version", json_mknumber(3)); + + const char *include = file.c_str(); + JsonNode *json_include = json_mkstring(include); + json_append_member(json_srcmap, "file", json_include); + + // pass-through sourceRoot option + if (!ctx.source_map_root.empty()) { + JsonNode* root = json_mkstring(ctx.source_map_root.c_str()); + json_append_member(json_srcmap, "sourceRoot", root); + } + + JsonNode *json_includes = json_mkarray(); + for (size_t i = 0; i < source_index.size(); ++i) { + std::string include(links[source_index[i]]); + if (ctx.c_options.source_map_file_urls) { + include = File::rel2abs(include); + // check for windows abs path + if (include[0] == '/') { + // ends up with three slashes + include = "file://" + include; + } else { + // needs an additional slash + include = "file:///" + include; + } + } + const char* inc = include.c_str(); + JsonNode *json_include = json_mkstring(inc); + json_append_element(json_includes, json_include); + } + json_append_member(json_srcmap, "sources", json_includes); + + if (include_sources) { + JsonNode *json_contents = json_mkarray(); + for (size_t i = 0; i < source_index.size(); ++i) { + const Resource& resource(sources[source_index[i]]); + JsonNode *json_content = json_mkstring(resource.contents); + json_append_element(json_contents, json_content); + } + if (json_contents->children.head) + json_append_member(json_srcmap, "sourcesContent", json_contents); + } + + JsonNode *json_names = json_mkarray(); + // so far we have no implementation for names + // no problem as we do not alter any identifiers + json_append_member(json_srcmap, "names", json_names); + + std::string mappings = serialize_mappings(); + JsonNode *json_mappings = json_mkstring(mappings.c_str()); + json_append_member(json_srcmap, "mappings", json_mappings); + + char *str = json_stringify(json_srcmap, "\t"); + std::string result = std::string(str); + free(str); + json_delete(json_srcmap); + return result; + } + + std::string SourceMap::serialize_mappings() { + std::string result = ""; + + size_t previous_generated_line = 0; + size_t previous_generated_column = 0; + size_t previous_original_line = 0; + size_t previous_original_column = 0; + size_t previous_original_file = 0; + for (size_t i = 0; i < mappings.size(); ++i) { + const size_t generated_line = mappings[i].generated_position.line; + const size_t generated_column = mappings[i].generated_position.column; + const size_t original_line = mappings[i].original_position.line; + const size_t original_column = mappings[i].original_position.column; + const size_t original_file = mappings[i].original_position.file; + + if (generated_line != previous_generated_line) { + previous_generated_column = 0; + if (generated_line > previous_generated_line) { + result += std::string(generated_line - previous_generated_line, ';'); + previous_generated_line = generated_line; + } + } + else if (i > 0) { + result += ","; + } + + // generated column + result += base64vlq.encode(static_cast(generated_column) - static_cast(previous_generated_column)); + previous_generated_column = generated_column; + // file + result += base64vlq.encode(static_cast(original_file) - static_cast(previous_original_file)); + previous_original_file = original_file; + // source line + result += base64vlq.encode(static_cast(original_line) - static_cast(previous_original_line)); + previous_original_line = original_line; + // source column + result += base64vlq.encode(static_cast(original_column) - static_cast(previous_original_column)); + previous_original_column = original_column; + } + + return result; + } + + void SourceMap::prepend(const OutputBuffer& out) + { + Offset size(out.smap.current_position); + for (Mapping mapping : out.smap.mappings) { + if (mapping.generated_position.line > size.line) { + throw(std::runtime_error("prepend sourcemap has illegal line")); + } + if (mapping.generated_position.line == size.line) { + if (mapping.generated_position.column > size.column) { + throw(std::runtime_error("prepend sourcemap has illegal column")); + } + } + } + // will adjust the offset + prepend(Offset(out.buffer)); + // now add the new mappings + VECTOR_UNSHIFT(mappings, out.smap.mappings); + } + + void SourceMap::append(const OutputBuffer& out) + { + append(Offset(out.buffer)); + } + + void SourceMap::prepend(const Offset& offset) + { + if (offset.line != 0 || offset.column != 0) { + for (Mapping& mapping : mappings) { + // move stuff on the first old line + if (mapping.generated_position.line == 0) { + mapping.generated_position.column += offset.column; + } + // make place for the new lines + mapping.generated_position.line += offset.line; + } + } + if (current_position.line == 0) { + current_position.column += offset.column; + } + current_position.line += offset.line; + } + + void SourceMap::append(const Offset& offset) + { + current_position += offset; + } + + void SourceMap::add_open_mapping(const AST_Node_Ptr node) + { + mappings.push_back(Mapping(node->pstate(), current_position)); + } + + void SourceMap::add_close_mapping(const AST_Node_Ptr node) + { + mappings.push_back(Mapping(node->pstate() + node->pstate().offset, current_position)); + } + + ParserState SourceMap::remap(const ParserState& pstate) { + for (size_t i = 0; i < mappings.size(); ++i) { + if ( + mappings[i].generated_position.file == pstate.file && + mappings[i].generated_position.line == pstate.line && + mappings[i].generated_position.column == pstate.column + ) return ParserState(pstate.path, pstate.src, mappings[i].original_position, pstate.offset); + } + return ParserState(pstate.path, pstate.src, Position(-1, -1, -1), Offset(0, 0)); + + } + +} diff --git a/src/libsass/src/source_map.hpp b/src/libsass/src/source_map.hpp new file mode 100755 index 000000000..07785640f --- /dev/null +++ b/src/libsass/src/source_map.hpp @@ -0,0 +1,62 @@ +#ifndef SASS_SOURCE_MAP_H +#define SASS_SOURCE_MAP_H + +#include +#include + +#include "ast_fwd_decl.hpp" +#include "base64vlq.hpp" +#include "position.hpp" +#include "mapping.hpp" + +#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end()) +#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end()) + +namespace Sass { + + class Context; + class OutputBuffer; + + class SourceMap { + + public: + std::vector source_index; + SourceMap(); + SourceMap(const std::string& file); + + void append(const Offset& offset); + void prepend(const Offset& offset); + void append(const OutputBuffer& out); + void prepend(const OutputBuffer& out); + void add_open_mapping(const AST_Node_Ptr node); + void add_close_mapping(const AST_Node_Ptr node); + + std::string render_srcmap(Context &ctx); + ParserState remap(const ParserState& pstate); + + private: + + std::string serialize_mappings(); + + std::vector mappings; + Position current_position; +public: + std::string file; +private: + Base64VLQ base64vlq; + }; + + class OutputBuffer { + public: + OutputBuffer(void) + : buffer(""), + smap() + { } + public: + std::string buffer; + SourceMap smap; + }; + +} + +#endif diff --git a/src/libsass/src/subset_map.cpp b/src/libsass/src/subset_map.cpp new file mode 100755 index 000000000..766073df8 --- /dev/null +++ b/src/libsass/src/subset_map.cpp @@ -0,0 +1,57 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "subset_map.hpp" + +namespace Sass { + + void Subset_Map::put(const Compound_Selector_Obj& sel, const Subset_Map_Val& value) + { + if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty"); + size_t index = values_.size(); + values_.push_back(value); + for (size_t i = 0, S = sel->length(); i < S; ++i) + { + hash_[(*sel)[i]].push_back(std::make_pair(&sel, index)); + } + } + + std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) + { + // std::vector s = sel->to_str_vec(); + // std::set dict(s.begin(), s.end()); + std::unordered_set dict(sel->begin(), sel->end()); + std::vector indices; + for (size_t i = 0, S = sel->length(); i < S; ++i) { + if (!hash_.count((*sel)[i])) { + continue; + } + const std::vector >& subsets = hash_[(*sel)[i]]; + for (const std::pair& item : subsets) { + bool include = true; + for (const Simple_Selector_Obj& it : item.first->elements()) { + auto found = dict.find(it); + if (found == dict.end()) { + include = false; + break; + } + } + if (include) indices.push_back(item.second); + } + } + sort(indices.begin(), indices.end()); + std::vector::iterator indices_end = unique(indices.begin(), indices.end()); + indices.resize(distance(indices.begin(), indices_end)); + + std::vector results; + for (size_t i = 0, S = indices.size(); i < S; ++i) { + results.push_back(values_[indices[i]]); + } + return results; + } + + std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) + { + return get_kv(sel); + } + +} \ No newline at end of file diff --git a/src/libsass/src/subset_map.hpp b/src/libsass/src/subset_map.hpp new file mode 100755 index 000000000..58916bccf --- /dev/null +++ b/src/libsass/src/subset_map.hpp @@ -0,0 +1,76 @@ +#ifndef SASS_SUBSET_MAP_H +#define SASS_SUBSET_MAP_H + +#include +#include +#include +#include +#include + +#include "ast_fwd_decl.hpp" + + +// #include +// #include +// template +// std::string vector_to_string(std::vector v) +// { +// std::stringstream buffer; +// buffer << "["; + +// if (!v.empty()) +// { buffer << v[0]; } +// else +// { buffer << "]"; } + +// if (v.size() == 1) +// { buffer << "]"; } +// else +// { +// for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; +// buffer << "]"; +// } + +// return buffer.str(); +// } + +// template +// std::string set_to_string(set v) +// { +// std::stringstream buffer; +// buffer << "["; +// typename std::set::iterator i = v.begin(); +// if (!v.empty()) +// { buffer << *i; } +// else +// { buffer << "]"; } + +// if (v.size() == 1) +// { buffer << "]"; } +// else +// { +// for (++i; i != v.end(); ++i) buffer << ", " << *i; +// buffer << "]"; +// } + +// return buffer.str(); +// } + +namespace Sass { + + class Subset_Map { + private: + std::vector values_; + std::map > > hash_; + public: + void put(const Compound_Selector_Obj& sel, const Subset_Map_Val& value); + std::vector get_kv(const Compound_Selector_Obj& s); + std::vector get_v(const Compound_Selector_Obj& s); + bool empty() { return values_.empty(); } + void clear() { values_.clear(); hash_.clear(); } + const std::vector values(void) { return values_; } + }; + +} + +#endif diff --git a/src/libsass/src/support/libsass.pc.in b/src/libsass/src/support/libsass.pc.in new file mode 100755 index 000000000..d201bfaaf --- /dev/null +++ b/src/libsass/src/support/libsass.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libsass +URL: https://github.com/sass/libsass +Description: A C implementation of a Sass compiler +Version: @VERSION@ +Libs: -L${libdir} -lsass +Cflags: -I${includedir} diff --git a/src/libsass/src/to_c.cpp b/src/libsass/src/to_c.cpp new file mode 100755 index 000000000..a39e3e76a --- /dev/null +++ b/src/libsass/src/to_c.cpp @@ -0,0 +1,74 @@ +#include "sass.hpp" +#include "to_c.hpp" +#include "ast.hpp" + +namespace Sass { + + union Sass_Value* To_C::fallback_impl(AST_Node_Ptr n) + { return sass_make_error("unknown type for C-API"); } + + union Sass_Value* To_C::operator()(Boolean_Ptr b) + { return sass_make_boolean(b->value()); } + + union Sass_Value* To_C::operator()(Number_Ptr n) + { return sass_make_number(n->value(), n->unit().c_str()); } + + union Sass_Value* To_C::operator()(Custom_Warning_Ptr w) + { return sass_make_warning(w->message().c_str()); } + + union Sass_Value* To_C::operator()(Custom_Error_Ptr e) + { return sass_make_error(e->message().c_str()); } + + union Sass_Value* To_C::operator()(Color_Ptr c) + { return sass_make_color(c->r(), c->g(), c->b(), c->a()); } + + union Sass_Value* To_C::operator()(String_Constant_Ptr s) + { + if (s->quote_mark()) { + return sass_make_qstring(s->value().c_str()); + } else { + return sass_make_string(s->value().c_str()); + } + } + + union Sass_Value* To_C::operator()(String_Quoted_Ptr s) + { return sass_make_qstring(s->value().c_str()); } + + union Sass_Value* To_C::operator()(List_Ptr l) + { + union Sass_Value* v = sass_make_list(l->length(), l->separator()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + sass_list_set_value(v, i, (*l)[i]->perform(this)); + } + return v; + } + + union Sass_Value* To_C::operator()(Map_Ptr m) + { + union Sass_Value* v = sass_make_map(m->length()); + int i = 0; + for (auto key : m->keys()) { + sass_map_set_key(v, i, key->perform(this)); + sass_map_set_value(v, i, m->at(key)->perform(this)); + i++; + } + return v; + } + + union Sass_Value* To_C::operator()(Arguments_Ptr a) + { + union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA); + for (size_t i = 0, L = a->length(); i < L; ++i) { + sass_list_set_value(v, i, (*a)[i]->perform(this)); + } + return v; + } + + union Sass_Value* To_C::operator()(Argument_Ptr a) + { return a->value()->perform(this); } + + // not strictly necessary because of the fallback + union Sass_Value* To_C::operator()(Null_Ptr n) + { return sass_make_null(); } + +}; diff --git a/src/libsass/src/to_c.hpp b/src/libsass/src/to_c.hpp new file mode 100755 index 000000000..a5331e3bf --- /dev/null +++ b/src/libsass/src/to_c.hpp @@ -0,0 +1,39 @@ +#ifndef SASS_TO_C_H +#define SASS_TO_C_H + +#include "ast_fwd_decl.hpp" +#include "operation.hpp" +#include "sass/values.h" + +namespace Sass { + + class To_C : public Operation_CRTP { + // override this to define a catch-all + union Sass_Value* fallback_impl(AST_Node_Ptr n); + + public: + + To_C() { } + ~To_C() { } + + union Sass_Value* operator()(Boolean_Ptr); + union Sass_Value* operator()(Number_Ptr); + union Sass_Value* operator()(Color_Ptr); + union Sass_Value* operator()(String_Constant_Ptr); + union Sass_Value* operator()(String_Quoted_Ptr); + union Sass_Value* operator()(Custom_Warning_Ptr); + union Sass_Value* operator()(Custom_Error_Ptr); + union Sass_Value* operator()(List_Ptr); + union Sass_Value* operator()(Map_Ptr); + union Sass_Value* operator()(Null_Ptr); + union Sass_Value* operator()(Arguments_Ptr); + union Sass_Value* operator()(Argument_Ptr); + + // dispatch to fallback implementation + union Sass_Value* fallback(AST_Node_Ptr x) + { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/libsass/src/to_value.cpp b/src/libsass/src/to_value.cpp new file mode 100755 index 000000000..b29e97044 --- /dev/null +++ b/src/libsass/src/to_value.cpp @@ -0,0 +1,107 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "to_value.hpp" + +namespace Sass { + + Value_Ptr To_Value::fallback_impl(AST_Node_Ptr n) + { + // throw a runtime error if this happens + // we want a well defined set of possible nodes + throw std::runtime_error("invalid node for to_value"); + // mute warning + return 0; + } + + // Custom_Error is a valid value + Value_Ptr To_Value::operator()(Custom_Error_Ptr e) + { + return e; + } + + // Custom_Warning is a valid value + Value_Ptr To_Value::operator()(Custom_Warning_Ptr w) + { + return w; + } + + // Boolean is a valid value + Value_Ptr To_Value::operator()(Boolean_Ptr b) + { + return b; + } + + // Number is a valid value + Value_Ptr To_Value::operator()(Number_Ptr n) + { + return n; + } + + // Color is a valid value + Value_Ptr To_Value::operator()(Color_Ptr c) + { + return c; + } + + // String_Constant is a valid value + Value_Ptr To_Value::operator()(String_Constant_Ptr s) + { + return s; + } + + // String_Quoted is a valid value + Value_Ptr To_Value::operator()(String_Quoted_Ptr s) + { + return s; + } + + // List is a valid value + Value_Ptr To_Value::operator()(List_Ptr l) + { + List_Obj ll = SASS_MEMORY_NEW(List, + l->pstate(), + l->length(), + l->separator(), + l->is_arglist()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + ll->append((*l)[i]->perform(this)); + } + return ll.detach(); + } + + // Map is a valid value + Value_Ptr To_Value::operator()(Map_Ptr m) + { + return m; + } + + // Null is a valid value + Value_Ptr To_Value::operator()(Null_Ptr n) + { + return n; + } + + // Argument returns its value + Value_Ptr To_Value::operator()(Argument_Ptr arg) + { + if (!arg->name().empty()) return 0; + return arg->value()->perform(this); + } + + // Selector_List is converted to a string + Value_Ptr To_Value::operator()(Selector_List_Ptr s) + { + return SASS_MEMORY_NEW(String_Quoted, + s->pstate(), + s->to_string(ctx.c_options)); + } + + // Binary_Expression is converted to a string + Value_Ptr To_Value::operator()(Binary_Expression_Ptr s) + { + return SASS_MEMORY_NEW(String_Quoted, + s->pstate(), + s->to_string(ctx.c_options)); + } + +}; diff --git a/src/libsass/src/to_value.hpp b/src/libsass/src/to_value.hpp new file mode 100755 index 000000000..e6c88dd69 --- /dev/null +++ b/src/libsass/src/to_value.hpp @@ -0,0 +1,49 @@ +#ifndef SASS_TO_VALUE_H +#define SASS_TO_VALUE_H + +#include "operation.hpp" +#include "sass/values.h" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + class To_Value : public Operation_CRTP { + + Value_Ptr fallback_impl(AST_Node_Ptr n); + + private: + + Context& ctx; + + public: + + To_Value(Context& ctx) + : ctx(ctx) + { } + ~To_Value() { } + using Operation::operator(); + + Value_Ptr operator()(Argument_Ptr); + Value_Ptr operator()(Boolean_Ptr); + Value_Ptr operator()(Number_Ptr); + Value_Ptr operator()(Color_Ptr); + Value_Ptr operator()(String_Constant_Ptr); + Value_Ptr operator()(String_Quoted_Ptr); + Value_Ptr operator()(Custom_Warning_Ptr); + Value_Ptr operator()(Custom_Error_Ptr); + Value_Ptr operator()(List_Ptr); + Value_Ptr operator()(Map_Ptr); + Value_Ptr operator()(Null_Ptr); + + // convert to string via `To_String` + Value_Ptr operator()(Selector_List_Ptr); + Value_Ptr operator()(Binary_Expression_Ptr); + + // fallback throws error + template + Value_Ptr fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/libsass/src/units.cpp b/src/libsass/src/units.cpp new file mode 100755 index 000000000..b3512dba6 --- /dev/null +++ b/src/libsass/src/units.cpp @@ -0,0 +1,197 @@ +#include "sass.hpp" +#include +#include "units.hpp" + +namespace Sass { + + /* the conversion matrix can be readed the following way */ + /* if you go down, the factor is for the numerator (multiply) */ + /* if you go right, the factor is for the denominator (divide) */ + /* and yes, we actually use both, not sure why, but why not!? */ + + const double size_conversion_factors[6][6] = + { + /* in cm pc mm pt px */ + /* in */ { 1, 2.54, 6, 25.4, 72, 96, }, + /* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54 }, + /* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0 }, + /* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4 }, + /* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0 }, + /* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, } + }; + + const double angle_conversion_factors[4][4] = + { + /* deg grad rad turn */ + /* deg */ { 1, 40.0/36.0, PI/180.0, 1.0/360.0 }, + /* grad */ { 36.0/40.0, 1, PI/200.0, 1.0/400.0 }, + /* rad */ { 180.0/PI, 200.0/PI, 1, 0.5/PI }, + /* turn */ { 360.0, 400.0, 2.0*PI, 1 } + }; + + const double time_conversion_factors[2][2] = + { + /* s ms */ + /* s */ { 1, 1000.0 }, + /* ms */ { 1/1000.0, 1 } + }; + const double frequency_conversion_factors[2][2] = + { + /* Hz kHz */ + /* Hz */ { 1, 1/1000.0 }, + /* kHz */ { 1000.0, 1 } + }; + const double resolution_conversion_factors[3][3] = + { + /* dpi dpcm dppx */ + /* dpi */ { 1, 1/2.54, 1/96.0 }, + /* dpcm */ { 2.54, 1, 2.54/96 }, + /* dppx */ { 96, 96/2.54, 1 } + }; + + UnitClass get_unit_type(UnitType unit) + { + switch (unit & 0xFF00) + { + case UnitClass::LENGTH: return UnitClass::LENGTH; break; + case UnitClass::ANGLE: return UnitClass::ANGLE; break; + case UnitClass::TIME: return UnitClass::TIME; break; + case UnitClass::FREQUENCY: return UnitClass::FREQUENCY; break; + case UnitClass::RESOLUTION: return UnitClass::RESOLUTION; break; + default: return UnitClass::INCOMMENSURABLE; break; + } + }; + + std::string get_unit_class(UnitType unit) + { + switch (unit & 0xFF00) + { + case UnitClass::LENGTH: return "LENGTH"; break; + case UnitClass::ANGLE: return "ANGLE"; break; + case UnitClass::TIME: return "TIME"; break; + case UnitClass::FREQUENCY: return "FREQUENCY"; break; + case UnitClass::RESOLUTION: return "RESOLUTION"; break; + default: return "INCOMMENSURABLE"; break; + } + }; + + UnitType string_to_unit(const std::string& s) + { + // size units + if (s == "px") return UnitType::PX; + else if (s == "pt") return UnitType::PT; + else if (s == "pc") return UnitType::PC; + else if (s == "mm") return UnitType::MM; + else if (s == "cm") return UnitType::CM; + else if (s == "in") return UnitType::IN; + // angle units + else if (s == "deg") return UnitType::DEG; + else if (s == "grad") return UnitType::GRAD; + else if (s == "rad") return UnitType::RAD; + else if (s == "turn") return UnitType::TURN; + // time units + else if (s == "s") return UnitType::SEC; + else if (s == "ms") return UnitType::MSEC; + // frequency units + else if (s == "Hz") return UnitType::HERTZ; + else if (s == "kHz") return UnitType::KHERTZ; + // resolutions units + else if (s == "dpi") return UnitType::DPI; + else if (s == "dpcm") return UnitType::DPCM; + else if (s == "dppx") return UnitType::DPPX; + // for unknown units + else return UnitType::UNKNOWN; + } + + const char* unit_to_string(UnitType unit) + { + switch (unit) { + // size units + case UnitType::PX: return "px"; break; + case UnitType::PT: return "pt"; break; + case UnitType::PC: return "pc"; break; + case UnitType::MM: return "mm"; break; + case UnitType::CM: return "cm"; break; + case UnitType::IN: return "in"; break; + // angle units + case UnitType::DEG: return "deg"; break; + case UnitType::GRAD: return "grad"; break; + case UnitType::RAD: return "rad"; break; + case UnitType::TURN: return "turn"; break; + // time units + case UnitType::SEC: return "s"; break; + case UnitType::MSEC: return "ms"; break; + // frequency units + case UnitType::HERTZ: return "Hz"; break; + case UnitType::KHERTZ: return "kHz"; break; + // resolutions units + case UnitType::DPI: return "dpi"; break; + case UnitType::DPCM: return "dpcm"; break; + case UnitType::DPPX: return "dppx"; break; + // for unknown units + default: return ""; break; + } + } + + std::string unit_to_class(const std::string& s) + { + if (s == "px") return "LENGTH"; + else if (s == "pt") return "LENGTH"; + else if (s == "pc") return "LENGTH"; + else if (s == "mm") return "LENGTH"; + else if (s == "cm") return "LENGTH"; + else if (s == "in") return "LENGTH"; + // angle units + else if (s == "deg") return "ANGLE"; + else if (s == "grad") return "ANGLE"; + else if (s == "rad") return "ANGLE"; + else if (s == "turn") return "ANGLE"; + // time units + else if (s == "s") return "TIME"; + else if (s == "ms") return "TIME"; + // frequency units + else if (s == "Hz") return "FREQUENCY"; + else if (s == "kHz") return "FREQUENCY"; + // resolutions units + else if (s == "dpi") return "RESOLUTION"; + else if (s == "dpcm") return "RESOLUTION"; + else if (s == "dppx") return "RESOLUTION"; + // for unknown units + return "CUSTOM:" + s; + } + + // throws incompatibleUnits exceptions + double conversion_factor(const std::string& s1, const std::string& s2, bool strict) + { + // assert for same units + if (s1 == s2) return 1; + // get unit enum from string + UnitType u1 = string_to_unit(s1); + UnitType u2 = string_to_unit(s2); + // query unit group types + UnitClass t1 = get_unit_type(u1); + UnitClass t2 = get_unit_type(u2); + // get absolute offset + // used for array acces + size_t i1 = u1 - t1; + size_t i2 = u2 - t2; + // error if units are not of the same group + // don't error for multiplication and division + if (strict && t1 != t2) throw incompatibleUnits(u1, u2); + // only process known units + if (u1 != UNKNOWN && u2 != UNKNOWN) { + switch (t1) { + case UnitClass::LENGTH: return size_conversion_factors[i1][i2]; break; + case UnitClass::ANGLE: return angle_conversion_factors[i1][i2]; break; + case UnitClass::TIME: return time_conversion_factors[i1][i2]; break; + case UnitClass::FREQUENCY: return frequency_conversion_factors[i1][i2]; break; + case UnitClass::RESOLUTION: return resolution_conversion_factors[i1][i2]; break; + // ToDo: should we throw error here? + case UnitClass::INCOMMENSURABLE: return 0; break; + } + } + // fallback + return 0; + } + +} diff --git a/src/libsass/src/units.hpp b/src/libsass/src/units.hpp new file mode 100755 index 000000000..eb29c693d --- /dev/null +++ b/src/libsass/src/units.hpp @@ -0,0 +1,92 @@ +#ifndef SASS_UNITS_H +#define SASS_UNITS_H + +#include +#include +#include + +namespace Sass { + + const double PI = std::acos(-1); + + enum UnitClass { + LENGTH = 0x000, + ANGLE = 0x100, + TIME = 0x200, + FREQUENCY = 0x300, + RESOLUTION = 0x400, + INCOMMENSURABLE = 0x500 + }; + + enum UnitType { + + // size units + IN = UnitClass::LENGTH, + CM, + PC, + MM, + PT, + PX, + + // angle units + DEG = ANGLE, + GRAD, + RAD, + TURN, + + // time units + SEC = TIME, + MSEC, + + // frequency units + HERTZ = FREQUENCY, + KHERTZ, + + // resolutions units + DPI = RESOLUTION, + DPCM, + DPPX, + + // for unknown units + UNKNOWN = INCOMMENSURABLE + + }; + + extern const double size_conversion_factors[6][6]; + extern const double angle_conversion_factors[4][4]; + extern const double time_conversion_factors[2][2]; + extern const double frequency_conversion_factors[2][2]; + extern const double resolution_conversion_factors[3][3]; + + enum Sass::UnitType string_to_unit(const std::string&); + const char* unit_to_string(Sass::UnitType unit); + enum Sass::UnitClass get_unit_type(Sass::UnitType unit); + std::string get_unit_class(Sass::UnitType unit); + std::string unit_to_class(const std::string&); + // throws incompatibleUnits exceptions + double conversion_factor(const std::string&, const std::string&, bool = true); + + class incompatibleUnits: public std::exception + { + public: + const char* msg; + incompatibleUnits(Sass::UnitType a, Sass::UnitType b) + : exception() + { + std::stringstream ss; + ss << "Incompatible units: "; + ss << "'" << unit_to_string(a) << "' and "; + ss << "'" << unit_to_string(b) << "'"; + // hold on to string on stack! + std::string str(ss.str()); + msg = str.c_str(); + } + virtual const char* what() const throw() + { + return msg; + } + }; + +} + +#endif diff --git a/src/libsass/src/utf8.h b/src/libsass/src/utf8.h new file mode 100755 index 000000000..82b13f59f --- /dev/null +++ b/src/libsass/src/utf8.h @@ -0,0 +1,34 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "utf8/checked.h" +#include "utf8/unchecked.h" + +#endif // header guard diff --git a/src/libsass/src/utf8/checked.h b/src/libsass/src/utf8/checked.h new file mode 100755 index 000000000..133115513 --- /dev/null +++ b/src/libsass/src/utf8/checked.h @@ -0,0 +1,327 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t cp) : cp(cp) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start != end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start != end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end) : + it(octet_it), range_start(range_start), range_end(range_end) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/src/libsass/src/utf8/core.h b/src/libsass/src/utf8/core.h new file mode 100755 index 000000000..f85081f8f --- /dev/null +++ b/src/libsass/src/utf8/core.h @@ -0,0 +1,329 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/src/libsass/src/utf8/unchecked.h b/src/libsass/src/utf8/unchecked.h new file mode 100755 index 000000000..989ccefa7 --- /dev/null +++ b/src/libsass/src/utf8/unchecked.h @@ -0,0 +1,228 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" + +namespace utf8 +{ + namespace unchecked + { + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + uint32_t next(octet_iterator& it) + { + uint32_t cp = utf8::internal::mask8(*it); + typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); + switch (length) { + case 1: + break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } + + template + uint32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } + + template + uint32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + + // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) + template + inline uint32_t previous(octet_iterator& it) + { + return utf8::unchecked::prior(it); + } + + template + void advance (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::next(it); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it): it(octet_it) {} + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + + } // namespace utf8::unchecked +} // namespace utf8 + + +#endif // header guard + diff --git a/src/libsass/src/utf8_string.cpp b/src/libsass/src/utf8_string.cpp new file mode 100755 index 000000000..ba4d7a25f --- /dev/null +++ b/src/libsass/src/utf8_string.cpp @@ -0,0 +1,102 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "utf8.h" + +namespace Sass { + namespace UTF_8 { + using std::string; + + // naming conventions: + // offset: raw byte offset (0 based) + // position: code point offset (0 based) + // index: code point offset (1 based or negative) + + // function that will count the number of code points (utf-8 characters) from the given beginning to the given end + size_t code_point_count(const string& str, size_t start, size_t end) { + return utf8::distance(str.begin() + start, str.begin() + end); + } + + size_t code_point_count(const string& str) { + return utf8::distance(str.begin(), str.end()); + } + + // function that will return the byte offset at a code point position + size_t offset_at_position(const string& str, size_t position) { + string::const_iterator it = str.begin(); + utf8::advance(it, position, str.end()); + return distance(str.begin(), it); + } + + // function that returns number of bytes in a character at offset + size_t code_point_size_at_offset(const string& str, size_t offset) { + // get iterator from string and forward by offset + string::const_iterator stop = str.begin() + offset; + // check if beyond boundary + if (stop == str.end()) return 0; + // advance by one code point + utf8::advance(stop, 1, str.end()); + // calculate offset for code point + return stop - str.begin() - offset; + } + + // function that will return a normalized index, given a crazy one + size_t normalize_index(int index, size_t len) { + long signed_len = static_cast(len); + // assuming the index is 1-based + // we are returning a 0-based index + if (index > 0 && index <= signed_len) { + // positive and within string length + return index-1; + } + else if (index > signed_len) { + // positive and past string length + return len; + } + else if (index == 0) { + return 0; + } + else if (std::abs((double)index) <= signed_len) { + // negative and within string length + return index + signed_len; + } + else { + // negative and past string length + return 0; + } + } + + #ifdef _WIN32 + + // utf16 functions + using std::wstring; + + // convert from utf16/wide string to utf8 string + string convert_from_utf16(const wstring& utf16) + { + string utf8; + // pre-allocate expected memory + utf8.reserve(sizeof(utf16)/2); + utf8::utf16to8(utf16.begin(), utf16.end(), + back_inserter(utf8)); + return utf8; + } + + // convert from utf8 string to utf16/wide string + wstring convert_to_utf16(const string& utf8) + { + wstring utf16; + // pre-allocate expected memory + utf16.reserve(code_point_count(utf8)*2); + utf8::utf8to16(utf8.begin(), utf8.end(), + back_inserter(utf16)); + return utf16; + } + + #endif + + } +} diff --git a/src/libsass/src/utf8_string.hpp b/src/libsass/src/utf8_string.hpp new file mode 100755 index 000000000..5e879bec3 --- /dev/null +++ b/src/libsass/src/utf8_string.hpp @@ -0,0 +1,37 @@ +#ifndef SASS_UTF8_STRING_H +#define SASS_UTF8_STRING_H + +#include +#include "utf8.h" + +namespace Sass { + namespace UTF_8 { + + // naming conventions: + // offset: raw byte offset (0 based) + // position: code point offset (0 based) + // index: code point offset (1 based or negative) + + // function that will count the number of code points (utf-8 characters) from the beginning to the given end + size_t code_point_count(const std::string& str, size_t start, size_t end); + size_t code_point_count(const std::string& str); + + // function that will return the byte offset of a code point in a + size_t offset_at_position(const std::string& str, size_t position); + + // function that returns number of bytes in a character in a string + size_t code_point_size_at_offset(const std::string& str, size_t offset); + + // function that will return a normalized index, given a crazy one + size_t normalize_index(int index, size_t len); + + #ifdef _WIN32 + // functions to handle unicode paths on windows + std::string convert_from_utf16(const std::wstring& wstr); + std::wstring convert_to_utf16(const std::string& str); + #endif + + } +} + +#endif diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp new file mode 100755 index 000000000..5e598131e --- /dev/null +++ b/src/libsass/src/util.cpp @@ -0,0 +1,639 @@ +#include "sass.hpp" +#include "sass.h" +#include "ast.hpp" +#include "util.hpp" +#include "lexer.hpp" +#include "prelexer.hpp" +#include "constants.hpp" +#include "utf8/checked.h" + +#include +#include + +namespace Sass { + + double round(double val, size_t precision) + { + // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 + if (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); + else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); + // work around some compiler issue + // cygwin has it not defined in std + using namespace std; + return ::round(val); + } + + /* Locale unspecific atof function. */ + double sass_atof(const char *str) + { + char separator = *(localeconv()->decimal_point); + if(separator != '.'){ + // The current locale specifies another + // separator. convert the separator to the + // one understood by the locale if needed + const char *found = strchr(str, '.'); + if(found != NULL){ + // substitution is required. perform the substitution on a copy + // of the string. This is slower but it is thread safe. + char *copy = sass_copy_c_string(str); + *(copy + (found - str)) = separator; + double res = atof(copy); + free(copy); + return res; + } + } + + return atof(str); + } + + // helper for safe access to c_ctx + const char* safe_str (const char* str, const char* alt) { + return str == NULL ? alt : str; + } + + void free_string_array(char ** arr) { + if(!arr) + return; + + char **it = arr; + while (it && (*it)) { + free(*it); + ++it; + } + + free(arr); + } + + char **copy_strings(const std::vector& strings, char*** array, int skip) { + int num = static_cast(strings.size()) - skip; + char** arr = (char**) calloc(num + 1, sizeof(char*)); + if (arr == 0) + return *array = (char **)NULL; + + for(int i = 0; i < num; i++) { + arr[i] = (char*) malloc(sizeof(char) * (strings[i + skip].size() + 1)); + if (arr[i] == 0) { + free_string_array(arr); + return *array = (char **)NULL; + } + std::copy(strings[i + skip].begin(), strings[i + skip].end(), arr[i]); + arr[i][strings[i + skip].size()] = '\0'; + } + + arr[num] = 0; + return *array = arr; + } + + // read css string (handle multiline DELIM) + std::string read_css_string(const std::string& str) + { + std::string out(""); + bool esc = false; + for (auto i : str) { + if (i == '\\') { + esc = ! esc; + } else if (esc && i == '\r') { + continue; + } else if (esc && i == '\n') { + out.resize (out.size () - 1); + esc = false; + continue; + } else { + esc = false; + } + out.push_back(i); + } + // happens when parsing does not correctly skip + // over escaped sequences for ie. interpolations + // one example: foo\#{interpolate} + // if (esc) out += '\\'; + return out; + } + + // double escape all escape sequences + // keep unescaped quotes and backslashes + std::string evacuate_escapes(const std::string& str) + { + std::string out(""); + bool esc = false; + for (auto i : str) { + if (i == '\\' && !esc) { + out += '\\'; + out += '\\'; + esc = true; + } else if (esc && i == '"') { + out += '\\'; + out += i; + esc = false; + } else if (esc && i == '\'') { + out += '\\'; + out += i; + esc = false; + } else if (esc && i == '\\') { + out += '\\'; + out += i; + esc = false; + } else { + esc = false; + out += i; + } + } + // happens when parsing does not correctly skip + // over escaped sequences for ie. interpolations + // one example: foo\#{interpolate} + // if (esc) out += '\\'; + return out; + } + + // bell characters are replaced with spaces + void newline_to_space(std::string& str) + { + std::replace(str.begin(), str.end(), '\n', ' '); + } + + // bell characters are replaced with spaces + // also eats spaces after line-feeds (ltrim) + std::string string_to_output(const std::string& str) + { + std::string out(""); + bool lf = false; + for (auto i : str) { + if (i == '\n') { + out += ' '; + lf = true; + } else if (!(lf && isspace(i))) { + out += i; + lf = false; + } + } + return out; + } + + std::string comment_to_string(const std::string& text) + { + std::string str = ""; + size_t has = 0; + char prev = 0; + bool clean = false; + for (auto i : text) { + if (clean) { + if (i == '\n') { has = 0; } + else if (i == '\r') { has = 0; } + else if (i == '\t') { ++ has; } + else if (i == ' ') { ++ has; } + else if (i == '*') {} + else { + clean = false; + str += ' '; + if (prev == '*' && i == '/') str += "*/"; + else str += i; + } + } else if (i == '\n') { + clean = true; + } else if (i == '\r') { + clean = true; + } else { + str += i; + } + prev = i; + } + if (has) return str; + else return text; + } + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string cotains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + char detect_best_quotemark(const char* s, char qm) + { + // ensure valid fallback quote_mark + char quote_mark = qm && qm != '*' ? qm : '"'; + while (*s) { + // force double quotes as soon + // as one single quote is found + if (*s == '\'') { return '"'; } + // a single does not force quote_mark + // maybe we see a double quote later + else if (*s == '"') { quote_mark = '\''; } + ++ s; + } + return quote_mark; + } + + std::string unquote(const std::string& s, char* qd, bool keep_utf8_sequences, bool strict) + { + + // not enough room for quotes + // no possibility to unquote + if (s.length() < 2) return s; + + char q; + bool skipped = false; + + // this is no guarantee that the unquoting will work + // what about whitespace before/after the quote_mark? + if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; + else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; + else return s; + + std::string unq; + unq.reserve(s.length()-2); + + for (size_t i = 1, L = s.length() - 1; i < L; ++i) { + + // implement the same strange ruby sass behavior + // an escape sequence can also mean a unicode char + if (s[i] == '\\' && !skipped) { + // remember + skipped = true; + + // skip it + // ++ i; + + // if (i == L) break; + + // escape length + size_t len = 1; + + // parse as many sequence chars as possible + // ToDo: Check if ruby aborts after possible max + while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + + // hex string? + if (keep_utf8_sequences) { + unq.push_back(s[i]); + } else if (len > 1) { + + // convert the extracted hex string to code point value + // ToDo: Maybe we could do this without creating a substring + uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); + + if (s[i + len] == ' ') ++ len; + + // assert invalid code points + if (cp == 0) cp = 0xFFFD; + // replace bell character + // if (cp == '\n') cp = 32; + + // use a very simple approach to convert via utf8 lib + // maybe there is a more elegant way; maybe we shoud + // convert the whole output from string to a stream!? + // allocate memory for utf8 char and convert to utf8 + unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); + for(size_t m = 0; u[m] && m < 5; m++) unq.push_back(u[m]); + + // skip some more chars? + i += len - 1; skipped = false; + + } + + + } + // check for unexpected delimiter + // be strict and throw error back + // else if (!skipped && q == s[i]) { + // // don't be that strict + // return s; + // // this basically always means an internal error and not users fault + // error("Unescaped delimiter in string to unquote found. [" + s + "]", ParserState("[UNQUOTE]")); + // } + else { + if (strict && !skipped) { + if (s[i] == q) return s; + } + skipped = false; + unq.push_back(s[i]); + } + + } + if (skipped) { return s; } + if (qd) *qd = q; + return unq; + + } + + std::string quote(const std::string& s, char q) + { + + // autodetect with fallback to given quote + q = detect_best_quotemark(s.c_str(), q); + + // return an empty quoted string + if (s.empty()) return std::string(2, q ? q : '"'); + + std::string quoted; + quoted.reserve(s.length()+2); + quoted.push_back(q); + + const char* it = s.c_str(); + const char* end = it + strlen(it) + 1; + while (*it && it < end) { + const char* now = it; + + if (*it == q) { + quoted.push_back('\\'); + } else if (*it == '\\') { + quoted.push_back('\\'); + } + + int cp = utf8::next(it, end); + + // in case of \r, check if the next in sequence + // is \n and then advance the iterator and skip \r + if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { + cp = utf8::next(it, end); + } + + if (cp == '\n') { + quoted.push_back('\\'); + quoted.push_back('a'); + // we hope we can remove this flag once we figure out + // why ruby sass has these different output behaviors + // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") + using namespace Prelexer; + if (alternatives < + Prelexer::char_range<'a', 'f'>, + Prelexer::char_range<'A', 'F'>, + Prelexer::char_range<'0', '9'>, + space + >(it) != NULL) { + quoted.push_back(' '); + } + } else if (cp < 127) { + quoted.push_back((char) cp); + } else { + while (now < it) { + quoted.push_back(*now); + ++ now; + } + } + } + + quoted.push_back(q); + return quoted; + } + + bool is_hex_doublet(double n) + { + return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || + n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || + n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || + n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; + } + + bool is_color_doublet(double r, double g, double b) + { + return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); + } + + bool peek_linefeed(const char* start) + { + using namespace Prelexer; + using namespace Constants; + return sequence < + zero_plus < + alternatives < + exactly <' '>, + exactly <'\t'>, + line_comment, + block_comment, + delimited_by < + slash_star, + star_slash, + false + > + > + >, + re_linebreak + >(start) != 0; + } + + namespace Util { + using std::string; + + std::string rtrim(const std::string &str) { + std::string trimmed = str; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) + { trimmed.erase(pos_ws + 1); } + else { trimmed.clear(); } + return trimmed; + } + + std::string normalize_underscores(const std::string& str) { + std::string normalized = str; + for(size_t i = 0, L = normalized.length(); i < L; ++i) { + if(normalized[i] == '_') { + normalized[i] = '-'; + } + } + return normalized; + } + + std::string normalize_decimals(const std::string& str) { + std::string prefix = "0"; + std::string normalized = str; + + return normalized[0] == '.' ? normalized.insert(0, prefix) : normalized; + } + + bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style) { + if (r == NULL) { + return false; + } + + Block_Obj b = r->block(); + + Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, r->selector()); + bool hasSelectors = sl ? sl->length() > 0 : false; + + if (!hasSelectors) { + return false; + } + + bool hasDeclarations = false; + bool hasPrintableChildBlocks = false; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (dynamic_cast(&stm)) { + return true; + } else if (Declaration_Ptr d = dynamic_cast(&stm)) { + return isPrintable(d, style); + } else if (dynamic_cast(&stm)) { + Block_Obj pChildBlock = ((Has_Block_Ptr)&stm)->block(); + if (isPrintable(&pChildBlock, style)) { + hasPrintableChildBlocks = true; + } + } else if (Comment_Ptr c = dynamic_cast(&stm)) { + // keep for uncompressed + if (style != COMPRESSED) { + hasDeclarations = true; + } + // output style compressed + if (c->is_important()) { + hasDeclarations = c->is_important(); + } + } else { + hasDeclarations = true; + } + + if (hasDeclarations || hasPrintableChildBlocks) { + return true; + } + } + + return false; + } + + bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style) + { + return ! s->value().empty(); + } + + bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style) + { + return true; + } + + bool isPrintable(Declaration_Ptr d, Sass_Output_Style style) + { + Expression_Obj val = d->value(); + if (String_Quoted_Obj sq = SASS_MEMORY_CAST(String_Quoted, val)) return isPrintable(&sq, style); + if (String_Constant_Obj sc = SASS_MEMORY_CAST(String_Constant, val)) return isPrintable(&sc, style); + return true; + } + + bool isPrintable(Supports_Block_Ptr f, Sass_Output_Style style) { + if (f == NULL) { + return false; + } + + Block_Obj b = f->block(); + +// bool hasSelectors = f->selector() && static_cast(f->selector())->length() > 0; + + bool hasDeclarations = false; + bool hasPrintableChildBlocks = false; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (dynamic_cast(&stm) || dynamic_cast(&stm)) { + hasDeclarations = true; + } + else if (dynamic_cast(&stm)) { + Block_Obj pChildBlock = ((Has_Block_Ptr)&stm)->block(); + if (isPrintable(&pChildBlock, style)) { + hasPrintableChildBlocks = true; + } + } + + if (hasDeclarations || hasPrintableChildBlocks) { + return true; + } + } + + return false; + } + + bool isPrintable(Media_Block_Ptr m, Sass_Output_Style style) + { + if (m == 0) return false; + Block_Obj b = m->block(); + if (b == 0) return false; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (dynamic_cast(&stm)) return true; + else if (dynamic_cast(&stm)) return true; + else if (dynamic_cast(&stm)) { + Comment_Ptr c = (Comment_Ptr) &stm; + if (isPrintable(c, style)) { + return true; + } + } + else if (dynamic_cast(&stm)) { + Ruleset_Ptr r = (Ruleset_Ptr) &stm; + if (isPrintable(r, style)) { + return true; + } + } + else if (dynamic_cast(&stm)) { + Supports_Block_Ptr f = (Supports_Block_Ptr) &stm; + if (isPrintable(f, style)) { + return true; + } + } + else if (dynamic_cast(&stm)) { + Media_Block_Ptr m = (Media_Block_Ptr) &stm; + if (isPrintable(m, style)) { + return true; + } + } + else if (dynamic_cast(&stm) && isPrintable(((Has_Block_Ptr)&stm)->block(), style)) { + return true; + } + } + return false; + } + + bool isPrintable(Comment_Ptr c, Sass_Output_Style style) + { + // keep for uncompressed + if (style != COMPRESSED) { + return true; + } + // output style compressed + if (c->is_important()) { + return true; + } + // not printable + return false; + }; + + bool isPrintable(Block_Obj b, Sass_Output_Style style) { + if (!b) { + return false; + } + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (dynamic_cast(&stm) || dynamic_cast(&stm)) { + return true; + } + else if (dynamic_cast(&stm)) { + Comment_Ptr c = (Comment_Ptr) &stm; + if (isPrintable(c, style)) { + return true; + } + } + else if (dynamic_cast(&stm)) { + Ruleset_Ptr r = (Ruleset_Ptr) &stm; + if (isPrintable(r, style)) { + return true; + } + } + else if (dynamic_cast(&stm)) { + Supports_Block_Ptr f = (Supports_Block_Ptr) &stm; + if (isPrintable(f, style)) { + return true; + } + } + else if (dynamic_cast(&stm)) { + Media_Block_Ptr m = (Media_Block_Ptr) &stm; + if (isPrintable(m, style)) { + return true; + } + } + else if (dynamic_cast(&stm) && isPrintable(((Has_Block_Ptr)&stm)->block(), style)) { + return true; + } + } + + return false; + } + + bool isAscii(const char chr) { + return unsigned(chr) < 128; + } + + } +} diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp new file mode 100755 index 000000000..ceed7d725 --- /dev/null +++ b/src/libsass/src/util.hpp @@ -0,0 +1,59 @@ +#ifndef SASS_UTIL_H +#define SASS_UTIL_H + +#include +#include +#include +#include "sass.hpp" +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#define SASS_ASSERT(cond, msg) assert(cond && msg) + +namespace Sass { + + #define out_of_memory() do { \ + std::cerr << "Out of memory.\n"; \ + exit(EXIT_FAILURE); \ + } while (0) + + double round(double val, size_t precision = 0); + double sass_atof(const char* str); + const char* safe_str(const char *, const char* = ""); + void free_string_array(char **); + char **copy_strings(const std::vector&, char ***, int = 0); + std::string read_css_string(const std::string& str); + std::string evacuate_escapes(const std::string& str); + std::string string_to_output(const std::string& str); + std::string comment_to_string(const std::string& text); + void newline_to_space(std::string& str); + + std::string quote(const std::string&, char q = 0); + std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); + char detect_best_quotemark(const char* s, char qm = '"'); + + bool is_hex_doublet(double n); + bool is_color_doublet(double r, double g, double b); + + bool peek_linefeed(const char* start); + + namespace Util { + + std::string rtrim(const std::string& str); + + std::string normalize_underscores(const std::string& str); + std::string normalize_decimals(const std::string& str); + + bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); + bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); + bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); + bool isAscii(const char chr); + + } +} +#endif diff --git a/src/libsass/src/values.cpp b/src/libsass/src/values.cpp new file mode 100755 index 000000000..4d16b614f --- /dev/null +++ b/src/libsass/src/values.cpp @@ -0,0 +1,139 @@ +#include "sass.hpp" +#include "sass.h" +#include "values.hpp" + +#include + +namespace Sass { + + // convert value from C++ side to C-API + union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val) + { + if (val->concrete_type() == Expression::NUMBER) + { + Number_Ptr_Const res = dynamic_cast(val); + return sass_make_number(res->value(), res->unit().c_str()); + } + else if (val->concrete_type() == Expression::COLOR) + { + Color_Ptr_Const col = dynamic_cast(val); + return sass_make_color(col->r(), col->g(), col->b(), col->a()); + } + else if (val->concrete_type() == Expression::LIST) + { + List_Ptr_Const l = dynamic_cast(val); + union Sass_Value* list = sass_make_list(l->size(), l->separator()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + Expression_Obj obj = l->at(i); + auto val = ast_node_to_sass_value(&obj); + sass_list_set_value(list, i, val); + } + return list; + } + else if (val->concrete_type() == Expression::MAP) + { + Map_Ptr_Const m = dynamic_cast(val); + union Sass_Value* map = sass_make_map(m->length()); + size_t i = 0; for (Expression_Obj key : m->keys()) { + sass_map_set_key(map, i, ast_node_to_sass_value(&key)); + sass_map_set_value(map, i, ast_node_to_sass_value(&m->at(key))); + ++ i; + } + return map; + } + else if (val->concrete_type() == Expression::NULL_VAL) + { + return sass_make_null(); + } + else if (val->concrete_type() == Expression::BOOLEAN) + { + Boolean_Ptr_Const res = dynamic_cast(val); + return sass_make_boolean(res->value()); + } + else if (val->concrete_type() == Expression::STRING) + { + if (String_Quoted_Ptr_Const qstr = dynamic_cast(val)) + { + return sass_make_qstring(qstr->value().c_str()); + } + else if (String_Constant_Ptr_Const cstr = dynamic_cast(val)) + { + return sass_make_string(cstr->value().c_str()); + } + } + return sass_make_error("unknown sass value type"); + } + + // convert value from C-API to C++ side + Value_Ptr sass_value_to_ast_node (const union Sass_Value* val) + { + switch (sass_value_get_tag(val)) { + case SASS_NUMBER: + return SASS_MEMORY_NEW(Number, + ParserState("[C-VALUE]"), + sass_number_get_value(val), + sass_number_get_unit(val)); + break; + case SASS_BOOLEAN: + return SASS_MEMORY_NEW(Boolean, + ParserState("[C-VALUE]"), + sass_boolean_get_value(val)); + break; + case SASS_COLOR: + return SASS_MEMORY_NEW(Color, + ParserState("[C-VALUE]"), + sass_color_get_r(val), + sass_color_get_g(val), + sass_color_get_b(val), + sass_color_get_a(val)); + break; + case SASS_STRING: + if (sass_string_is_quoted(val)) { + return SASS_MEMORY_NEW(String_Quoted, + ParserState("[C-VALUE]"), + sass_string_get_value(val)); + } else { + return SASS_MEMORY_NEW(String_Constant, + ParserState("[C-VALUE]"), + sass_string_get_value(val)); + } + break; + case SASS_LIST: { + List_Ptr l = SASS_MEMORY_NEW(List, + ParserState("[C-VALUE]"), + sass_list_get_length(val), + sass_list_get_separator(val)); + for (size_t i = 0, L = sass_list_get_length(val); i < L; ++i) { + l->append(sass_value_to_ast_node(sass_list_get_value(val, i))); + } + return l; + } + break; + case SASS_MAP: { + Map_Ptr m = SASS_MEMORY_NEW(Map, ParserState("[C-VALUE]")); + for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) { + *m << std::make_pair( + sass_value_to_ast_node(sass_map_get_key(val, i)), + sass_value_to_ast_node(sass_map_get_value(val, i))); + } + return m; + } + break; + case SASS_NULL: + return SASS_MEMORY_NEW(Null, ParserState("[C-VALUE]")); + break; + case SASS_ERROR: + return SASS_MEMORY_NEW(Custom_Error, + ParserState("[C-VALUE]"), + sass_error_get_message(val)); + break; + case SASS_WARNING: + return SASS_MEMORY_NEW(Custom_Warning, + ParserState("[C-VALUE]"), + sass_warning_get_message(val)); + break; + } + return 0; + } + +} diff --git a/src/libsass/src/values.hpp b/src/libsass/src/values.hpp new file mode 100755 index 000000000..f78ca1281 --- /dev/null +++ b/src/libsass/src/values.hpp @@ -0,0 +1,12 @@ +#ifndef SASS_VALUES_H +#define SASS_VALUES_H + +#include "ast.hpp" + +namespace Sass { + + union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val); + Value_Ptr sass_value_to_ast_node (const union Sass_Value* val); + +} +#endif diff --git a/src/libsass/test/test_node.cpp b/src/libsass/test/test_node.cpp new file mode 100755 index 000000000..905dc1899 --- /dev/null +++ b/src/libsass/test/test_node.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include "node.hpp" +#include "parser.hpp" + + +#define STATIC_ARRAY_SIZE(array) (sizeof((array))/sizeof((array[0]))) + + +namespace Sass { + + Context ctx = Context::Data(); + + const char* const ROUNDTRIP_TESTS[] = { + NULL, + "~", + "CMPD", + "~ CMPD", + "CMPD >", + "> > CMPD", + "CMPD ~ ~", + "> + CMPD1.CMPD2 > ~", + "> + CMPD1.CMPD2 CMPD3.CMPD4 > ~", + "+ CMPD1 CMPD2 ~ CMPD3 + CMPD4 > CMPD5 > ~" + }; + + + + static Complex_Selector* createComplexSelector(std::string src) { + std::string temp(src); + temp += ";"; + return (*Parser::from_c_str(temp.c_str(), ctx, "", Position()).parse_selector_list())[0]; + } + + + void roundtripTest(const char* toTest) { + + // Create the initial selector + + Complex_Selector* pOrigSelector = NULL; + if (toTest) { + pOrigSelector = createComplexSelector(toTest); + } + + std::string expected(pOrigSelector ? pOrigSelector->to_string() : "NULL"); + + + // Roundtrip the selector into a node and back + + Node node = complexSelectorToNode(pOrigSelector, ctx); + + std::stringstream nodeStringStream; + nodeStringStream << node; + std::string nodeString = nodeStringStream.str(); + cout << "ASNODE: " << node << endl; + + Complex_Selector* pNewSelector = nodeToComplexSelector(node, ctx); + + // Show the result + + std::string result(pNewSelector ? pNewSelector->to_string() : "NULL"); + + cout << "SELECTOR: " << expected << endl; + cout << "NEW SELECTOR: " << result << endl; + + + // Test that they are equal using the equality operator + + assert( (!pOrigSelector && !pNewSelector ) || (pOrigSelector && pNewSelector) ); + if (pOrigSelector) { + assert( *pOrigSelector == *pNewSelector ); + } + + + // Test that they are equal by comparing the string versions of the selectors + + assert(expected == result); + + } + + + int main() { + for (int index = 0; index < STATIC_ARRAY_SIZE(ROUNDTRIP_TESTS); index++) { + const char* const toTest = ROUNDTRIP_TESTS[index]; + cout << "\nINPUT STRING: " << (toTest ? toTest : "NULL") << endl; + roundtripTest(toTest); + } + + cout << "\nTesting Done.\n"; + } + + +} diff --git a/src/libsass/test/test_paths.cpp b/src/libsass/test/test_paths.cpp new file mode 100755 index 000000000..bfcf8ec6d --- /dev/null +++ b/src/libsass/test/test_paths.cpp @@ -0,0 +1,28 @@ +#include +#include "../paths.hpp" + +using namespace Sass; + +template +std::vector& operator<<(std::vector& v, const T& e) +{ + v.push_back(e); + return v; +} + +int main() +{ + std::vector v1, v2, v3; + v1 << 1 << 2; + v2 << 3; + v3 << 4 << 5 << 6; + + std::vector > ss; + ss << v1 << v2 << v3; + + std::vector > ps = paths(ss); + for (size_t i = 0, S = ps.size(); i < S; ++i) { + std::cout << vector_to_string(ps[i]) << std::endl; + } + return 0; +} diff --git a/src/libsass/test/test_selector_difference.cpp b/src/libsass/test/test_selector_difference.cpp new file mode 100755 index 000000000..e2880c0b0 --- /dev/null +++ b/src/libsass/test/test_selector_difference.cpp @@ -0,0 +1,25 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include +#include + +using namespace Sass; + +Context ctx = Context::Data(); + +Compound_Selector* selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } + +void diff(std::string s, std::string t) +{ + std::cout << s << " - " << t << " = " << selector(s + ";")->minus(selector(t + ";"), ctx)->to_string() << std::endl; +} + +int main() +{ + diff(".a.b.c", ".c.b"); + diff(".a.b.c", ".fludge.b"); + + return 0; +} diff --git a/src/libsass/test/test_specificity.cpp b/src/libsass/test/test_specificity.cpp new file mode 100755 index 000000000..ba9bbfc46 --- /dev/null +++ b/src/libsass/test/test_specificity.cpp @@ -0,0 +1,25 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include +#include + +using namespace Sass; + +Context ctx = Context::Data(); + +Selector* selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_selector_list(); } + +void spec(std::string sel) +{ std::cout << sel << "\t::\t" << selector(sel + ";")->specificity() << std::endl; } + +int main() +{ + spec("foo bar hux"); + spec(".foo .bar hux"); + spec("#foo .bar[hux='mux']"); + spec("a b c d e f"); + + return 0; +} diff --git a/src/libsass/test/test_subset_map.cpp b/src/libsass/test/test_subset_map.cpp new file mode 100755 index 000000000..37945143f --- /dev/null +++ b/src/libsass/test/test_subset_map.cpp @@ -0,0 +1,472 @@ +#include +#include +#include +#include "../subset_map.hpp" + +Subset_Map ssm; + +string toString(std::vector v); +string toString(std::vector>> v); +void assertEqual(string std::sExpected, std::string sResult); + +void setup() { + ssm.clear(); + + //@ssm[Set[1, 2]] = "Foo" + std::vector s1; + s1.push_back("1"); + s1.push_back("2"); + ssm.put(s1, "Foo"); + + //@ssm[Set["fizz", "fazz"]] = "Bar" + std::vector s2; + s2.push_back("fizz"); + s2.push_back("fazz"); + ssm.put(s2, "Bar"); + + //@ssm[Set[:foo, :bar]] = "Baz" + std::vector s3; + s3.push_back(":foo"); + s3.push_back(":bar"); + ssm.put(s3, "Baz"); + + //@ssm[Set[:foo, :bar, :baz]] = "Bang" + std::vector s4; + s4.push_back(":foo"); + s4.push_back(":bar"); + s4.push_back(":baz"); + ssm.put(s4, "Bang"); + + //@ssm[Set[:bip, :bop, :blip]] = "Qux" + std::vector s5; + s5.push_back(":bip"); + s5.push_back(":bop"); + s5.push_back(":blip"); + ssm.put(s5, "Qux"); + + //@ssm[Set[:bip, :bop]] = "Thram" + std::vector s6; + s6.push_back(":bip"); + s6.push_back(":bop"); + ssm.put(s6, "Thram"); +} + +void testEqualKeys() { + std::cout << "testEqualKeys" << std::endl; + + //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2]) + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); + + //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz"]) + std::vector k2; + k2.push_back("fizz"); + k2.push_back("fazz"); + assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); + + std::cout << std::endl; +} + +void testSubsetKeys() { + std::cout << "testSubsetKeys" << std::endl; + + //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2, "fuzz"]) + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + k1.push_back("fuzz"); + assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); + + //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz", 3]) + std::vector k2; + k2.push_back("fizz"); + k2.push_back("fazz"); + k2.push_back("3"); + assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); + + std::cout << std::endl; +} + +void testSupersetKeys() { + std::cout << "testSupersetKeys" << std::endl; + + //assert_equal [], @ssm.get(Set[1]) + std::vector k1; + k1.push_back("1"); + assertEqual("[]", toString(ssm.get_kv(k1))); + + //assert_equal [], @ssm.get(Set[2]) + std::vector k2; + k2.push_back("2"); + assertEqual("[]", toString(ssm.get_kv(k2))); + + //assert_equal [], @ssm.get(Set["fizz"]) + std::vector k3; + k3.push_back("fizz"); + assertEqual("[]", toString(ssm.get_kv(k3))); + + //assert_equal [], @ssm.get(Set["fazz"]) + std::vector k4; + k4.push_back("fazz"); + assertEqual("[]", toString(ssm.get_kv(k4))); + + std::cout << std::endl; +} + +void testDisjointKeys() { + std::cout << "testDisjointKeys" << std::endl; + + //assert_equal [], @ssm.get(Set[3, 4]) + std::vector k1; + k1.push_back("3"); + k1.push_back("4"); + assertEqual("[]", toString(ssm.get_kv(k1))); + + //assert_equal [], @ssm.get(Set["fuzz", "frizz"]) + std::vector k2; + k2.push_back("fuzz"); + k2.push_back("frizz"); + assertEqual("[]", toString(ssm.get_kv(k2))); + + //assert_equal [], @ssm.get(Set["gran", 15]) + std::vector k3; + k3.push_back("gran"); + k3.push_back("15"); + assertEqual("[]", toString(ssm.get_kv(k3))); + + std::cout << std::endl; +} + +void testSemiDisjointKeys() { + std::cout << "testSemiDisjointKeys" << std::endl; + + //assert_equal [], @ssm.get(Set[2, 3]) + std::vector k1; + k1.push_back("2"); + k1.push_back("3"); + assertEqual("[]", toString(ssm.get_kv(k1))); + + //assert_equal [], @ssm.get(Set["fizz", "fuzz"]) + std::vector k2; + k2.push_back("fizz"); + k2.push_back("fuzz"); + assertEqual("[]", toString(ssm.get_kv(k2))); + + //assert_equal [], @ssm.get(Set[1, "fazz"]) + std::vector k3; + k3.push_back("1"); + k3.push_back("fazz"); + assertEqual("[]", toString(ssm.get_kv(k3))); + + std::cout << std::endl; +} + +void testEmptyKeySet() { + std::cout << "testEmptyKeySet" << std::endl; + + //assert_raises(ArgumentError) {@ssm[Set[]] = "Fail"} + std::vector s1; + try { + ssm.put(s1, "Fail"); + } + catch (const char* &e) { + assertEqual("internal error: subset map keys may not be empty", e); + } +} + +void testEmptyKeyGet() { + std::cout << "testEmptyKeyGet" << std::endl; + + //assert_equal [], @ssm.get(Set[]) + std::vector k1; + assertEqual("[]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} +void testMultipleSubsets() { + std::cout << "testMultipleSubsets" << std::endl; + + //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, "fizz", "fazz"]) + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + k1.push_back("fizz"); + k1.push_back("fazz"); + assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k1))); + + //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, 3, "fizz", "fazz", "fuzz"]) + std::vector k2; + k2.push_back("1"); + k2.push_back("2"); + k2.push_back("3"); + k2.push_back("fizz"); + k2.push_back("fazz"); + k2.push_back("fuzz"); + assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); + + //assert_equal [["Baz", Set[:foo, :bar]]], @ssm.get(Set[:foo, :bar]) + std::vector k3; + k3.push_back(":foo"); + k3.push_back(":bar"); + assertEqual("[[Baz, Set[:foo, :bar]]]", toString(ssm.get_kv(k3))); + + //assert_equal [["Baz", Set[:foo, :bar]], ["Bang", Set[:foo, :bar, :baz]]], @ssm.get(Set[:foo, :bar, :baz]) + std::vector k4; + k4.push_back(":foo"); + k4.push_back(":bar"); + k4.push_back(":baz"); + assertEqual("[[Baz, Set[:foo, :bar]], [Bang, Set[:foo, :bar, :baz]]]", toString(ssm.get_kv(k4))); + + std::cout << std::endl; +} +void testBracketBracket() { + std::cout << "testBracketBracket" << std::endl; + + //assert_equal ["Foo"], @ssm[Set[1, 2, "fuzz"]] + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + k1.push_back("fuzz"); + assertEqual("[Foo]", toString(ssm.get_v(k1))); + + //assert_equal ["Baz", "Bang"], @ssm[Set[:foo, :bar, :baz]] + std::vector k2; + k2.push_back(":foo"); + k2.push_back(":bar"); + k2.push_back(":baz"); + assertEqual("[Baz, Bang]", toString(ssm.get_v(k2))); + + std::cout << std::endl; +} + +void testKeyOrder() { + std::cout << "testEqualKeys" << std::endl; + + //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[2, 1]) + std::vector k1; + k1.push_back("2"); + k1.push_back("1"); + assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} + +void testOrderPreserved() { + std::cout << "testOrderPreserved" << std::endl; + //@ssm[Set[10, 11, 12]] = 1 + std::vector s1; + s1.push_back("10"); + s1.push_back("11"); + s1.push_back("12"); + ssm.put(s1, "1"); + + //@ssm[Set[10, 11]] = 2 + std::vector s2; + s2.push_back("10"); + s2.push_back("11"); + ssm.put(s2, "2"); + + //@ssm[Set[11]] = 3 + std::vector s3; + s3.push_back("11"); + ssm.put(s3, "3"); + + //@ssm[Set[11, 12]] = 4 + std::vector s4; + s4.push_back("11"); + s4.push_back("12"); + ssm.put(s4, "4"); + + //@ssm[Set[9, 10, 11, 12, 13]] = 5 + std::vector s5; + s5.push_back("9"); + s5.push_back("10"); + s5.push_back("11"); + s5.push_back("12"); + s5.push_back("13"); + ssm.put(s5, "5"); + + //@ssm[Set[10, 13]] = 6 + std::vector s6; + s6.push_back("10"); + s6.push_back("13"); + ssm.put(s6, "6"); + + //assert_equal([[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]], @ssm.get(Set[9, 10, 11, 12, 13])) + std::vector k1; + k1.push_back("9"); + k1.push_back("10"); + k1.push_back("11"); + k1.push_back("12"); + k1.push_back("13"); + assertEqual("[[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} +void testMultipleEqualValues() { + std::cout << "testMultipleEqualValues" << std::endl; + //@ssm[Set[11, 12]] = 1 + std::vector s1; + s1.push_back("11"); + s1.push_back("12"); + ssm.put(s1, "1"); + + //@ssm[Set[12, 13]] = 2 + std::vector s2; + s2.push_back("12"); + s2.push_back("13"); + ssm.put(s2, "2"); + + //@ssm[Set[13, 14]] = 1 + std::vector s3; + s3.push_back("13"); + s3.push_back("14"); + ssm.put(s3, "1"); + + //@ssm[Set[14, 15]] = 1 + std::vector s4; + s4.push_back("14"); + s4.push_back("15"); + ssm.put(s4, "1"); + + //assert_equal([[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]], @ssm.get(Set[11, 12, 13, 14, 15])) + std::vector k1; + k1.push_back("11"); + k1.push_back("12"); + k1.push_back("13"); + k1.push_back("14"); + k1.push_back("15"); + assertEqual("[[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} + +int main() +{ + std::vector s1; + s1.push_back("1"); + s1.push_back("2"); + + std::vector s2; + s2.push_back("2"); + s2.push_back("3"); + + std::vector s3; + s3.push_back("3"); + s3.push_back("4"); + + ssm.put(s1, "value1"); + ssm.put(s2, "value2"); + ssm.put(s3, "value3"); + + std::vector s4; + s4.push_back("1"); + s4.push_back("2"); + s4.push_back("3"); + + std::vector > > fetched(ssm.get_kv(s4)); + + std::cout << "PRINTING RESULTS:" << std::endl; + for (size_t i = 0, S = fetched.size(); i < S; ++i) { + std::cout << fetched[i].first << std::endl; + } + + Subset_Map ssm2; + ssm2.put(s1, "foo"); + ssm2.put(s2, "bar"); + ssm2.put(s4, "hux"); + + std::vector > > fetched2(ssm2.get_kv(s4)); + + std::cout << std::endl << "PRINTING RESULTS:" << std::endl; + for (size_t i = 0, S = fetched2.size(); i < S; ++i) { + std::cout << fetched2[i].first << std::endl; + } + + std::cout << "TRYING ON A SELECTOR-LIKE OBJECT" << std::endl; + + Subset_Map sel_ssm; + std::vector target; + target.push_back("desk"); + target.push_back(".wood"); + + std::vector actual; + actual.push_back("desk"); + actual.push_back(".wood"); + actual.push_back(".mine"); + + sel_ssm.put(target, "has-aquarium"); + std::vector > > fetched3(sel_ssm.get_kv(actual)); + std::cout << "RESULTS:" << std::endl; + for (size_t i = 0, S = fetched3.size(); i < S; ++i) { + std::cout << fetched3[i].first << std::endl; + } + + std::cout << std::endl; + + // BEGIN PORTED RUBY TESTS FROM /test/sass/util/subset_map_test.rb + + setup(); + testEqualKeys(); + testSubsetKeys(); + testSupersetKeys(); + testDisjointKeys(); + testSemiDisjointKeys(); + testEmptyKeySet(); + testEmptyKeyGet(); + testMultipleSubsets(); + testBracketBracket(); + testKeyOrder(); + + setup(); + testOrderPreserved(); + + setup(); + testMultipleEqualValues(); + + return 0; +} + +string toString(std::vector>> v) +{ + std::stringstream buffer; + buffer << "["; + for (size_t i = 0, S = v.size(); i < S; ++i) { + buffer << "[" << v[i].first; + buffer << ", Set["; + for (size_t j = 0, S = v[i].second.size(); j < S; ++j) { + buffer << v[i].second[j]; + if (j < S-1) { + buffer << ", "; + } + } + buffer << "]]"; + if (i < S-1) { + buffer << ", "; + } + } + buffer << "]"; + return buffer.str(); +} + +string toString(std::vector v) +{ + std::stringstream buffer; + buffer << "["; + for (size_t i = 0, S = v.size(); i < S; ++i) { + buffer << v[i]; + if (i < S-1) { + buffer << ", "; + } + } + buffer << "]"; + return buffer.str(); +} + +void assertEqual(string sExpected, string sResult) { + std::cout << "Expected: " << sExpected << std::endl; + std::cout << "Result: " << sResult << std::endl; + assert(sExpected == sResult); +} diff --git a/src/libsass/test/test_superselector.cpp b/src/libsass/test/test_superselector.cpp new file mode 100755 index 000000000..bf21c7c4d --- /dev/null +++ b/src/libsass/test/test_superselector.cpp @@ -0,0 +1,69 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include + +using namespace Sass; + +Context ctx = Context(Context::Data()); + +Compound_Selector* compound_selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } + +Complex_Selector* complex_selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_complex_selector(false); } + +void check_compound(std::string s1, std::string s2) +{ + std::cout << "Is " + << s1 + << " a superselector of " + << s2 + << "?\t" + << compound_selector(s1 + ";")->is_superselector_of(compound_selector(s2 + ";")) + << std::endl; +} + +void check_complex(std::string s1, std::string s2) +{ + std::cout << "Is " + << s1 + << " a superselector of " + << s2 + << "?\t" + << complex_selector(s1 + ";")->is_superselector_of(complex_selector(s2 + ";")) + << std::endl; +} + +int main() +{ + check_compound(".foo", ".foo.bar"); + check_compound(".foo.bar", ".foo"); + check_compound(".foo.bar", "div.foo"); + check_compound(".foo", "div.foo"); + check_compound("div.foo", ".foo"); + check_compound("div.foo", "div.bar.foo"); + check_compound("p.foo", "div.bar.foo"); + check_compound(".hux", ".mumble"); + + std::cout << std::endl; + + check_complex(".foo ~ .bar", ".foo + .bar"); + check_complex(".foo .bar", ".foo + .bar"); + check_complex(".foo .bar", ".foo > .bar"); + check_complex(".foo .bar > .hux", ".foo.a .bar.b > .hux"); + check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .hux"); + check_complex(".foo", ".bar .foo"); + check_complex(".foo", ".foo.a"); + check_complex(".foo.bar", ".foo"); + check_complex(".foo .bar .hux", ".bar .hux"); + check_complex(".foo ~ .bar .hux.x", ".foo.a + .bar.b > .hux.y"); + check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .mumble"); + check_complex(".foo + .bar", ".foo ~ .bar"); + check_complex("a c e", "a b c d e"); + check_complex("c a e", "a b c d e"); + + return 0; +} + + diff --git a/src/libsass/test/test_unification.cpp b/src/libsass/test/test_unification.cpp new file mode 100755 index 000000000..5c663ee90 --- /dev/null +++ b/src/libsass/test/test_unification.cpp @@ -0,0 +1,31 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include + +using namespace Sass; + +Context ctx = Context(Context::Data()); + +Compound_Selector* selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } + +void unify(std::string lhs, std::string rhs) +{ + Compound_Selector* unified = selector(lhs + ";")->unify_with(selector(rhs + ";"), ctx); + std::cout << lhs << " UNIFIED WITH " << rhs << " =\t" << (unified ? unified->to_string() : "NOTHING") << std::endl; +} + +int main() +{ + unify(".foo", ".foo.bar"); + unify("div:nth-of-type(odd)", "div:first-child"); + unify("div", "span:whatever"); + unify("div", "span"); + unify("foo:bar::after", "foo:bar::first-letter"); + unify(".foo#bar.hux", ".hux.foo#bar"); + unify(".foo#bar.hux", ".hux.foo#baz"); + unify("*:blah:fudge", "p:fudge:blah"); + + return 0; +} diff --git a/src/libsass/version.sh b/src/libsass/version.sh new file mode 100755 index 000000000..281de74d7 --- /dev/null +++ b/src/libsass/version.sh @@ -0,0 +1,10 @@ +if test "x$LIBSASS_VERSION" = "x"; then + LIBSASS_VERSION=`git describe --abbrev=4 --dirty --always --tags 2>/dev/null` +fi +if test "x$LIBSASS_VERSION" = "x"; then + LIBSASS_VERSION=`cat VERSION 2>/dev/null` +fi +if test "x$LIBSASS_VERSION" = "x"; then + LIBSASS_VERSION="[na]" +fi +echo $LIBSASS_VERSION diff --git a/src/libsass/win/libsass.sln b/src/libsass/win/libsass.sln new file mode 100755 index 000000000..9354d85f5 --- /dev/null +++ b/src/libsass/win/libsass.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30723.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsass", "libsass.vcxproj", "{E4030474-AFC9-4CC6-BEB6-D846F631502B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|Win64 = Debug|Win64 + Release|Win32 = Release|Win32 + Release|Win64 = Release|Win64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.ActiveCfg = Debug|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.Build.0 = Debug|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.ActiveCfg = Debug|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.Build.0 = Debug|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.ActiveCfg = Release|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.Build.0 = Release|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.ActiveCfg = Release|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets new file mode 100755 index 000000000..222c4381a --- /dev/null +++ b/src/libsass/win/libsass.targets @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libsass/win/libsass.vcxproj b/src/libsass/win/libsass.vcxproj new file mode 100755 index 000000000..8cfd61f2a --- /dev/null +++ b/src/libsass/win/libsass.vcxproj @@ -0,0 +1,188 @@ + + + + [NA] + ..\src + ..\src + ..\include + + + + + + + + + + %(PreprocessorDefinitions);LIBSASS_VERSION="$(LIBSASS_VERSION)"; + + + + + + + + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + {E4030474-AFC9-4CC6-BEB6-D846F631502B} + Win32Proj + libsass + + + libsass + Unicode + + + DynamicLibrary + ADD_EXPORTS;$(PreprocessorDefinitions); + + + StaticLibrary + + + v120 + + + v140 + + + true + + + true + + + false + true + + + false + true + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)bin\Debug\ + $(SolutionDir)bin\Debug\obj\ + + + true + $(SolutionDir)bin\Debug\ + $(SolutionDir)bin\Debug\obj\ + + + false + $(SolutionDir)bin\ + $(SolutionDir)bin\obj\ + + + false + $(SolutionDir)bin\ + $(SolutionDir)bin\obj\ + + + + ..\include;%(AdditionalIncludeDirectories) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + true + true + + + + + + + diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters new file mode 100755 index 000000000..47ea1c147 --- /dev/null +++ b/src/libsass/win/libsass.vcxproj.filters @@ -0,0 +1,348 @@ + + + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {bb9c270d-e9f5-49bf-afda-771a1a4bb5b7} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + + + Sources + + + Sources + + + Sources + + + Sources + + + Source Files + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + From 6f1c5b312afc0e9639b7d8b8aae4b612539d39cb Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:40:25 +1100 Subject: [PATCH 019/286] Normalise whitespace in docs --- CONTRIBUTING.md | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b3b21443..c1861124f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ ## Contributing * Fork the project. * Make your feature addition or bug fix. - * Add documentation if necessary. + * Add documentation if necessary. * Add tests for it. This is important so I don't break it in a future version unintentionally. * Send a pull request. Bonus points for topic branches. diff --git a/README.md b/README.md index 595688fac..69edc768a 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ Used to determine how many digits after the decimal will be allowed. For instanc Type: `Boolean` Default: `false` -`true` Enables the line number and file where a selector is defined to be emitted into the compiled CSS as a comment. Useful for debugging, especially when using imports and mixins. +`true` Enables the line number and file where a selector is defined to be emitted into the compiled CSS as a comment. Useful for debugging, especially when using imports and mixins. ### sourceMap Type: `Boolean | String | undefined` @@ -351,7 +351,7 @@ var result = sass.renderSync({ data: 'body{background:blue; a{color:black;}}', outputStyle: 'compressed', outFile: '/to/my/output.css', - sourceMap: true, // or an absolute or relative (to outFile) path + sourceMap: true, // or an absolute or relative (to outFile) path importer: function(url, prev, done) { // url is the path in import as is, which LibSass encountered. // prev is the previously resolved path. @@ -475,8 +475,8 @@ The interface for command-line usage is fairly simplistic at this stage, as seen Output will be sent to stdout if the `--output` flag is omitted. ### Usage - `node-sass [options] [output]` - Or: + `node-sass [options] [output]` + Or: `cat | node-sass > output` Example: From 2d6d19ed158cc9e36479326a8ef1f1143aaf4756 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:40:52 +1100 Subject: [PATCH 020/286] Remove reference to the LibSass to Sass Spec git submodules --- CONTRIBUTING.md | 3 +-- README.md | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1861124f..58cf3c260 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,8 +34,7 @@ If this project is missing an API or command line flag that has been added to [l 1. Ensure the `master` branch is up-to-date with: `git checkout master; git pull` 2. Create a new Git branch and make your changes. `git checkout -b name-of-new-branch` 3. Install all the node.js dependencies of node-sass with: `npm install` -4. The Sass test spec and LibSass are Git submodules, so get their codebase with: `git submodule update --init` -5. Ensure the tests pass with: `npm test` +4. Ensure the tests pass with: `npm test` If the bug fix requires modifying any of the C++ files in the src/ directory, you'll need to re-build the bindings to libSass. diff --git a/README.md b/README.md index 69edc768a..dc3e6aafe 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,6 @@ Check out the project: ```bash git clone --recursive https://github.com/sass/node-sass.git cd node-sass -git submodule update --init --recursive npm install node scripts/build -f # use -d switch for debug release # if succeeded, it will generate and move From ae31ada765a5e81b02087cc1a6acbe0d839f7091 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:42:35 +1100 Subject: [PATCH 021/286] Remove contributing documentation about bumping LibSass I can't imagine accepting a PR with a LibSass bump. It'd be painful to audit. LibSass should be handled by core. --- CONTRIBUTING.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58cf3c260..fba03d7ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,11 +42,6 @@ If the bug fix requires modifying any of the C++ files in the src/ directory, yo Alternatively, the `scripts/build.js` build script has several different command line flags that can be passed by running: `node scripts/build.js -[flag]` -If the bug fix requires updating the version of libSass, you'll need to update its git submodule. - -1. Move into node-sass's libSass directory with: `cd src/libsass` -2. Look for a new version of libSass with: `git tag` and check it out with: `git checkout [tag]` -3. Then return to root of the node-sass repository and add the change to your feature branch. ## Reporting Sass compilation and syntax issues From a00aece60ceb7fb08359250a16f6c9c2bb55e21c Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:43:42 +1100 Subject: [PATCH 022/286] Remove LibSass git submdule from CI --- .travis.yml | 1 - appveyor.yml | 2 -- 2 files changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd6c63b22..ab225d22d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,6 @@ addons: - g++-4.7 before_install: - - git submodule update --init --recursive - npm config set python `which python` - if [ $TRAVIS_OS_NAME == "linux" ]; then export CC="gcc-4.7"; diff --git a/appveyor.yml b/appveyor.yml index b9e141e7c..b9c2c4358 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -49,7 +49,6 @@ - ps: Install-Product node $env:nodejs_version $env:platform - node --version - npm --version - - git submodule update --init --recursive - npm install --msvs_version=2013 test: off @@ -121,7 +120,6 @@ - ps: Install-Product node $env:nodejs_version $env:platform - node --version - npm --version - - git submodule update --init --recursive - npm install --msvs_version=2013 test_script: From 93eeed1bc7b386aedef3e8fe431ec2543fd65b3e Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 02:00:49 +1100 Subject: [PATCH 023/286] Remove the git fallback The git submodule is removed as of #1850. This was added by me in #874 to support node-sass being installed as a gitish via GitHub rather than the npm registry. We publish the LibSass src to npm already so it should continue to work as is. --- scripts/build.js | 116 ++++++++--------------------------------------- 1 file changed, 20 insertions(+), 96 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index 79423abc0..ef919eed5 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -2,8 +2,7 @@ * node-sass: scripts/build.js */ -var pkg = require('../package.json'), - fs = require('fs'), +var fs = require('fs'), mkdir = require('mkdirp'), path = require('path'), spawn = require('cross-spawn'), @@ -49,74 +48,6 @@ function afterBuild(options) { }); } -/** - * manageProcess - * - * @param {ChildProcess} proc - * @param {Function} cb - * @api private - */ - -function manageProcess(proc, cb) { - var errorMsg = ''; - proc.stderr.on('data', function(data) { - errorMsg += data.toString(); - }); - proc.on('close', function(code) { - cb(code === 0 ? null : { message: errorMsg }); - }); -} - -/** - * initSubmodules - * - * @param {Function} cb - * @api private - */ - -function initSubmodules(cb) { - console.log('Detected a git install'); - console.log('Cloning LibSass into src/libsass'); - - var clone = spawn('git', ['clone', 'https://github.com/sass/libsass.git', './src/libsass']); - manageProcess(clone, function(err) { - if (err) { - cb(err); - return; - } - - console.log('Checking out LibSass to', pkg.libsass); - - var checkout = spawn('git', ['checkout', pkg.libsass], { cwd: './src/libsass' }); - manageProcess(checkout, function(err) { - cb(err); - }); - }); -} - -/** - * installGitDependencies - * - * @param {Function} cb - * @api private - */ - -function installGitDependencies(options, cb) { - var libsassPath = './src/libsass'; - - if (process.env.LIBSASS_EXT || options.libsassExt) { - cb(); - } else if (fs.access) { // node 0.12+, iojs 1.0.0+ - fs.access(libsassPath, fs.R_OK, function(err) { - err && err.code === 'ENOENT' ? initSubmodules(cb) : cb(); - }); - } else { // node < 0.12 - fs.exists(libsassPath, function(exists) { - exists ? cb() : initSubmodules(cb); - }); - } -} - /** * Build * @@ -125,37 +56,30 @@ function installGitDependencies(options, cb) { */ function build(options) { - installGitDependencies(options, function(err) { - if (err) { - console.error(err.message); - process.exit(1); - } - - var args = [require.resolve(path.join('node-gyp', 'bin', 'node-gyp.js')), 'rebuild', '--verbose'].concat( - ['libsass_ext', 'libsass_cflags', 'libsass_ldflags', 'libsass_library'].map(function(subject) { - return ['--', subject, '=', process.env[subject.toUpperCase()] || ''].join(''); - })).concat(options.args); + var args = [require.resolve(path.join('node-gyp', 'bin', 'node-gyp.js')), 'rebuild', '--verbose'].concat( + ['libsass_ext', 'libsass_cflags', 'libsass_ldflags', 'libsass_library'].map(function(subject) { + return ['--', subject, '=', process.env[subject.toUpperCase()] || ''].join(''); + })).concat(options.args); - console.log('Building:', [process.execPath].concat(args).join(' ')); + console.log('Building:', [process.execPath].concat(args).join(' ')); - var proc = spawn(process.execPath, args, { - stdio: [0, 1, 2] - }); + var proc = spawn(process.execPath, args, { + stdio: [0, 1, 2] + }); - proc.on('exit', function(errorCode) { - if (!errorCode) { - afterBuild(options); - return; - } + proc.on('exit', function(errorCode) { + if (!errorCode) { + afterBuild(options); + return; + } - if (errorCode === 127 ) { - console.error('node-gyp not found!'); - } else { - console.error('Build failed with error code:', errorCode); - } + if (errorCode === 127 ) { + console.error('node-gyp not found!'); + } else { + console.error('Build failed with error code:', errorCode); + } - process.exit(1); - }); + process.exit(1); }); } From 1e76d99164a112a8895c668af795594a35dcdca6 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jan 2017 15:07:20 +1100 Subject: [PATCH 024/286] 4.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dbffb3a42..3c9a6d6c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.1.1", + "version": "4.2.0", "libsass": "3.4.3", "description": "Wrapper around libsass", "license": "MIT", From 71a7a84ca58daaeba9fc4fc22621ebe00d756ff6 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 12 Jan 2017 20:42:27 +1100 Subject: [PATCH 025/286] 4.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c9a6d6c1..afdba96e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.2.0", + "version": "4.3.0", "libsass": "3.4.3", "description": "Wrapper around libsass", "license": "MIT", From 1b9970a4278f36ba4ff88d41303c083a4cce5ac7 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Jan 2017 09:23:24 +1100 Subject: [PATCH 026/286] Bump LibSass to 3.5.0.beta.2 --- package.json | 2 +- src/libsass.gyp | 1 + src/libsass/.travis.yml | 55 +- src/libsass/GNUmakefile.am | 4 +- src/libsass/Makefile | 14 +- src/libsass/Makefile.conf | 1 + src/libsass/appveyor.yml | 2 +- src/libsass/configure.ac | 4 +- src/libsass/docs/api-context-internal.md | 50 +- src/libsass/docs/api-context.md | 32 +- src/libsass/docs/api-doc.md | 63 +- src/libsass/docs/api-function-example.md | 2 +- src/libsass/docs/api-function.md | 38 +- src/libsass/docs/api-importer.md | 38 +- src/libsass/docs/api-value.md | 4 +- src/libsass/docs/build-on-windows.md | 2 +- src/libsass/docs/custom-functions-internal.md | 18 +- src/libsass/include/sass/base.h | 3 - src/libsass/include/sass/context.h | 19 +- src/libsass/include/sass/functions.h | 31 + src/libsass/include/sass/values.h | 4 +- src/libsass/include/sass/version.h | 2 +- src/libsass/include/sass/version.h.in | 2 +- src/libsass/script/ci-build-libsass | 13 +- src/libsass/script/ci-build-plugin | 62 ++ src/libsass/script/ci-report-coverage | 16 +- src/libsass/src/GNUmakefile.am | 2 +- src/libsass/src/ast.cpp | 658 +++++++++--------- src/libsass/src/ast.hpp | 445 ++++++------ src/libsass/src/ast_def_macros.hpp | 18 +- src/libsass/src/ast_fwd_decl.cpp | 29 + src/libsass/src/ast_fwd_decl.hpp | 95 ++- src/libsass/src/backtrace.hpp | 2 +- src/libsass/src/bind.cpp | 42 +- src/libsass/src/bind.hpp | 1 - src/libsass/src/check_nesting.cpp | 148 ++-- src/libsass/src/check_nesting.hpp | 4 +- src/libsass/src/color_maps.hpp | 14 +- src/libsass/src/context.cpp | 26 +- src/libsass/src/context.hpp | 1 + src/libsass/src/cssize.cpp | 146 ++-- src/libsass/src/cssize.hpp | 1 - src/libsass/src/debugger.hpp | 467 ++++++------- src/libsass/src/environment.cpp | 4 +- src/libsass/src/environment.hpp | 3 + src/libsass/src/eval.cpp | 322 +++++---- src/libsass/src/expand.cpp | 222 +++--- src/libsass/src/expand.hpp | 2 - src/libsass/src/extend.cpp | 191 +++-- src/libsass/src/file.cpp | 76 +- src/libsass/src/file.hpp | 17 +- src/libsass/src/functions.cpp | 260 +++---- src/libsass/src/functions.hpp | 5 +- src/libsass/src/inspect.cpp | 80 ++- src/libsass/src/inspect.hpp | 3 + src/libsass/src/lexer.cpp | 1 - src/libsass/src/listize.cpp | 4 +- src/libsass/src/listize.hpp | 1 - src/libsass/src/memory/SharedPtr.cpp | 4 +- src/libsass/src/memory/SharedPtr.hpp | 49 +- src/libsass/src/node.cpp | 43 +- src/libsass/src/node.hpp | 2 - src/libsass/src/output.cpp | 28 +- src/libsass/src/parser.cpp | 510 +++++++------- src/libsass/src/parser.hpp | 18 +- src/libsass/src/plugins.cpp | 38 +- src/libsass/src/position.hpp | 2 +- src/libsass/src/prelexer.cpp | 23 +- src/libsass/src/prelexer.hpp | 4 + src/libsass/src/remove_placeholders.cpp | 17 +- src/libsass/src/remove_placeholders.hpp | 6 +- src/libsass/src/sass.cpp | 60 +- src/libsass/src/sass_context.cpp | 35 +- src/libsass/src/sass_context.hpp | 7 +- src/libsass/src/sass_functions.cpp | 64 +- src/libsass/src/sass_functions.hpp | 20 +- src/libsass/src/sass_util.hpp | 2 +- src/libsass/src/sass_values.cpp | 43 +- src/libsass/src/sass_values.hpp | 3 +- src/libsass/src/source_map.cpp | 1 - src/libsass/src/subset_map.cpp | 14 +- src/libsass/src/subset_map.hpp | 12 +- src/libsass/src/to_c.cpp | 4 +- src/libsass/src/util.cpp | 70 +- src/libsass/src/values.cpp | 23 +- src/libsass/win/libsass.targets | 1 + src/libsass/win/libsass.vcxproj.filters | 3 + src/sass_types/list.cpp | 3 +- 88 files changed, 2771 insertions(+), 2110 deletions(-) create mode 100755 src/libsass/script/ci-build-plugin create mode 100755 src/libsass/src/ast_fwd_decl.cpp diff --git a/package.json b/package.json index afdba96e7..f64a62fa9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.3.0", - "libsass": "3.4.3", + "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", diff --git a/src/libsass.gyp b/src/libsass.gyp index b0764ab97..0fd857d81 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -12,6 +12,7 @@ ], 'sources': [ 'libsass/src/ast.cpp', + 'libsass/src/ast_fwd_decl.cpp', 'libsass/src/base64vlq.cpp', 'libsass/src/bind.cpp', 'libsass/src/cencode.c', diff --git a/src/libsass/.travis.yml b/src/libsass/.travis.yml index c36f15572..09ca55066 100755 --- a/src/libsass/.travis.yml +++ b/src/libsass/.travis.yml @@ -1,13 +1,6 @@ language: cpp sudo: false -os: - - linux - - osx - -compiler: - - gcc - - clang # don't create redundant code coverage reports # - AUTOTOOLS=yes COVERAGE=yes BUILD=static @@ -19,27 +12,53 @@ compiler: # this will still catch all coding errors! # - AUTOTOOLS=yes COVERAGE=no BUILD=static -env: - - AUTOTOOLS=no COVERAGE=no BUILD=shared - - AUTOTOOLS=no COVERAGE=yes BUILD=static - - AUTOTOOLS=yes COVERAGE=no BUILD=shared - # currenty there are various issues when # built with coverage, clang and autotools # - AUTOTOOLS=yes COVERAGE=yes BUILD=shared matrix: - exclude: - - compiler: clang - env: AUTOTOOLS=yes COVERAGE=yes BUILD=static + include : + - os: linux + compiler: gcc + env: AUTOTOOLS=no COVERAGE=yes BUILD=static - os: linux + compiler: g++-5 + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-5 + - os: linux + compiler: clang++-3.7 + env: AUTOTOOLS=no COVERAGE=yes BUILD=static + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - clang-3.7 + - os: linux + compiler: clang + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared + - os: osx + compiler: clang env: AUTOTOOLS=no COVERAGE=no BUILD=shared - os: osx - compiler: gcc + compiler: clang + env: AUTOTOOLS=no COVERAGE=yes BUILD=static - os: osx - env: AUTOTOOLS=no BUILD=static + compiler: clang + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared -script: ./script/ci-build-libsass +script: + - ./script/ci-build-libsass + - ./script/ci-build-plugin math + - ./script/ci-build-plugin glob + - ./script/ci-build-plugin digest + - ./script/ci-build-plugin tests before_install: ./script/ci-install-deps install: ./script/ci-install-compiler after_success: ./script/ci-report-coverage diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am index d2bfdbfec..3dfc2b532 100755 --- a/src/libsass/GNUmakefile.am +++ b/src/libsass/GNUmakefile.am @@ -4,7 +4,7 @@ AM_COPT = -Wall -O2 AM_COVLDFLAGS = if ENABLE_COVERAGE - AM_COPT = -O0 --coverage + AM_COPT = -Wall -O1 -fno-omit-frame-pointer --coverage AM_COVLDFLAGS += -lgcov endif @@ -57,7 +57,7 @@ TESTS = \ $(SASS_SPEC_PATH)/spec/scss-tests \ $(SASS_SPEC_PATH)/spec/types -SASS_TEST_FLAGS = -V 3.4 --impl libsass +SASS_TEST_FLAGS = -V 3.5 --impl libsass LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) ./script/tap-driver AM_LOG_FLAGS = -c ./tester $(LOG_FLAGS) if USE_TAP diff --git a/src/libsass/Makefile b/src/libsass/Makefile index abfe7efaf..7215a1dc4 100755 --- a/src/libsass/Makefile +++ b/src/libsass/Makefile @@ -15,10 +15,14 @@ INSTALL ?= install CFLAGS ?= -Wall CXXFLAGS ?= -Wall LDFLAGS ?= -Wall -ifneq "$(COVERAGE)" "yes" +ifeq "x$(COVERAGE)" "x" CFLAGS += -O2 CXXFLAGS += -O2 LDFLAGS += -O2 +else + CFLAGS += -O1 -fno-omit-frame-pointer + CXXFLAGS += -O1 -fno-omit-frame-pointer + LDFLAGS += -O1 -fno-omit-frame-pointer endif LDFLAGS += -Wl,-undefined,error CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) @@ -305,16 +309,16 @@ version: $(SASSC_BIN) $(SASSC_BIN) -v test: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) test_build: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) test_full: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) test_probe: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) clean-objects: lib -$(RM) lib/*.a lib/*.so lib/*.dll lib/*.la diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf index 6c15907b1..a8954edea 100755 --- a/src/libsass/Makefile.conf +++ b/src/libsass/Makefile.conf @@ -13,6 +13,7 @@ SOURCES = \ functions.cpp \ color_maps.cpp \ environment.cpp \ + ast_fwd_decl.cpp \ bind.cpp \ file.cpp \ util.cpp \ diff --git a/src/libsass/appveyor.yml b/src/libsass/appveyor.yml index e88cfcb81..767e6738a 100755 --- a/src/libsass/appveyor.yml +++ b/src/libsass/appveyor.yml @@ -65,7 +65,7 @@ test_script: } $env:TargetPath = Join-Path $pwd.Path $env:TargetPath If (Test-Path "$env:TargetPath") { - ruby sass-spec/sass-spec.rb -V 3.4 --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec + ruby sass-spec/sass-spec.rb -V 3.5 --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec if(-not($?)) { echo "sass-spec tests failed" exit 1 diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac index 3a6ccc940..bf05dbfaa 100755 --- a/src/libsass/configure.ac +++ b/src/libsass/configure.ac @@ -121,8 +121,8 @@ if test "x$enable_cov" = "xyes"; then # Remove all optimization flags from C[XX]FLAGS changequote({,}) - CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'` - CXXFLAGS=`echo "$CXXFLAGS" | $SED -e 's/-O[0-9]*//g'` + CFLAGS=`echo "$CFLAGS -O1 -fno-omit-frame-pointer" | $SED -e 's/-O[0-9]*//g'` + CXXFLAGS=`echo "$CXXFLAGS -O1 -fno-omit-frame-pointer" | $SED -e 's/-O[0-9]*//g'` changequote([,]) AC_SUBST(GCOV) diff --git a/src/libsass/docs/api-context-internal.md b/src/libsass/docs/api-context-internal.md index 94fd62804..1a2818b34 100755 --- a/src/libsass/docs/api-context-internal.md +++ b/src/libsass/docs/api-context-internal.md @@ -7,26 +7,35 @@ enum Sass_Input_Style { SASS_CONTEXT_FOLDER }; -// simple linked list -struct string_list { - string_list* next; - char* string; -}; - // sass config options structure -struct Sass_Options { - - // Precision for fractional numbers - int precision; +struct Sass_Inspect_Options { // Output style for the generated css code // A value from above SASS_STYLE_* constants enum Sass_Output_Style output_style; + // Precision for fractional numbers + int precision; + +}; + +// sass config options structure +struct Sass_Output_Options : Sass_Inspect_Options { + + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + // Emit comments in the generated CSS indicating // the corresponding source line. bool source_comments; +}; + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + // embed sourceMappingUrl as data uri bool source_map_embed; @@ -56,15 +65,9 @@ struct Sass_Options { // information in source-maps etc. char* output_path; - // String to be used for indentation - const char* indent; - // String to be used to for line feeds - const char* linefeed; - // Colon-separated list of paths // Semicolon-separated on Windows - // Note: It may be better to use - // array interface instead + // Maybe use array interface instead? char* include_path; char* plugin_path; @@ -82,10 +85,13 @@ struct Sass_Options { char* source_map_root; // Custom functions that can be called from sccs code - Sass_C_Function_List c_functions; + Sass_Function_List c_functions; // Callback to overload imports - Sass_C_Import_Callback importer; + Sass_Importer_List c_importers; + + // List of custom headers + Sass_Importer_List c_headers; }; @@ -111,6 +117,7 @@ struct Sass_Context : Sass_Options char* error_file; size_t error_line; size_t error_column; + const char* error_src; // report imported files char** included_files; @@ -130,6 +137,7 @@ struct Sass_Data_Context : Sass_Context { // provided source string char* source_string; + char* srcmap_string; }; @@ -147,9 +155,9 @@ struct Sass_Compiler { // original c context Sass_Context* c_ctx; // Sass::Context - void* cpp_ctx; + Sass::Context* cpp_ctx; // Sass::Block - void* root; + Sass::Block_Obj root; }; ``` diff --git a/src/libsass/docs/api-context.md b/src/libsass/docs/api-context.md index b023d4e00..dfd10c181 100755 --- a/src/libsass/docs/api-context.md +++ b/src/libsass/docs/api-context.md @@ -207,6 +207,15 @@ size_t sass_context_get_error_column (struct Sass_Context* ctx); const char* sass_context_get_source_map_string (struct Sass_Context* ctx); char** sass_context_get_included_files (struct Sass_Context* ctx); +// Getters for Sass_Compiler options (query import stack) +size_t sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +Sass_Import_Entry sass_compiler_get_last_import(struct Sass_Compiler* compiler); +Sass_Import_Entry sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +// Getters for Sass_Compiler options (query function stack) +size_t sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +Sass_Callee_Entry sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +Sass_Callee_Entry sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); + // Take ownership of memory (value on context is set to 0) char* sass_context_take_error_json (struct Sass_Context* ctx); char* sass_context_take_error_text (struct Sass_Context* ctx); @@ -214,10 +223,6 @@ char* sass_context_take_error_message (struct Sass_Context* ctx); char* sass_context_take_error_file (struct Sass_Context* ctx); char* sass_context_take_output_string (struct Sass_Context* ctx); char* sass_context_take_source_map_string (struct Sass_Context* ctx); - -// Push function for plugin/include paths (no manipulation support for now) -void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -void sass_option_push_include_path (struct Sass_Options* options, const char* path); ``` ### Sass Options API @@ -236,13 +241,18 @@ const char* sass_option_get_indent (struct Sass_Options* options); const char* sass_option_get_linefeed (struct Sass_Options* options); const char* sass_option_get_input_path (struct Sass_Options* options); const char* sass_option_get_output_path (struct Sass_Options* options); -const char* sass_option_get_plugin_path (struct Sass_Options* options); -const char* sass_option_get_include_path (struct Sass_Options* options); const char* sass_option_get_source_map_file (struct Sass_Options* options); const char* sass_option_get_source_map_root (struct Sass_Options* options); Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); +// Getters for Context_Option include path array +size_t sass_option_get_include_path_size(struct Sass_Options* options); +const char* sass_option_get_include_path(struct Sass_Options* options, size_t i); +// Plugin paths to load dynamic libraries work the same +size_t sass_option_get_plugin_path_size(struct Sass_Options* options); +const char* sass_option_get_plugin_path(struct Sass_Options* options, size_t i); + // Setters for Context_Option values void sass_option_set_precision (struct Sass_Options* options, int precision); void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); @@ -266,6 +276,16 @@ void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callb // Push function for paths (no manipulation support for now) void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); void sass_option_push_include_path (struct Sass_Options* options, const char* path); + +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +char* sass_find_file (const char* path, struct Sass_Options* opt); +char* sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +char* sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +char* sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); ``` ### More links diff --git a/src/libsass/docs/api-doc.md b/src/libsass/docs/api-doc.md index b1393e07f..58e427eeb 100755 --- a/src/libsass/docs/api-doc.md +++ b/src/libsass/docs/api-doc.md @@ -1,6 +1,9 @@ ## Introduction -LibSass wouldn't be much good without a way to interface with it. These interface documentations describe the various functions and data structures available to implementers. They are split up over three major components, which have all their own source files (plus some common functionality). +LibSass wouldn't be much good without a way to interface with it. These +interface documentations describe the various functions and data structures +available to implementers. They are split up over three major components, which +have all their own source files (plus some common functionality). - [Sass Context](api-context.md) - Trigger and handle the main Sass compilation - [Sass Value](api-value.md) - Exchange values and its format with LibSass @@ -41,7 +44,12 @@ gcc -Wall version.c -lsass -o version && ./version ## Compiling your code -The most important is your sass file (or string of sass code). With this, you will want to start a LibSass compiler. Here is some pseudocode describing the process. The compiler has two different modes: direct input as a string with `Sass_Data_Context` or LibSass will do file reading for you by using `Sass_File_Context`. See the code for a list of options available [Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) +The most important is your sass file (or string of sass code). With this, you +will want to start a LibSass compiler. Here is some pseudocode describing the +process. The compiler has two different modes: direct input as a string with +`Sass_Data_Context` or LibSass will do file reading for you by using +`Sass_File_Context`. See the code for a list of options available +[Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) **Building a file compiler** @@ -97,7 +105,9 @@ struct Sass_Data_context : Sass_Context; This mirrors very well how `libsass` uses these structures. -- `Sass_Options` holds everything you feed in before the compilation. It also hosts `input_path` and `output_path` options, because they are used to generate/calculate relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. +- `Sass_Options` holds everything you feed in before the compilation. It also hosts +`input_path` and `output_path` options, because they are used to generate/calculate +relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. - `Sass_Context` holds all the data returned by the compilation step. - `Sass_File_Context` is a specific implementation that requires no additional fields - `Sass_Data_Context` is a specific implementation that adds the `input_source` field @@ -106,8 +116,11 @@ Structs can be down-casted to access `context` or `options`! ## Memory handling and life-cycles -We keep memory around for as long as the main [context](api-context.md) object is not destroyed (`sass_delete_context`). LibSass will create copies of most inputs/options beside the main sass code. -You need to allocate and fill that buffer before passing it to LibSass. You may also overtake memory management from libsass for certain return values (i.e. `sass_context_take_output_string`). +We keep memory around for as long as the main [context](api-context.md) object +is not destroyed (`sass_delete_context`). LibSass will create copies of most +inputs/options beside the main sass code. You need to allocate and fill that +buffer before passing it to LibSass. You may also overtake memory management +from libsass for certain return values (i.e. `sass_context_take_output_string`). ```C // to allocate buffer to be filled @@ -125,9 +138,6 @@ void sass_free_memory(void* ptr); char* sass_string_unquote (const char* str); char* sass_string_quote (const char* str, const char quote_mark); -// Resolve a file via the given include paths in the include char* array -char* sass_resolve_file (const char* path, const char* incs[]); - // Get compiled libsass version const char* libsass_version(void); @@ -140,15 +150,25 @@ const char* libsass_language_version(void); **input_path** -The `input_path` is part of `Sass_Options`, but it also is the main option for `Sass_File_Context`. It is also used to generate relative file links in source-maps. Therefore it is pretty usefull to pass this information if you have a `Sass_Data_Context` and know the original path. +The `input_path` is part of `Sass_Options`, but it also is the main option for +`Sass_File_Context`. It is also used to generate relative file links in source- +maps. Therefore it is pretty usefull to pass this information if you have a +`Sass_Data_Context` and know the original path. **output_path** -Be aware that `libsass` does not write the output file itself. This option merely exists to give `libsass` the proper information to generate links in source-maps. The file has to be written to the disk by the binding/implementation. If the `output_path` is omitted, `libsass` tries to extrapolate one from the `input_path` by replacing (or adding) the file ending with `.css`. +Be aware that `libsass` does not write the output file itself. This option +merely exists to give `libsass` the proper information to generate links in +source-maps. The file has to be written to the disk by the +binding/implementation. If the `output_path` is omitted, `libsass` tries to +extrapolate one from the `input_path` by replacing (or adding) the file ending +with `.css`. ## Error Codes -The `error_code` is integer value which indicates the type of error that occurred inside the LibSass process. Following is the list of error codes along with the short description: +The `error_code` is integer value which indicates the type of error that +occurred inside the LibSass process. Following is the list of error codes along +with the short description: * 1: normal errors like parsing or `eval` errors * 2: bad allocation error (memory error) @@ -156,11 +176,15 @@ The `error_code` is integer value which indicates the type of error that occurre * 4: legacy string exceptions ( `throw const char*` or `std::string` ) * 5: Some other unknown exception -Although for the API consumer, error codes do not offer much value except indicating whether *any* error occurred during the compilation, it helps debugging the LibSass internal code paths. +Although for the API consumer, error codes do not offer much value except +indicating whether *any* error occurred during the compilation, it helps +debugging the LibSass internal code paths. ## Real-World Implementations -The proof is in the pudding, so we have highlighted a few implementations that should be on par with the latest LibSass interface version. Some of them may not have all features implemented! +The proof is in the pudding, so we have highlighted a few implementations that +should be on par with the latest LibSass interface version. Some of them may not +have all features implemented! 1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) 2. [Go Example](http://godoc.org/github.com/wellington/go-libsass#example-Context-Compile) @@ -168,11 +192,20 @@ The proof is in the pudding, so we have highlighted a few implementations that s ## ABI forward compatibility -We use a functional API to make dynamic linking more robust and future compatible. The API is not yet 100% stable, so we do not yet guarantee [ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward compatibility. We will do so, once we increase the shared library version above 1.0. +We use a functional API to make dynamic linking more robust and future +compatible. The API is not yet 100% stable, so we do not yet guarantee +[ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward +compatibility. ## Plugins (experimental) -LibSass can load plugins from directories. Just define `plugin_path` on context options to load all plugins from the given directories. To implement plugins, please consult the [[Wiki-Page for plugins|API-Plugins]]. +LibSass can load plugins from directories. Just define `plugin_path` on context +options to load all plugins from the directories. To implement plugins, please +consult the following example implementations. + +- https://github.com/mgreter/libsass-glob +- https://github.com/mgreter/libsass-math +- https://github.com/mgreter/libsass-digest ## Internal Structs diff --git a/src/libsass/docs/api-function-example.md b/src/libsass/docs/api-function-example.md index 0e41940ce..38608e1a2 100755 --- a/src/libsass/docs/api-function-example.md +++ b/src/libsass/docs/api-function-example.md @@ -11,7 +11,7 @@ union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entr struct Sass_Context* ctx = sass_compiler_get_context(comp); struct Sass_Options* opts = sass_compiler_get_options(comp); // get information about previous importer entry from the stack - struct Sass_Import* import = sass_compiler_get_last_import(comp); + Sass_Import_Entry import = sass_compiler_get_last_import(comp); const char* prev_abs_path = sass_import_get_abs_path(import); const char* prev_imp_path = sass_import_get_imp_path(import); // get the cookie from function descriptor diff --git a/src/libsass/docs/api-function.md b/src/libsass/docs/api-function.md index eeaa61a1d..8d9d97ca4 100755 --- a/src/libsass/docs/api-function.md +++ b/src/libsass/docs/api-function.md @@ -30,17 +30,41 @@ typedef union Sass_Value* (*Sass_Function_Fn) (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); // Creators for sass function list and function descriptors -ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); -ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); +Sass_Function_List sass_make_function_list (size_t length); +Sass_Function_Entry sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); +// In case you need to free them yourself +void sass_delete_function (Sass_Function_Entry entry); +void sass_delete_function_list (Sass_Function_List list); // Setters and getters for callbacks on function lists -ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); -ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); +Sass_Function_Entry sass_function_get_list_entry(Sass_Function_List list, size_t pos); +void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +void sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); +Sass_Import_Entry sass_import_get_list_entry (Sass_Import_List list, size_t idx); // Getters for custom function descriptors -ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); -ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); -ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); +const char* sass_function_get_signature (Sass_Function_Entry cb); +Sass_Function_Fn sass_function_get_function (Sass_Function_Entry cb); +void* sass_function_get_cookie (Sass_Function_Entry cb); + +// Getters for callee entry +const char* sass_callee_get_name (Sass_Callee_Entry); +const char* sass_callee_get_path (Sass_Callee_Entry); +size_t sass_callee_get_line (Sass_Callee_Entry); +size_t sass_callee_get_column (Sass_Callee_Entry); +enum Sass_Callee_Type sass_callee_get_type (Sass_Callee_Entry); +Sass_Env_Frame sass_callee_get_env (Sass_Callee_Entry); + +// Getters and Setters for environments (lexical, local and global) +union Sass_Value* sass_env_get_lexical (Sass_Env_Frame, const char*); +void sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); +union Sass_Value* sass_env_get_local (Sass_Env_Frame, const char*); +void sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); +union Sass_Value* sass_env_get_global (Sass_Env_Frame, const char*); +void sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); ``` ### More links diff --git a/src/libsass/docs/api-importer.md b/src/libsass/docs/api-importer.md index 08ef6cd2e..b6265002e 100755 --- a/src/libsass/docs/api-importer.md +++ b/src/libsass/docs/api-importer.md @@ -5,7 +5,7 @@ By using custom importers, Sass stylesheets can be implemented in any possible w You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). ```C -struct Sass_Import** rv = sass_make_import_list(1); +Sass_Import_Entry* rv = sass_make_import_list(1); rv[0] = sass_make_import(rel, abs, source, srcmap); ``` @@ -31,7 +31,7 @@ struct Sass_C_Import_Descriptor; // Typedef defining the custom importer callback typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); // Typedef defining the importer c function prototype -typedef struct Sass_Import** (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); +typedef Sass_Import_Entry* (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); // Creators for custom importer callback (with some additional pointer) // The pointer is mostly used to store the callback into the actual function @@ -45,38 +45,38 @@ void* sass_import_get_cookie (Sass_C_Import_Callback fn); void sass_delete_importer (Sass_C_Import_Callback fn); // Creator for sass custom importer return argument list -struct Sass_Import** sass_make_import_list (size_t length); +Sass_Import_Entry* sass_make_import_list (size_t length); // Creator for a single import entry returned by the custom importer inside the list -struct Sass_Import* sass_make_import_entry (const char* path, char* source, char* srcmap); -struct Sass_Import* sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); +Sass_Import_Entry sass_make_import_entry (const char* path, char* source, char* srcmap); +Sass_Import_Entry sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); // set error message to abort import and to print out a message (path from existing object is used in output) -struct Sass_Import* sass_import_set_error(struct Sass_Import* import, const char* message, size_t line, size_t col); +Sass_Import_Entry sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); // Setters to insert an entry into the import list (you may also use [] access directly) // Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (struct Sass_Import** list, size_t idx, struct Sass_Import* entry); -struct Sass_Import* sass_import_get_list_entry (struct Sass_Import** list, size_t idx); +void sass_import_set_list_entry (Sass_Import_Entry* list, size_t idx, Sass_Import_Entry entry); +Sass_Import_Entry sass_import_get_list_entry (Sass_Import_Entry* list, size_t idx); // Getters for import entry -const char* sass_import_get_rel_path (struct Sass_Import*); -const char* sass_import_get_abs_path (struct Sass_Import*); -const char* sass_import_get_source (struct Sass_Import*); -const char* sass_import_get_srcmap (struct Sass_Import*); +const char* sass_import_get_imp_path (Sass_Import_Entry); +const char* sass_import_get_abs_path (Sass_Import_Entry); +const char* sass_import_get_source (Sass_Import_Entry); +const char* sass_import_get_srcmap (Sass_Import_Entry); // Explicit functions to take ownership of these items // The property on our struct will be reset to NULL -char* sass_import_take_source (struct Sass_Import*); -char* sass_import_take_srcmap (struct Sass_Import*); +char* sass_import_take_source (Sass_Import_Entry); +char* sass_import_take_srcmap (Sass_Import_Entry); // Getters for import error entries -size_t sass_import_get_error_line (struct Sass_Import*); -size_t sass_import_get_error_column (struct Sass_Import*); -const char* sass_import_get_error_message (struct Sass_Import*); +size_t sass_import_get_error_line (Sass_Import_Entry); +size_t sass_import_get_error_column (Sass_Import_Entry); +const char* sass_import_get_error_message (Sass_Import_Entry); // Deallocator for associated memory (incl. entries) -void sass_delete_import_list (struct Sass_Import**); +void sass_delete_import_list (Sass_Import_Entry*); // Just in case we have some stray import structs -void sass_delete_import (struct Sass_Import*); +void sass_delete_import (Sass_Import_Entry); ``` ### More links diff --git a/src/libsass/docs/api-value.md b/src/libsass/docs/api-value.md index ddf016c17..9ac60f2d1 100755 --- a/src/libsass/docs/api-value.md +++ b/src/libsass/docs/api-value.md @@ -58,7 +58,7 @@ union Sass_Value* sass_make_string (const char* val); union Sass_Value* sass_make_qstring (const char* val); union Sass_Value* sass_make_number (double val, const char* unit); union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); union Sass_Value* sass_make_map (size_t len); union Sass_Value* sass_make_error (const char* msg); union Sass_Value* sass_make_warning (const char* msg); @@ -124,6 +124,8 @@ size_t sass_list_get_length (const union Sass_Value* v); // Getters and setters for Sass_List enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +bool sass_list_get_is_bracketed (const union Sass_Value* v); +void sass_list_set_is_bracketed (union Sass_Value* v, bool value); // Getters and setters for Sass_List values union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); diff --git a/src/libsass/docs/build-on-windows.md b/src/libsass/docs/build-on-windows.md index 7c095fad0..4639d4928 100755 --- a/src/libsass/docs/build-on-windows.md +++ b/src/libsass/docs/build-on-windows.md @@ -130,7 +130,7 @@ cd libsass REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin msbuild /m:4 /p:Configuration=Release win\libsass.sln REM running the spec test-suite manually (needs ruby and minitest gem) -ruby sass-spec\sass-spec.rb -V 3.4 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec +ruby sass-spec\sass-spec.rb -V 3.5 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec cd .. ``` diff --git a/src/libsass/docs/custom-functions-internal.md b/src/libsass/docs/custom-functions-internal.md index 1c19965c6..57fec82b8 100755 --- a/src/libsass/docs/custom-functions-internal.md +++ b/src/libsass/docs/custom-functions-internal.md @@ -45,14 +45,16 @@ The cookie can hold any pointer you want. In the `perl-libsass` implementation i ```C // allocate memory (copies passed strings) -union Sass_Value* make_sass_boolean (int val); -union Sass_Value* make_sass_number (double val, const char* unit); -union Sass_Value* make_sass_color (double r, double g, double b, double a); -union Sass_Value* make_sass_string (const char* val); -union Sass_Value* make_sass_list (size_t len, enum Sass_Separator sep); -union Sass_Value* make_sass_map (size_t len); -union Sass_Value* make_sass_null (); -union Sass_Value* make_sass_error (const char* msg); +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); // Make a deep cloned copy of the given sass value union Sass_Value* sass_clone_value (const union Sass_Value* val); diff --git a/src/libsass/include/sass/base.h b/src/libsass/include/sass/base.h index d88af927f..88dd8d303 100755 --- a/src/libsass/include/sass/base.h +++ b/src/libsass/include/sass/base.h @@ -75,9 +75,6 @@ ADDAPI void ADDCALL sass_free_memory(void* ptr); ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); ADDAPI char* ADDCALL sass_string_unquote (const char* str); -// Resolve a file via the given include paths in the include char* array -ADDAPI char* ADDCALL sass_resolve_file (const char* path, const char* incs[]); - // Implemented sass language version // Hardcoded version 3.4 for time being ADDAPI const char* ADDCALL libsass_version(void); diff --git a/src/libsass/include/sass/context.h b/src/libsass/include/sass/context.h index 0a913c02d..2f88d6888 100755 --- a/src/libsass/include/sass/context.h +++ b/src/libsass/include/sass/context.h @@ -81,8 +81,6 @@ ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options) ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_plugin_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_include_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); @@ -124,6 +122,10 @@ ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); +// Getters for options include path array +ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); + // Calculate the size of the stored null terminated array ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); @@ -143,11 +145,24 @@ ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compil ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); // Push function for paths (no manipulation support for now) ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); +ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); + #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/src/libsass/include/sass/functions.h b/src/libsass/include/sass/functions.h index 2eeb39c4c..ac47e8ede 100755 --- a/src/libsass/include/sass/functions.h +++ b/src/libsass/include/sass/functions.h @@ -11,12 +11,18 @@ extern "C" { // Forward declaration +struct Sass_Env; +struct Sass_Callee; struct Sass_Import; struct Sass_Options; struct Sass_Compiler; struct Sass_Importer; struct Sass_Function; +// Typedef helpers for callee lists +typedef struct Sass_Env (*Sass_Env_Frame); +// Typedef helpers for callee lists +typedef struct Sass_Callee (*Sass_Callee_Entry); // Typedef helpers for import lists typedef struct Sass_Import (*Sass_Import_Entry); typedef struct Sass_Import* (*Sass_Import_List); @@ -34,11 +40,18 @@ typedef struct Sass_Function* (*Sass_Function_List); typedef union Sass_Value* (*Sass_Function_Fn) (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); +// Type of function calls +enum Sass_Callee_Type { + SASS_CALLEE_MIXIN, + SASS_CALLEE_FUNCTION, + SASS_CALLEE_C_FUNCTION, +}; // Creator for sass custom importer return argument list ADDAPI Sass_Importer_List ADDCALL sass_make_importer_list (size_t length); ADDAPI Sass_Importer_Entry ADDCALL sass_importer_get_list_entry (Sass_Importer_List list, size_t idx); ADDAPI void ADDCALL sass_importer_set_list_entry (Sass_Importer_List list, size_t idx, Sass_Importer_Entry entry); +ADDAPI void ADDCALL sass_delete_importer_list (Sass_Importer_List list); // Creators for custom importer callback (with some additional pointer) @@ -66,6 +79,22 @@ ADDAPI Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx); +// Getters for callee entry +ADDAPI const char* ADDCALL sass_callee_get_name (Sass_Callee_Entry); +ADDAPI const char* ADDCALL sass_callee_get_path (Sass_Callee_Entry); +ADDAPI size_t ADDCALL sass_callee_get_line (Sass_Callee_Entry); +ADDAPI size_t ADDCALL sass_callee_get_column (Sass_Callee_Entry); +ADDAPI enum Sass_Callee_Type ADDCALL sass_callee_get_type (Sass_Callee_Entry); +ADDAPI Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry); + +// Getters and Setters for environments (lexical, local and global) +ADDAPI union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame, const char*); +ADDAPI void ADDCALL sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); +ADDAPI union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame, const char*); +ADDAPI void ADDCALL sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); +ADDAPI union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame, const char*); +ADDAPI void ADDCALL sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); + // Getters for import entry ADDAPI const char* ADDCALL sass_import_get_imp_path (Sass_Import_Entry); ADDAPI const char* ADDCALL sass_import_get_abs_path (Sass_Import_Entry); @@ -90,6 +119,8 @@ ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry); // Creators for sass function list and function descriptors ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); +ADDAPI void ADDCALL sass_delete_function (Sass_Function_Entry entry); +ADDAPI void ADDCALL sass_delete_function_list (Sass_Function_List list); // Setters and getters for callbacks on function lists ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); diff --git a/src/libsass/include/sass/values.h b/src/libsass/include/sass/values.h index c00d091ec..9832038b7 100755 --- a/src/libsass/include/sass/values.h +++ b/src/libsass/include/sass/values.h @@ -50,7 +50,7 @@ ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); -ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep); +ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg); @@ -116,6 +116,8 @@ ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); // Getters and setters for Sass_List ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v); ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +ADDAPI bool ADDCALL sass_list_get_is_bracketed (const union Sass_Value* v); +ADDAPI void ADDCALL sass_list_set_is_bracketed (union Sass_Value* v, bool value); // Getters and setters for Sass_List values ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); diff --git a/src/libsass/include/sass/version.h b/src/libsass/include/sass/version.h index 3047c3953..56ea016a2 100755 --- a/src/libsass/include/sass/version.h +++ b/src/libsass/include/sass/version.h @@ -6,7 +6,7 @@ #endif #ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.4" +#define LIBSASS_LANGUAGE_VERSION "3.5" #endif #endif diff --git a/src/libsass/include/sass/version.h.in b/src/libsass/include/sass/version.h.in index 197468b6f..b8d4072d4 100755 --- a/src/libsass/include/sass/version.h.in +++ b/src/libsass/include/sass/version.h.in @@ -6,7 +6,7 @@ #endif #ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.4" +#define LIBSASS_LANGUAGE_VERSION "3.5" #endif #endif diff --git a/src/libsass/script/ci-build-libsass b/src/libsass/script/ci-build-libsass index e2e5b2d2c..a5085fec6 100755 --- a/src/libsass/script/ci-build-libsass +++ b/src/libsass/script/ci-build-libsass @@ -18,9 +18,14 @@ if [ "x$TRAVIS_OS_NAME" == "x" ]; then export TRAVIS_OS_NAME=`uname -s | perl -n if [ "x$COVERAGE" == "xyes" ]; then COVERAGE_OPT="--enable-coverage" - export EXTRA_CFLAGS="--coverage" - export EXTRA_CXXFLAGS="--coverage" - export EXTRA_LDFLAGS="--coverage" + export EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" + export EXTRA_CXXFLAGS="-fprofile-arcs -ftest-coverage" + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + # osx doesn't seem to know gcov lib? + export EXTRA_LDFLAGS="--coverage" + else + export EXTRA_LDFLAGS="-lgcov --coverage" + fi else COVERAGE_OPT="--disable-coverage" fi @@ -37,7 +42,7 @@ fi if [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then MAKE_OPTS="$MAKE_OPTS -j1 V=1" else - MAKE_OPTS="$MAKE_OPTS -j3 V=1" + MAKE_OPTS="$MAKE_OPTS -j5 V=1" fi if [ "x$PREFIX" == "x" ]; then diff --git a/src/libsass/script/ci-build-plugin b/src/libsass/script/ci-build-plugin new file mode 100755 index 000000000..3660c6224 --- /dev/null +++ b/src/libsass/script/ci-build-plugin @@ -0,0 +1,62 @@ +#!/bin/bash + +PLUGIN=$1 +RUBY_BIN=ruby +SASS_SPEC_PATH=sass-spec +SASSC_BIN=sassc/bin/sassc +SASS_SPEC_SPEC_DIR=plugins/libsass-${PLUGIN}/test + +if [ -e ./tester ] ; then + SASSC_BIN=./tester +fi + +if [ -d ./build/lib ] ; then + cp -a build/lib lib +fi + +if [ "x$1" == "x" ] ; then + echo "No plugin name given" + exit 1 +fi + +if [ "x$COVERAGE" == "0" ] ; then + unset COVERAGE +fi + +export EXTRA_CFLAGS="" +export EXTRA_CXXFLAGS="" +if [ "$TRAVIS_OS_NAME" == "osx" ]; then + # osx doesn't seem to know gcov lib? + export EXTRA_LDFLAGS="--coverage" +else + export EXTRA_LDFLAGS="-lgcov --coverage" +fi + +mkdir -p plugins +if [ ! -d plugins/libsass-${PLUGIN} ] ; then + git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} +fi +if [ ! -d plugins/libsass-${PLUGIN}/build ] ; then + mkdir plugins/libsass-${PLUGIN}/build +fi +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi + +cd plugins/libsass-${PLUGIN}/build +cmake -G "Unix Makefiles" -D LIBSASS_DIR="../../.." .. +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi +make VERBOSE=1 -j2 +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi +cd ../../.. + +# glob only works on paths relative to imports +if [ "x$PLUGIN" == "xglob" ]; then + ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss > ${SASS_SPEC_SPEC_DIR}/basic/result.css + ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss --sourcemap > /dev/null +else + cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic > ${SASS_SPEC_SPEC_DIR}/basic/result.css + cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic --sourcemap > /dev/null +fi +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi + +diff ${SASS_SPEC_SPEC_DIR}/basic/expected_output.css ${SASS_SPEC_SPEC_DIR}/basic/result.css +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi diff --git a/src/libsass/script/ci-report-coverage b/src/libsass/script/ci-report-coverage index 4e4c99f03..495cb05cb 100755 --- a/src/libsass/script/ci-report-coverage +++ b/src/libsass/script/ci-report-coverage @@ -2,6 +2,12 @@ if [ "x$COVERAGE" = "xyes" ]; then + # find / -name "gcovr" + # find / -name "coveralls" + # this is only needed for mac os x builds! + PATH=$PATH:/Users/travis/Library/Python/2.7/bin/ + + # exclude some directories from profiling (.libs is from autotools) export EXCLUDE_COVERAGE="--exclude plugins --exclude sassc/sassc.c @@ -21,10 +27,14 @@ if [ "x$COVERAGE" = "xyes" ]; then --exclude src/test --exclude src/posix --exclude src/debugger.hpp" - # debug via gcovr - gcov -v + # debug used gcov version + # option not available on mac + if [ "$TRAVIS_OS_NAME" != "osx" ]; then + gcov -v + fi + # create summarized report gcovr -r . - # generate and submit report to coveralls.io + # submit report to coveralls.io coveralls $EXCLUDE_COVERAGE --gcov-options '\-lp' else diff --git a/src/libsass/src/GNUmakefile.am b/src/libsass/src/GNUmakefile.am index c4c5e08cb..fee9312f9 100755 --- a/src/libsass/src/GNUmakefile.am +++ b/src/libsass/src/GNUmakefile.am @@ -34,7 +34,7 @@ include $(top_srcdir)/Makefile.conf libsass_la_SOURCES = ${CSOURCES} ${SOURCES} -libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 0:9:0 +libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 1:0:0 if ENABLE_TESTS if ENABLE_COVERAGE diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp index 726f0103c..d8c67e339 100755 --- a/src/libsass/src/ast.cpp +++ b/src/libsass/src/ast.cpp @@ -2,6 +2,7 @@ #include "ast.hpp" #include "context.hpp" #include "node.hpp" +#include "eval.hpp" #include "extend.hpp" #include "emitter.hpp" #include "color_maps.hpp" @@ -19,79 +20,33 @@ namespace Sass { static Null sass_null(ParserState("null")); bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { - if (Supports_Operator_Obj op = SASS_MEMORY_CAST(Supports_Operator, cond)) { + if (Supports_Operator_Obj op = Cast(cond)) { return op->operand() != operand(); } - return SASS_MEMORY_CAST(Supports_Negation, cond) != NULL; + return Cast(cond) != NULL; } bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const { - return SASS_MEMORY_CAST(Supports_Negation, cond) || - SASS_MEMORY_CAST(Supports_Operator, cond); + return Cast(cond) || + Cast(cond); } - size_t HashExpression::operator() (Expression_Obj ex) const { - return ex ? ex->hash() : 0; - } - - size_t HashSimpleSelector::operator() (Simple_Selector_Obj ex) const { - return ex ? ex->hash() : 0; - } - - - bool CompareExpression::operator()(const Expression_Obj& lhs, const Expression_Obj& rhs) const { - return lhs && rhs && lhs->eq(*rhs); - } - - bool CompareSimpleSelector::operator()(Simple_Selector_Obj lhs, Simple_Selector_Obj rhs) const { - return &lhs && &rhs && *lhs == *rhs; - } - - std::string & str_ltrim(std::string & str) - { - auto it2 = std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace(ch , std::locale::classic() ) ; } ); - str.erase( str.begin() , it2); - return str; - } - - std::string & str_rtrim(std::string & str) + void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v") { - auto it1 = std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace(ch , std::locale::classic() ) ; } ); - str.erase( it1.base() , str.end() ); - return str; + str.erase( str.find_last_not_of( delimiters ) + 1 ); } void String_Constant::rtrim() { - value_ = str_rtrim(value_); - } - void String_Constant::ltrim() - { - value_ = str_ltrim(value_); - } - void String_Constant::trim() - { - rtrim(); - ltrim(); + str_rtrim(value_); } void String_Schema::rtrim() { if (!empty()) { - if (String_Ptr str = SASS_MEMORY_CAST(String, last())) str->rtrim(); - } - } - void String_Schema::ltrim() - { - if (!empty()) { - if (String_Ptr str = SASS_MEMORY_CAST(String, first())) str->ltrim(); + if (String_Ptr str = Cast(last())) str->rtrim(); } } - void String_Schema::trim() - { - rtrim(); - ltrim(); - } void Argument::set_delayed(bool delayed) { @@ -111,7 +66,7 @@ namespace Sass { bool At_Root_Query::exclude(std::string str) { bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; - List_Ptr l = static_cast(&value()); + List_Ptr l = static_cast(value().ptr()); std::string v; if (with) @@ -141,21 +96,20 @@ namespace Sass { pstate_.offset += pstate - pstate_ + pstate.offset; } - void AST_Node::set_pstate_offset(const Offset& offset) - { - pstate_.offset = offset; - } - - inline bool is_ns_eq(const std::string& l, const std::string& r) + bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const { - if (l.empty() && r.empty()) return true; - else if (l.empty() && r == "*") return true; - else if (r.empty() && l == "*") return true; - else return l == r; + // https://github.com/sass/sass/issues/2229 + if ((has_ns_ == r.has_ns_) || + (has_ns_ && ns_.empty()) || + (r.has_ns_ && r.ns_.empty()) + ) { + if (ns_.empty() && r.ns() == "*") return false; + else if (r.ns().empty() && ns() == "*") return false; + else return ns() == r.ns(); + } + return false; } - - bool Compound_Selector::operator< (const Compound_Selector& rhs) const { size_t L = std::min(length(), rhs.length()); @@ -173,7 +127,7 @@ namespace Sass { return length() < rhs.length(); } - bool Compound_Selector::has_parent_ref() + bool Compound_Selector::has_parent_ref() const { for (Simple_Selector_Obj s : *this) { if (s && s->has_parent_ref()) return true; @@ -181,7 +135,7 @@ namespace Sass { return false; } - bool Compound_Selector::has_real_parent_ref() + bool Compound_Selector::has_real_parent_ref() const { for (Simple_Selector_Obj s : *this) { if (s && s->has_real_parent_ref()) return true; @@ -189,13 +143,13 @@ namespace Sass { return false; } - bool Complex_Selector::has_parent_ref() + bool Complex_Selector::has_parent_ref() const { return (head() && head()->has_parent_ref()) || (tail() && tail()->has_parent_ref()); } - bool Complex_Selector::has_real_parent_ref() + bool Complex_Selector::has_real_parent_ref() const { return (head() && head()->has_real_parent_ref()) || (tail() && tail()->has_real_parent_ref()); @@ -206,25 +160,31 @@ namespace Sass { // const iterators for tails Complex_Selector_Ptr_Const l = this; Complex_Selector_Ptr_Const r = &rhs; - Compound_Selector_Ptr l_h = l ? &l->head() : 0; - Compound_Selector_Ptr r_h = r ? &r->head() : 0; + Compound_Selector_Ptr l_h = NULL; + Compound_Selector_Ptr r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); // process all tails while (true) { + #ifdef DEBUG // skip empty ancestor first if (l && l->is_empty_ancestor()) { - l = &l->tail(); - l_h = l ? &l->head() : 0; + l_h = NULL; + l = l->tail(); + if(l) l_h = l->head(); continue; } // skip empty ancestor first if (r && r->is_empty_ancestor()) { - r = &r->tail(); - r_h = r ? &r->head() : 0; + r_h = NULL; + r = r->tail(); + if (r) r_h = r->head(); continue; } + #endif // check for valid selectors if (!l) return !!r; if (!r) return false; @@ -235,11 +195,12 @@ namespace Sass { if (l->combinator() != r->combinator()) { return l->combinator() < r->combinator(); } // advance to next tails - l = &l->tail(); - r = &r->tail(); + l = l->tail(); + r = r->tail(); // fetch the next headers - l_h = l ? &l->head() : 0; - r_h = r ? &r->head() : 0; + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); } // one side is null else if (!r_h) return true; @@ -251,11 +212,12 @@ namespace Sass { if (l->combinator() != r->combinator()) { return l->combinator() < r->combinator(); } // advance to next tails - l = &l->tail(); - r = &r->tail(); + l = l->tail(); + r = r->tail(); // fetch the next headers - l_h = l ? &l->head() : 0; - r_h = r ? &r->head() : 0; + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); } // heads are not equal else return *l_h < *r_h; @@ -268,25 +230,31 @@ namespace Sass { // const iterators for tails Complex_Selector_Ptr_Const l = this; Complex_Selector_Ptr_Const r = &rhs; - Compound_Selector_Ptr l_h = l ? &l->head() : 0; - Compound_Selector_Ptr r_h = r ? &r->head() : 0; + Compound_Selector_Ptr l_h = NULL; + Compound_Selector_Ptr r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); // process all tails while (true) { + #ifdef DEBUG // skip empty ancestor first if (l && l->is_empty_ancestor()) { - l = &l->tail(); - l_h = l ? &l->head() : 0; + l_h = NULL; + l = l->tail(); + if (l) l_h = l->head(); continue; } // skip empty ancestor first if (r && r->is_empty_ancestor()) { - r = &r->tail(); - r_h = r ? &r->head() : 0; + r_h = NULL; + r = r->tail(); + if (r) r_h = r->head(); continue; } + #endif // check the pointers if (!r) return !l; if (!l) return !r; @@ -297,11 +265,12 @@ namespace Sass { if (l->combinator() != r->combinator()) { return l->combinator() < r->combinator(); } // advance to next tails - l = &l->tail(); - r = &r->tail(); + l = l->tail(); + r = r->tail(); // fetch the next heads - l_h = l ? &l->head() : 0; - r_h = r ? &r->head() : 0; + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); } // equals if other head is empty else if ((!l_h && !r_h) || @@ -313,11 +282,12 @@ namespace Sass { if (l->combinator() != r->combinator()) { return l->combinator() == r->combinator(); } // advance to next tails - l = &l->tail(); - r = &r->tail(); + l = l->tail(); + r = r->tail(); // fetch the next heads - l_h = l ? &l->head() : 0; - r_h = r ? &r->head() : 0; + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); } // abort else break; @@ -333,71 +303,102 @@ namespace Sass { for (size_t i = 0, L = length(); i < L; ++i) { if (unified.isNull()) break; - unified = at(i)->unify_with(&unified, ctx); + unified = at(i)->unify_with(unified, ctx); } return unified.detach(); } - bool Selector::operator== (const Selector& rhs) const + bool Complex_Selector::operator== (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; + throw std::runtime_error("invalid selector base classes to compare"); + return false; + } + + + bool Complex_Selector::operator< (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; + throw std::runtime_error("invalid selector base classes to compare"); + return false; + } + + bool Compound_Selector::operator== (const Selector& rhs) const { - if (Selector_List_Ptr_Const sl = dynamic_cast(this)) return *sl == rhs; - if (Simple_Selector_Ptr_Const sp = dynamic_cast(this)) return *sp == rhs; + if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); return false; } - bool Selector::operator< (const Selector& rhs) const + bool Compound_Selector::operator< (const Selector& rhs) const { - if (Selector_List_Ptr_Const sl = dynamic_cast(this)) return *sl < rhs; - if (Simple_Selector_Ptr_Const sp = dynamic_cast(this)) return *sp < rhs; + if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; + throw std::runtime_error("invalid selector base classes to compare"); + return false; + } + + bool Selector_Schema::operator== (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; + throw std::runtime_error("invalid selector base classes to compare"); + return false; + } + + bool Selector_Schema::operator< (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); return false; } bool Simple_Selector::operator== (const Selector& rhs) const { - if (Simple_Selector_Ptr_Const sp = dynamic_cast(&rhs)) return *this == *sp; + if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this == *sp; return false; } bool Simple_Selector::operator< (const Selector& rhs) const { - if (Simple_Selector_Ptr_Const sp = dynamic_cast(&rhs)) return *this < *sp; + if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this < *sp; return false; } bool Simple_Selector::operator== (const Simple_Selector& rhs) const { - Simple_Type type = simple_type(); - // dynamic cast is a bottleneck - use concrete type as types are final - if (type == PSEUDO_SEL /* Pseudo_Selector_Ptr_Const lp = dynamic_cast(this) */) { - return *static_cast(this) == rhs; - } - else if (type == WRAPPED_SEL /* Wrapped_Selector_Ptr_Const lw = dynamic_cast(this) */) { - return *static_cast(this) == rhs; - } - else if (type == ATTR_SEL /* Attribute_Selector_Ptr_Const la = dynamic_cast(this) */) { - return *static_cast(this) == rhs; - } + // solve the double dispatch problem by using RTTI information via dynamic cast + if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (name_ == rhs.name_) - { return is_ns_eq(ns_, rhs.ns_); } + { return is_ns_eq(rhs); } else return false; } bool Simple_Selector::operator< (const Simple_Selector& rhs) const { - Simple_Type type = simple_type(); - // dynamic cast is a bottleneck - use concrete type as types are final - if (type == PSEUDO_SEL /* Pseudo_Selector_Ptr_Const lp = dynamic_cast(this) */) { - return *static_cast(this) < rhs; - } - else if (type == WRAPPED_SEL /* Wrapped_Selector_Ptr_Const lw = dynamic_cast(this) */) { - return *static_cast(this) < rhs; - } - else if (type == ATTR_SEL /* Attribute_Selector_Ptr_Const la = dynamic_cast(this) */) { - return *static_cast(this) < rhs; - } - if (is_ns_eq(ns_, rhs.ns_)) + // solve the double dispatch problem by using RTTI information via dynamic cast + if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs < rhs; } + else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs < rhs; } + else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs < rhs; } + if (is_ns_eq(rhs)) { return name_ < rhs.name_; } return ns_ < rhs.ns_; } @@ -405,9 +406,9 @@ namespace Sass { bool Selector_List::operator== (const Selector& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast - if (Selector_List_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } - else if (Complex_Selector_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } - else if (Compound_Selector_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + if (Selector_List_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } + else if (Complex_Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } + else if (Compound_Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } // no compare method return this == &rhs; } @@ -416,8 +417,8 @@ namespace Sass { bool Selector_List::operator==(const Expression& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast - if (List_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } - if (Selector_Ptr_Const ls = dynamic_cast(&rhs)) { return *this == *ls; } + if (List_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } + if (Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } // compare invalid (maybe we should error?) return false; } @@ -431,8 +432,8 @@ namespace Sass { // create temporary vectors and sort them std::vector l_lst = this->elements(); std::vector r_lst = rhs.elements(); - std::sort(l_lst.begin(), l_lst.end(), cmp_complex_selector()); - std::sort(r_lst.begin(), r_lst.end(), cmp_complex_selector()); + std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); + std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); // process loop while (true) { @@ -457,17 +458,18 @@ namespace Sass { bool Selector_List::operator< (const Selector& rhs) const { - if (Selector_List_Ptr_Const sp = dynamic_cast(&rhs)) return *this < *sp; + if (Selector_List_Ptr_Const sp = Cast(&rhs)) return *this < *sp; return false; } bool Selector_List::operator< (const Selector_List& rhs) const { - if (this->length() != rhs.length()) return false; - for (size_t i = 0; i < rhs.length(); i ++) { - if (!(*at(i) < *rhs.at(i))) return false; + size_t l = rhs.length(); + if (length() < l) l = length(); + for (size_t i = 0; i < l; i ++) { + if (*at(i) < *rhs.at(i)) return true; } - return true; + return false; } Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) @@ -482,7 +484,7 @@ namespace Sass { { for (i = 0, L = rhs->length(); i < L; ++i) { - if ((SASS_MEMORY_CAST(Pseudo_Selector, (*rhs)[i]) || SASS_MEMORY_CAST(Wrapped_Selector, (*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) + if ((Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) { found = true; break; } } } @@ -490,7 +492,7 @@ namespace Sass { { for (i = 0, L = rhs->length(); i < L; ++i) { - if (SASS_MEMORY_CAST(Pseudo_Selector, (*rhs)[i]) || SASS_MEMORY_CAST(Wrapped_Selector, (*rhs)[i])) + if (Cast((*rhs)[i]) || Cast((*rhs)[i])) { found = true; break; } } } @@ -544,18 +546,18 @@ namespace Sass { return rhs; } - Simple_Selector_Ptr rhs_0 = &rhs->at(0); + Simple_Selector_Ptr rhs_0 = rhs->at(0); // otherwise, this is a tag name if (name() == "*") { if (typeid(*rhs_0) == typeid(Element_Selector)) { // if rhs is universal, just return this tagname + rhs's qualifiers - Element_Selector_Ptr ts = SASS_MEMORY_CAST_PTR(Element_Selector, rhs_0); + Element_Selector_Ptr ts = Cast(rhs_0); rhs->at(0) = this->unify_with(ts, ctx); return rhs; } - else if (SASS_MEMORY_CAST_PTR(Class_Selector, rhs_0) || SASS_MEMORY_CAST_PTR(Id_Selector, rhs_0)) { + else if (Cast(rhs_0) || Cast(rhs_0)) { // qualifier is `.class`, so we can prefix with `ns|*.class` if (has_ns() && !rhs_0->has_ns()) { if (ns() != "*") rhs->elements().insert(rhs->begin(), this); @@ -591,7 +593,7 @@ namespace Sass { { for (size_t i = 0, L = rhs->length(); i < L; ++i) { - if (Id_Selector_Ptr sel = SASS_MEMORY_CAST(Id_Selector, rhs->at(i))) { + if (Id_Selector_Ptr sel = Cast(rhs->at(i))) { if (sel->name() != name()) return 0; } } @@ -605,7 +607,7 @@ namespace Sass { { for (size_t i = 0, L = rhs->length(); i < L; ++i) { - if (Pseudo_Selector_Ptr sel = SASS_MEMORY_CAST(Pseudo_Selector, rhs->at(i))) { + if (Pseudo_Selector_Ptr sel = Cast(rhs->at(i))) { if (sel->is_pseudo_element() && sel->name() != name()) return 0; } } @@ -615,30 +617,27 @@ namespace Sass { bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const { - if (is_ns_eq(ns(), rhs.ns())) { + if (is_ns_eq(rhs)) { if (name() == rhs.name()) { if (matcher() == rhs.matcher()) { bool no_lhs_val = value().isNull(); bool no_rhs_val = rhs.value().isNull(); - if (no_lhs_val && no_rhs_val) { - return true; - } - if (!no_lhs_val && !no_rhs_val) { - return *value() < *rhs.value(); - } + if (no_lhs_val && no_rhs_val) return false; // equal + else if (no_lhs_val) return true; // lhs is null + else if (no_rhs_val) return false; // rhs is null + return *value() < *rhs.value(); // both are given } else { return matcher() < rhs.matcher(); } } else { return name() < rhs.name(); } - } - return false; + } else { return ns() < rhs.ns(); } } bool Attribute_Selector::operator< (const Simple_Selector& rhs) const { - if (Attribute_Selector_Ptr_Const w = dynamic_cast(&rhs)) + if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } - if (is_ns_eq(ns(), rhs.ns())) + if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } @@ -652,13 +651,13 @@ namespace Sass { if (no_lhs_val && no_rhs_val) { return (name() == rhs.name()) && (matcher() == rhs.matcher()) - && (is_ns_eq(ns(), rhs.ns())); + && (is_ns_eq(rhs)); } // both are defined, evaluate if (no_lhs_val == no_rhs_val) { return (name() == rhs.name()) && (matcher() == rhs.matcher()) - && (is_ns_eq(ns(), rhs.ns())) + && (is_ns_eq(rhs)) && (*value() == *rhs.value()); } // not equal @@ -668,97 +667,94 @@ namespace Sass { bool Attribute_Selector::operator== (const Simple_Selector& rhs) const { - if (Attribute_Selector_Ptr_Const w = dynamic_cast(&rhs)) + if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) { return *this == *w; } - if (is_ns_eq(ns(), rhs.ns())) - { return name() == rhs.name(); } - return ns() == rhs.ns(); + return is_ns_eq(rhs) && + name() == rhs.name(); } bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const { - if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + if (is_ns_eq(rhs) && name() == rhs.name()) { String_Obj lhs_ex = expression(); String_Obj rhs_ex = rhs.expression(); if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; - else return lhs_ex == rhs_ex; + else return lhs_ex.ptr() == rhs_ex.ptr(); } else return false; } bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const { - if (Pseudo_Selector_Ptr_Const w = dynamic_cast(&rhs)) + if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) { return *this == *w; } - if (is_ns_eq(ns(), rhs.ns())) - { return name() == rhs.name(); } - return ns() == rhs.ns(); + return is_ns_eq(rhs) && + name() == rhs.name(); } bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const { - if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + if (is_ns_eq(rhs) && name() == rhs.name()) { String_Obj lhs_ex = expression(); String_Obj rhs_ex = rhs.expression(); if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; - else return lhs_ex < rhs_ex; + else return lhs_ex.ptr() < rhs_ex.ptr(); } - if (is_ns_eq(ns(), rhs.ns())) + if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const { - if (Pseudo_Selector_Ptr_Const w = dynamic_cast(&rhs)) + if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } - if (is_ns_eq(ns(), rhs.ns())) + if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const { - if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + if (is_ns_eq(rhs) && name() == rhs.name()) { return *(selector()) == *(rhs.selector()); } else return false; } bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const { - if (Wrapped_Selector_Ptr_Const w = dynamic_cast(&rhs)) + if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) { return *this == *w; } - if (is_ns_eq(ns(), rhs.ns())) - { return name() == rhs.name(); } - return ns() == rhs.ns(); + return is_ns_eq(rhs) && + name() == rhs.name(); } bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const { - if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name()) + if (is_ns_eq(rhs) && name() == rhs.name()) { return *(selector()) < *(rhs.selector()); } - if (is_ns_eq(ns(), rhs.ns())) + if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const { - if (Wrapped_Selector_Ptr_Const w = dynamic_cast(&rhs)) + if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } - if (is_ns_eq(ns(), rhs.ns())) + if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } @@ -767,28 +763,26 @@ namespace Sass { { if (this->name() != sub->name()) return false; if (this->name() == ":current") return false; - if (Selector_List_Obj rhs_list = SASS_MEMORY_CAST(Selector_List, sub->selector())) { - if (Selector_List_Obj lhs_list = SASS_MEMORY_CAST(Selector_List, selector())) { + if (Selector_List_Obj rhs_list = Cast(sub->selector())) { + if (Selector_List_Obj lhs_list = Cast(selector())) { return lhs_list->is_superselector_of(rhs_list); } - error("is_superselector expected a Selector_List", sub->pstate()); - } else { - error("is_superselector expected a Selector_List", sub->pstate()); } + error("is_superselector expected a Selector_List", sub->pstate()); return false; } bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped) { for (Complex_Selector_Obj item : rhs->elements()) { - if (is_superselector_of(&item, wrapped)) return true; + if (is_superselector_of(item, wrapped)) return true; } return false; } bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped) { - if (rhs->head()) return is_superselector_of(&rhs->head(), wrapped); + if (rhs->head()) return is_superselector_of(rhs->head(), wrapped); return false; } @@ -821,6 +815,9 @@ namespace Sass { return false; } + // would like to replace this without stringification + // https://github.com/sass/sass/issues/2229 + // SimpleSelectorSet lset, rset; std::set lset, rset; if (lbase && rbase) @@ -837,11 +834,11 @@ namespace Sass { for (size_t i = 0, iL = length(); i < iL; ++i) { - Selector_Obj lhs = &(*this)[i]; + Selector_Obj lhs = (*this)[i]; // very special case for wrapped matches selector - if (Wrapped_Selector_Obj wrapped = SASS_MEMORY_CAST(Wrapped_Selector, lhs)) { + if (Wrapped_Selector_Obj wrapped = Cast(lhs)) { if (wrapped->name() == ":not") { - if (Selector_List_Obj not_list = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + if (Selector_List_Obj not_list = Cast(wrapped->selector())) { if (not_list->is_superselector_of(rhs, wrapped->name())) return false; } else { throw std::runtime_error("wrapped not selector is not a list"); @@ -849,8 +846,8 @@ namespace Sass { } if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { lhs = wrapped->selector(); - if (Selector_List_Obj list = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { - if (Compound_Selector_Obj comp = SASS_MEMORY_CAST(Compound_Selector, rhs)) { + if (Selector_List_Obj list = Cast(wrapped->selector())) { + if (Compound_Selector_Obj comp = Cast(rhs)) { if (!wrapping.empty() && wrapping != wrapped->name()) return false; if (wrapping.empty() || wrapping != wrapped->name()) {; if (list->is_superselector_of(comp, wrapped->name())) return true; @@ -858,8 +855,9 @@ namespace Sass { } } } - Simple_Selector_Ptr rhs_sel = rhs->elements().size() > i ? &(*rhs)[i] : 0; - if (Wrapped_Selector_Ptr wrapped_r = dynamic_cast(rhs_sel)) { + Simple_Selector_Ptr rhs_sel = NULL; + if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; + if (Wrapped_Selector_Ptr wrapped_r = Cast(rhs_sel)) { if (wrapped->name() == wrapped_r->name()) { if (wrapped->is_superselector_of(wrapped_r)) { continue; @@ -874,10 +872,10 @@ namespace Sass { for (size_t n = 0, nL = rhs->length(); n < nL; ++n) { - Selector_Obj r = &(*rhs)[n]; - if (Wrapped_Selector_Obj wrapped = SASS_MEMORY_CAST(Wrapped_Selector, r)) { + Selector_Obj r = (*rhs)[n]; + if (Wrapped_Selector_Obj wrapped = Cast(r)) { if (wrapped->name() == ":not") { - if (Selector_List_Obj ls = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + if (Selector_List_Obj ls = Cast(wrapped->selector())) { ls->remove_parent_selectors(); if (is_superselector_of(ls, wrapped->name())) return false; } @@ -886,7 +884,7 @@ namespace Sass { if (!wrapping.empty()) { if (wrapping != wrapped->name()) return false; } - if (Selector_List_Obj ls = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) { + if (Selector_List_Obj ls = Cast(wrapped->selector())) { ls->remove_parent_selectors(); return (is_superselector_of(ls, wrapped->name())); } @@ -941,7 +939,7 @@ namespace Sass { SASS_ASSERT(r_last_head, "rhs head is null"); // get the unification of the last compound selectors - Compound_Selector_Obj unified = r_last_head->unify_with(&l_last_head, ctx); + Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head, ctx); // abort if we could not unify heads if (unified == 0) return 0; @@ -966,7 +964,7 @@ namespace Sass { { // create some temporaries to convert to node Complex_Selector_Obj fake = unified->to_complex(); - Node unified_node = complexSelectorToNode(&fake, ctx); + Node unified_node = complexSelectorToNode(fake, ctx); // add to permutate the list? rhsNode.plus(unified_node); } @@ -992,8 +990,8 @@ namespace Sass { // create temporary vectors and sort them std::vector l_lst = this->elements(); std::vector r_lst = rhs.elements(); - std::sort(l_lst.begin(), l_lst.end(), cmp_simple_selector()); - std::sort(r_lst.begin(), r_lst.end(), cmp_simple_selector()); + std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); + std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); // process loop while (true) { @@ -1016,10 +1014,6 @@ namespace Sass { return true; } - bool Complex_Selector_Pointer_Compare::operator() (const Complex_Selector_Obj& pLeft, const Complex_Selector_Obj& pRight) const { - return *pLeft < *pRight; - } - bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) { return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); @@ -1043,7 +1037,7 @@ namespace Sass { { return false; } if (l_len == 1) - { return lhs->head()->is_superselector_of(&rhs->last()->head(), wrapping); } + { return lhs->head()->is_superselector_of(rhs->last()->head(), wrapping); } // we have to look one tail deeper, since we cary the // combinator around for it (which is important here) @@ -1054,7 +1048,7 @@ namespace Sass { if (lhs_tail->head() && !rhs_tail->head()) return false; if (!lhs_tail->head() && rhs_tail->head()) return false; if (lhs_tail->head() && rhs_tail->head()) { - if (!lhs_tail->head()->is_superselector_of(&rhs_tail->head())) return false; + if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; } } @@ -1063,7 +1057,7 @@ namespace Sass { for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (i == L-1) { return false; } - if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(&marker->head(), wrapping)) + if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping)) { found = true; break; } marker = marker->tail(); } @@ -1089,17 +1083,17 @@ namespace Sass { { return false; } if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) { return false; } - return lhs->tail()->is_superselector_of(&marker->tail()); + return lhs->tail()->is_superselector_of(marker->tail()); } else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) { if (marker->combinator() != Complex_Selector::PARENT_OF) { return false; } - return lhs->tail()->is_superselector_of(&marker->tail()); + return lhs->tail()->is_superselector_of(marker->tail()); } else { - return lhs->tail()->is_superselector_of(&marker->tail()); + return lhs->tail()->is_superselector_of(marker->tail()); } // catch-all return false; @@ -1112,15 +1106,6 @@ namespace Sass { return 1 + tail()->length(); } - Complex_Selector_Obj Complex_Selector::context(Context& ctx) - { - if (!tail()) return 0; - if (!head()) return tail()->context(ctx); - Complex_Selector_Obj cpy = SASS_MEMORY_NEW(Complex_Selector, pstate(), combinator(), head(), tail()->context(ctx)); - cpy->media_block(media_block()); - return cpy; - } - // append another complex selector at the end // check if we need to append some headers // then we need to check for the combinator @@ -1143,43 +1128,43 @@ namespace Sass { } else if (last()->head_ && last()->head_->length()) { Compound_Selector_Obj rh = last()->head(); size_t i = 0, L = h->length(); - if (SASS_MEMORY_CAST(Element_Selector, h->first())) { - if (Class_Selector_Ptr sq = SASS_MEMORY_CAST(Class_Selector, rh->last())) { + if (Cast(h->first())) { + if (Class_Selector_Ptr sq = Cast(rh->last())) { Class_Selector_Ptr sqs = SASS_MEMORY_COPY(sq); sqs->name(sqs->name() + (*h)[0]->name()); sqs->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = sqs; rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append(&(*h)[i]); - } else if (Id_Selector_Ptr sq = SASS_MEMORY_CAST(Id_Selector, rh->last())) { + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else if (Id_Selector_Ptr sq = Cast(rh->last())) { Id_Selector_Ptr sqs = SASS_MEMORY_COPY(sq); sqs->name(sqs->name() + (*h)[0]->name()); sqs->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = sqs; rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append(&(*h)[i]); - } else if (Element_Selector_Ptr ts = SASS_MEMORY_CAST(Element_Selector, rh->last())) { + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else if (Element_Selector_Ptr ts = Cast(rh->last())) { Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts); tss->name(tss->name() + (*h)[0]->name()); tss->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = tss; rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append(&(*h)[i]); - } else if (Placeholder_Selector_Ptr ps = SASS_MEMORY_CAST(Placeholder_Selector, rh->last())) { + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else if (Placeholder_Selector_Ptr ps = Cast(rh->last())) { Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps); pss->name(pss->name() + (*h)[0]->name()); pss->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = pss; rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append(&(*h)[i]); + for (i = 1; i < L; ++i) rh->append((*h)[i]); } else { - last()->head_->concat(&h); + last()->head_->concat(h); } } else { - last()->head_->concat(&h); + last()->head_->concat(h); } } else { - last()->head_->concat(&h); + last()->head_->concat(h); } } else { // std::cerr << "has no or empty head\n"; @@ -1201,18 +1186,25 @@ namespace Sass { } } + } + Selector_List_Obj Selector_List::eval(Eval& eval) + { + Selector_List_Obj list = schema() ? + eval(schema()) : eval(this); + list->schema(schema()); + return list; } Selector_List_Ptr Selector_List::resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent) { if (!this->has_parent_ref()) return this; Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); - Selector_List_Ptr ps = &pstack.back(); + Selector_List_Ptr ps = pstack.back(); for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { for (size_t si = 0, sL = this->length(); si < sL; ++si) { Selector_List_Obj rv = at(si)->resolve_parent_refs(ctx, pstack, implicit_parent); - ss->concat(&rv); + ss->concat(rv); } } return ss; @@ -1222,7 +1214,7 @@ namespace Sass { { Complex_Selector_Obj tail = this->tail(); Compound_Selector_Obj head = this->head(); - Selector_List_Ptr parents = &pstack.back(); + Selector_List_Ptr parents = pstack.back(); if (!this->has_real_parent_ref() && !implicit_parent) { Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate()); @@ -1238,7 +1230,7 @@ namespace Sass { Selector_List_Obj retval; // we have a parent selector in a simple compound list // mix parent complex selector into the compound list - if (SASS_MEMORY_CAST(Parent_Selector, (*head)[0])) { + if (Cast((*head)[0])) { retval = SASS_MEMORY_NEW(Selector_List, pstate()); // it turns out that real parent references reach @@ -1246,7 +1238,7 @@ namespace Sass { if (parents == NULL && head->has_real_parent_ref()) { int i = pstack.size() - 1; while (!parents && i > -1) { - parents = &pstack.at(i--); + parents = pstack.at(i--); } } @@ -1258,11 +1250,15 @@ namespace Sass { Complex_Selector_Obj parent = (*parents)[i]; Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - ss->tail(t ? SASS_MEMORY_CLONE(t) : 0); + ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL); Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); // remove parent selector from sequence - if (h->length()) h->erase(h->begin()); - ss->head(h->length() ? &h : 0); + if (h->length()) { + h->erase(h->begin()); + ss->head(h); + } else { + ss->head(NULL); + } // adjust for parent selector (1 char) if (h->length()) { ParserState state(h->at(0)->pstate()); @@ -1288,13 +1284,17 @@ namespace Sass { // this is only if valid if the parent has no trailing op // otherwise we cannot append more simple selectors to head if (parent->last()->combinator() != ANCESTOR_OF) { - throw Exception::InvalidParent(&parent, &ss); + throw Exception::InvalidParent(parent, ss); } - ss->tail(tail ? SASS_MEMORY_CLONE(tail) : 0); + ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); // remove parent selector from sequence - if (h->length()) h->erase(h->begin()); - ss->head(h->length() ? &h : 0); + if (h->length()) { + h->erase(h->begin()); + ss->head(h); + } else { + ss->head(NULL); + } // \/ IMO ruby sass bug \/ ss->has_line_feed(false); // adjust for parent selector (1 char) @@ -1307,7 +1307,7 @@ namespace Sass { // keep old parser state s->pstate(pstate()); // append new tail - s->append(ctx, &ss); + s->append(ctx, ss); retval->append(s); } } @@ -1320,7 +1320,7 @@ namespace Sass { cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append(&(*this->head())[i]); + cpy->head()->append((*this->head())[i]); if (!cpy->head()->length()) cpy->head(0); retval->append(cpy->skip_empty_reference()); } @@ -1330,7 +1330,7 @@ namespace Sass { Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append(&(*this->head())[i]); + cpy->head()->append((*this->head())[i]); if (!cpy->head()->length()) cpy->head(0); retval->append(cpy->skip_empty_reference()); } @@ -1338,12 +1338,12 @@ namespace Sass { } // no parent selector in head else { - retval = this->tails(ctx, &tails); + retval = this->tails(ctx, tails); } for (Simple_Selector_Obj ss : head->elements()) { - if (Wrapped_Selector_Ptr ws = SASS_MEMORY_CAST(Wrapped_Selector, ss)) { - if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) { + if (Wrapped_Selector_Ptr ws = Cast(ss)) { + if (Selector_List_Ptr sl = Cast(ws->selector())) { if (parents) ws->selector(sl->resolve_parent_refs(ctx, pstack, implicit_parent)); } } @@ -1354,7 +1354,7 @@ namespace Sass { } // has no head else { - return this->tails(ctx, &tails); + return this->tails(ctx, tails); } // unreachable @@ -1389,21 +1389,27 @@ namespace Sass { // get the head head = cur->head_; // abort (and return) if it is not a parent selector - if (!head || head->length() != 1 || !SASS_MEMORY_CAST(Parent_Selector, (*head)[0])) { + if (!head || head->length() != 1 || !Cast((*head)[0])) { break; } // advance to next cur = cur->tail_; } // result - return &cur; + return cur; } // return the last tail that is defined Complex_Selector_Obj Complex_Selector::last() { - // ToDo: implement with a while loop - return tail_? tail_->last() : this; + Complex_Selector_Ptr cur = this; + Complex_Selector_Ptr nxt = cur; + // loop until last + while (nxt) { + cur = nxt; + nxt = cur->tail(); + } + return cur; } Complex_Selector::Combinator Complex_Selector::clear_innermost() @@ -1474,7 +1480,31 @@ namespace Sass { } } - bool Selector_List::has_parent_ref() + size_t Wrapped_Selector::hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + if (selector_) hash_combine(hash_, selector_->hash()); + } + return hash_; + } + bool Wrapped_Selector::has_parent_ref() const { + // if (has_reference()) return true; + if (!selector()) return false; + return selector()->has_parent_ref(); + } + bool Wrapped_Selector::has_real_parent_ref() const { + // if (has_reference()) return true; + if (!selector()) return false; + return selector()->has_real_parent_ref(); + } + unsigned long Wrapped_Selector::specificity() const + { + return selector_ ? selector_->specificity() : 0; + } + + + bool Selector_List::has_parent_ref() const { for (Complex_Selector_Obj s : elements()) { if (s && s->has_parent_ref()) return true; @@ -1482,7 +1512,7 @@ namespace Sass { return false; } - bool Selector_List::has_real_parent_ref() + bool Selector_List::has_real_parent_ref() const { for (Complex_Selector_Obj s : elements()) { if (s && s->has_real_parent_ref()) return true; @@ -1490,18 +1520,18 @@ namespace Sass { return false; } - bool Selector_Schema::has_parent_ref() + bool Selector_Schema::has_parent_ref() const { - if (String_Schema_Obj schema = SASS_MEMORY_CAST(String_Schema, contents())) { - return schema->length() > 0 && SASS_MEMORY_CAST(Parent_Selector, schema->at(0)) != NULL; + if (String_Schema_Obj schema = Cast(contents())) { + return schema->length() > 0 && Cast(schema->at(0)) != NULL; } return false; } - bool Selector_Schema::has_real_parent_ref() + bool Selector_Schema::has_real_parent_ref() const { - if (String_Schema_Obj schema = SASS_MEMORY_CAST(String_Schema, contents())) { - Parent_Selector_Obj p = SASS_MEMORY_CAST(Parent_Selector, schema->at(0)); + if (String_Schema_Obj schema = Cast(contents())) { + Parent_Selector_Obj p = Cast(schema->at(0)); return schema->length() > 0 && p && p->is_real_parent_ref(); } return false; @@ -1518,7 +1548,7 @@ namespace Sass { { // Check every rhs selector against left hand list for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of(&(*sub)[i], wrapping)) return false; + if (!is_superselector_of((*sub)[i], wrapping)) return false; } return true; } @@ -1529,7 +1559,7 @@ namespace Sass { { // Check every rhs selector against left hand list for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of(&(*sub)[i], wrapping)) return false; + if (!is_superselector_of((*sub)[i], wrapping)) return false; } return true; } @@ -1562,12 +1592,12 @@ namespace Sass { for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { Complex_Selector_Obj seq1 = (*this)[lhs_i]; for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { - Complex_Selector_Ptr seq2 = &rhs->at(rhs_i); + Complex_Selector_Ptr seq2 = rhs->at(rhs_i); Selector_List_Obj result = seq1->unify_with(seq2, ctx); if( result ) { for(size_t i = 0, L = result->length(); i < L; ++i) { - unified_complex_selectors.push_back( &(*result)[i] ); + unified_complex_selectors.push_back( (*result)[i] ); } } } @@ -1594,7 +1624,7 @@ namespace Sass { Complex_Selector_Obj pIter = complex_sel; while (pIter) { Compound_Selector_Obj pHead = pIter->head(); - if (pHead && SASS_MEMORY_CAST(Parent_Selector, pHead->elements()[0]) == NULL) { + if (pHead && Cast(pHead->elements()[0]) == NULL) { compound_sel = pHead; break; } @@ -1609,19 +1639,11 @@ namespace Sass { compound_sel->is_optional(extendee->is_optional()); for (size_t i = 0, L = extender->length(); i < L; ++i) { - extends.put(compound_sel, std::make_pair(&(*extender)[i], &compound_sel)); + extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel)); } } }; - std::vector Compound_Selector::to_str_vec() - { - std::vector result(length()); - for (size_t i = 0, L = length(); i < L; ++i) - { result.push_back((*this)[i]->to_string()); } - return result; - } - void Compound_Selector::append(Simple_Selector_Ptr element) { Vectorized::append(element); @@ -1646,15 +1668,15 @@ namespace Sass { break; } } - if (!found) result->append(&(*this)[i]); + if (!found) result->append((*this)[i]); } return result; } - void Compound_Selector::mergeSources(SourcesSet& sources, Context& ctx) + void Compound_Selector::mergeSources(ComplexSelectorSet& sources, Context& ctx) { - for (SourcesSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { + for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); } } @@ -1717,7 +1739,7 @@ namespace Sass { } bool Ruleset::is_invisible() const { - if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, selector())) { + if (Selector_List_Ptr sl = Cast(selector())) { for (size_t i = 0, L = sl->length(); i < L; ++i) if (!(*sl)[i]->has_placeholder()) return false; } @@ -2094,7 +2116,7 @@ namespace Sass { bool Custom_Warning::operator== (const Expression& rhs) const { - if (Custom_Warning_Ptr_Const r = dynamic_cast(&rhs)) { + if (Custom_Warning_Ptr_Const r = Cast(&rhs)) { return message() == r->message(); } return false; @@ -2102,30 +2124,15 @@ namespace Sass { bool Custom_Error::operator== (const Expression& rhs) const { - if (Custom_Error_Ptr_Const r = dynamic_cast(&rhs)) { + if (Custom_Error_Ptr_Const r = Cast(&rhs)) { return message() == r->message(); } return false; } - bool Number::eq (const Expression& rhs) const - { - if (Number_Ptr_Const r = dynamic_cast(&rhs)) { - size_t lhs_units = numerator_units_.size() + denominator_units_.size(); - size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); - if (!lhs_units && !rhs_units) { - return std::fabs(value() - r->value()) < NUMBER_EPSILON; - } - return (numerator_units_ == r->numerator_units_) && - (denominator_units_ == r->denominator_units_) && - std::fabs(value() - r->value()) < NUMBER_EPSILON; - } - return false; - } - bool Number::operator== (const Expression& rhs) const { - if (Number_Ptr_Const r = dynamic_cast(&rhs)) { + if (Number_Ptr_Const r = Cast(&rhs)) { size_t lhs_units = numerator_units_.size() + denominator_units_.size(); size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); // unitless and only having one unit seems equivalent (will change in future) @@ -2160,9 +2167,9 @@ namespace Sass { bool String_Quoted::operator== (const Expression& rhs) const { - if (String_Quoted_Ptr_Const qstr = dynamic_cast(&rhs)) { + if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { return (value() == qstr->value()); - } else if (String_Constant_Ptr_Const cstr = dynamic_cast(&rhs)) { + } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { return (value() == cstr->value()); } return false; @@ -2174,9 +2181,9 @@ namespace Sass { bool String_Constant::operator== (const Expression& rhs) const { - if (String_Quoted_Ptr_Const qstr = dynamic_cast(&rhs)) { + if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { return (value() == qstr->value()); - } else if (String_Constant_Ptr_Const cstr = dynamic_cast(&rhs)) { + } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { return (value() == cstr->value()); } return false; @@ -2193,7 +2200,7 @@ namespace Sass { bool String_Schema::operator== (const Expression& rhs) const { - if (String_Schema_Ptr_Const r = dynamic_cast(&rhs)) { + if (String_Schema_Ptr_Const r = Cast(&rhs)) { if (length() != r->length()) return false; for (size_t i = 0, L = length(); i < L; ++i) { Expression_Obj rv = (*r)[i]; @@ -2208,7 +2215,7 @@ namespace Sass { bool Boolean::operator== (const Expression& rhs) const { - if (Boolean_Ptr_Const r = dynamic_cast(&rhs)) { + if (Boolean_Ptr_Const r = Cast(&rhs)) { return (value() == r->value()); } return false; @@ -2216,7 +2223,7 @@ namespace Sass { bool Color::operator== (const Expression& rhs) const { - if (Color_Ptr_Const r = dynamic_cast(&rhs)) { + if (Color_Ptr_Const r = Cast(&rhs)) { return r_ == r->r() && g_ == r->g() && b_ == r->b() && @@ -2227,9 +2234,10 @@ namespace Sass { bool List::operator== (const Expression& rhs) const { - if (List_Ptr_Const r = dynamic_cast(&rhs)) { + if (List_Ptr_Const r = Cast(&rhs)) { if (length() != r->length()) return false; if (separator() != r->separator()) return false; + if (is_bracketed() != r->is_bracketed()) return false; for (size_t i = 0, L = length(); i < L; ++i) { Expression_Obj rv = r->at(i); Expression_Obj lv = this->at(i); @@ -2243,7 +2251,7 @@ namespace Sass { bool Map::operator== (const Expression& rhs) const { - if (Map_Ptr_Const r = dynamic_cast(&rhs)) { + if (Map_Ptr_Const r = Cast(&rhs)) { if (length() != r->length()) return false; for (auto key : keys()) { Expression_Obj lv = at(key); @@ -2267,7 +2275,7 @@ namespace Sass { // so we need to break before keywords for (size_t i = 0, L = length(); i < L; ++i) { Expression_Obj obj = this->at(i); - if (Argument* arg = dynamic_cast(&obj)) { + if (Argument_Ptr arg = Cast(obj)) { if (!arg->name().empty()) return i; } } @@ -2290,7 +2298,7 @@ namespace Sass { return is_interpolant() || (right() && right()->is_right_interpolant()); } - std::string AST_Node::to_string(Sass_Inspect_Options opt) const + const std::string AST_Node::to_string(Sass_Inspect_Options opt) const { Sass_Output_Options out(opt); Emitter emitter(out); @@ -2301,7 +2309,7 @@ namespace Sass { return i.get_buffer(); } - std::string AST_Node::to_string() const + const std::string AST_Node::to_string() const { return to_string({ NESTED, 5 }); } @@ -2322,13 +2330,13 @@ namespace Sass { Expression_Obj List::value_at_index(size_t i) { Expression_Obj obj = this->at(i); if (is_arglist_) { - if (Argument* arg = dynamic_cast(&obj)) { + if (Argument_Ptr arg = Cast(obj)) { return arg->value(); } else { - return &obj; + return obj; } } else { - return &obj; + return obj; } } @@ -2340,9 +2348,9 @@ namespace Sass { for (auto key : keys()) { List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(&key); + l->append(key); l->append(at(key)); - ret->append(&l); + ret->append(l); } return ret; diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp index 3df08888e..d41e434c0 100755 --- a/src/libsass/src/ast.hpp +++ b/src/libsass/src/ast.hpp @@ -1,6 +1,7 @@ #ifndef SASS_AST_H #define SASS_AST_H +#include "sass.hpp" #include #include #include @@ -120,12 +121,11 @@ namespace Sass { ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node); virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } - virtual std::string to_string(Sass_Inspect_Options opt) const; - virtual std::string to_string() const; + virtual const std::string to_string(Sass_Inspect_Options opt) const; + virtual const std::string to_string() const; virtual void cloneChildren() {}; public: void update_pstate(const ParserState& pstate); - void set_pstate_offset(const Offset& offset); public: Offset off() { return pstate(); } Position pos() { return pstate(); } @@ -133,6 +133,21 @@ namespace Sass { }; inline AST_Node::~AST_Node() { } + ////////////////////////////////////////////////////////////////////// + // define cast template now (need complete type) + ////////////////////////////////////////////////////////////////////// + + template + T* Cast(AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + template + const T* Cast(const AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; ////////////////////////////////////////////////////////////////////// // Abstract base class for expressions. This side of the AST hierarchy @@ -186,7 +201,7 @@ namespace Sass { { } virtual operator bool() { return true; } virtual ~Expression() { } - virtual std::string type() { return ""; /* TODO: raise an error? */ } + virtual std::string type() const { return ""; /* TODO: raise an error? */ } virtual bool is_invisible() const { return false; } static std::string type_name() { return ""; } virtual bool is_false() { return false; } @@ -454,7 +469,6 @@ namespace Sass { //////////////////////// class Block : public Statement, public Vectorized { ADD_PROPERTY(bool, is_root) - ADD_PROPERTY(bool, is_at_root); // needed for properly formatted CSS emission protected: void adjust_after_pushing(Statement_Obj s) @@ -464,14 +478,12 @@ namespace Sass { Block(ParserState pstate, size_t s = 0, bool r = false) : Statement(pstate), Vectorized(s), - is_root_(r), - is_at_root_(false) + is_root_(r) { } Block(const Block* ptr) : Statement(ptr), Vectorized(*ptr), - is_root_(ptr->is_root_), - is_at_root_(ptr->is_at_root_) + is_root_(ptr->is_root_) { } virtual bool has_content() { @@ -509,17 +521,15 @@ namespace Sass { // of style declarations. ///////////////////////////////////////////////////////////////////////////// class Ruleset : public Has_Block { - ADD_PROPERTY(Selector_Obj, selector) - ADD_PROPERTY(bool, at_root); + ADD_PROPERTY(Selector_List_Obj, selector) ADD_PROPERTY(bool, is_root); public: - Ruleset(ParserState pstate, Selector_Obj s = 0, Block_Obj b = 0) - : Has_Block(pstate, b), selector_(s), at_root_(false), is_root_(false) + Ruleset(ParserState pstate, Selector_List_Obj s = 0, Block_Obj b = 0) + : Has_Block(pstate, b), selector_(s), is_root_(false) { statement_type(RULESET); } Ruleset(const Ruleset* ptr) : Has_Block(ptr), selector_(ptr->selector_), - at_root_(ptr->at_root_), is_root_(ptr->is_root_) { statement_type(RULESET); } bool is_invisible() const; @@ -551,7 +561,7 @@ namespace Sass { // Trace. ///////////////// class Trace : public Has_Block { - ADD_PROPERTY(std::string, name) + ADD_CONSTREF(std::string, name) public: Trace(ParserState pstate, std::string n, Block_Obj b = 0) : Has_Block(pstate, b), name_(n) @@ -573,9 +583,6 @@ namespace Sass { Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b) : Has_Block(pstate, b), media_queries_(mqs) { statement_type(MEDIA); } - Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b, Selector_Obj s) - : Has_Block(pstate, b), media_queries_(mqs) - { statement_type(MEDIA); } Media_Block(const Media_Block* ptr) : Has_Block(ptr), media_queries_(ptr->media_queries_) { statement_type(MEDIA); } @@ -590,11 +597,11 @@ namespace Sass { // optional statement block. /////////////////////////////////////////////////////////////////////// class Directive : public Has_Block { - ADD_PROPERTY(std::string, keyword) - ADD_PROPERTY(Selector_Obj, selector) + ADD_CONSTREF(std::string, keyword) + ADD_PROPERTY(Selector_List_Obj, selector) ADD_PROPERTY(Expression_Obj, value) public: - Directive(ParserState pstate, std::string kwd, Selector_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0) + Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0) : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed { statement_type(DIRECTIVE); } Directive(const Directive* ptr) @@ -626,7 +633,7 @@ namespace Sass { class Keyframe_Rule : public Has_Block { // according to css spec, this should be // = | - ADD_PROPERTY(Selector_Obj, name) + ADD_PROPERTY(Selector_List_Obj, name) public: Keyframe_Rule(ParserState pstate, Block_Obj b) : Has_Block(pstate, b), name_() @@ -666,7 +673,7 @@ namespace Sass { // Assignments -- variable and value. ///////////////////////////////////// class Assignment : public Statement { - ADD_PROPERTY(std::string, variable) + ADD_CONSTREF(std::string, variable) ADD_PROPERTY(Expression_Obj, value) ADD_PROPERTY(bool, is_default) ADD_PROPERTY(bool, is_global) @@ -811,7 +818,7 @@ namespace Sass { ADD_PROPERTY(Block_Obj, alternative) public: If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0) - : Has_Block(pstate, &con), predicate_(pred), alternative_(alt) + : Has_Block(pstate, con), predicate_(pred), alternative_(alt) { statement_type(IF); } If(const If* ptr) : Has_Block(ptr), @@ -830,7 +837,7 @@ namespace Sass { // The Sass `@for` control directive. ///////////////////////////////////// class For : public Has_Block { - ADD_PROPERTY(std::string, variable) + ADD_CONSTREF(std::string, variable) ADD_PROPERTY(Expression_Obj, lower_bound) ADD_PROPERTY(Expression_Obj, upper_bound) ADD_PROPERTY(bool, is_inclusive) @@ -904,9 +911,9 @@ namespace Sass { // The Sass `@extend` directive. //////////////////////////////// class Extension : public Statement { - ADD_PROPERTY(Selector_Obj, selector) + ADD_PROPERTY(Selector_List_Obj, selector) public: - Extension(ParserState pstate, Selector_Obj s) + Extension(ParserState pstate, Selector_List_Obj s) : Statement(pstate), selector_(s) { statement_type(EXTEND); } Extension(const Extension* ptr) @@ -921,13 +928,12 @@ namespace Sass { // by a type tag. ///////////////////////////////////////////////////////////////////////////// struct Backtrace; - typedef Environment Env; typedef const char* Signature; typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); class Definition : public Has_Block { public: enum Type { MIXIN, FUNCTION }; - ADD_PROPERTY(std::string, name) + ADD_CONSTREF(std::string, name) ADD_PROPERTY(Parameters_Obj, parameters) ADD_PROPERTY(Env*, environment) ADD_PROPERTY(Type, type) @@ -1009,7 +1015,7 @@ namespace Sass { // Mixin calls (i.e., `@include ...`). ////////////////////////////////////// class Mixin_Call : public Has_Block { - ADD_PROPERTY(std::string, name) + ADD_CONSTREF(std::string, name) ADD_PROPERTY(Arguments_Obj, arguments) public: Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0) @@ -1028,7 +1034,7 @@ namespace Sass { // The @content directive for mixin content blocks. /////////////////////////////////////////////////// class Content : public Statement { - ADD_PROPERTY(Media_Block_Obj, media_block) + ADD_PROPERTY(Media_Block_Ptr, media_block) public: Content(ParserState pstate) : Statement(pstate) { statement_type(CONTENT); } @@ -1047,14 +1053,16 @@ namespace Sass { private: ADD_PROPERTY(enum Sass_Separator, separator) ADD_PROPERTY(bool, is_arglist) + ADD_PROPERTY(bool, is_bracketed) ADD_PROPERTY(bool, from_selector) public: List(ParserState pstate, - size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false) + size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false) : Value(pstate), Vectorized(size), separator_(sep), is_arglist_(argl), + is_bracketed_(bracket), from_selector_(false) { concrete_type(LIST); } List(const List* ptr) @@ -1062,15 +1070,16 @@ namespace Sass { Vectorized(*ptr), separator_(ptr->separator_), is_arglist_(ptr->is_arglist_), + is_bracketed_(ptr->is_bracketed_), from_selector_(ptr->from_selector_) { concrete_type(LIST); } - std::string type() { return is_arglist_ ? "arglist" : "list"; } + std::string type() const { return is_arglist_ ? "arglist" : "list"; } static std::string type_name() { return "list"; } const char* sep_string(bool compressed = false) const { return separator() == SASS_SPACE ? " " : (compressed ? "," : ", "); } - bool is_invisible() const { return empty(); } + bool is_invisible() const { return empty() && !is_bracketed(); } Expression_Obj value_at_index(size_t i); virtual size_t size() const; @@ -1079,6 +1088,7 @@ namespace Sass { { if (hash_ == 0) { hash_ = std::hash()(sep_string()); + hash_combine(hash_, std::hash()(is_bracketed())); for (size_t i = 0, L = length(); i < L; ++i) hash_combine(hash_, (elements()[i])->hash()); } @@ -1112,7 +1122,7 @@ namespace Sass { : Value(ptr), Hashed(*ptr) { concrete_type(MAP); } - std::string type() { return "map"; } + std::string type() const { return "map"; } static std::string type_name() { return "map"; } bool is_invisible() const { return empty(); } List_Obj to_list(Context& ctx, ParserState& pstate); @@ -1163,9 +1173,9 @@ namespace Sass { ////////////////////////////////////////////////////////////////////////// class Binary_Expression : public PreValue { private: - ADD_HASHED(Operand, op) - ADD_HASHED(Expression_Obj, left) - ADD_HASHED(Expression_Obj, right) + HASH_PROPERTY(Operand, op) + HASH_PROPERTY(Expression_Obj, left) + HASH_PROPERTY(Expression_Obj, right) size_t hash_; public: Binary_Expression(ParserState pstate, @@ -1180,7 +1190,7 @@ namespace Sass { hash_(ptr->hash_) { } const std::string type_name() { - switch (type()) { + switch (optype()) { case AND: return "and"; break; case OR: return "or"; break; case EQ: return "eq"; break; @@ -1200,7 +1210,7 @@ namespace Sass { } } const std::string separator() { - switch (type()) { + switch (optype()) { case AND: return "&&"; break; case OR: return "||"; break; case EQ: return "=="; break; @@ -1236,11 +1246,11 @@ namespace Sass { { try { - Binary_Expression_Ptr_Const m = dynamic_cast(&rhs); + Binary_Expression_Ptr_Const m = Cast(&rhs); if (m == 0) return false; return type() == m->type() && - left() == m->left() && - right() == m->right(); + *left() == *m->left() && + *right() == *m->right(); } catch (std::bad_cast&) { @@ -1251,13 +1261,13 @@ namespace Sass { virtual size_t hash() { if (hash_ == 0) { - hash_ = std::hash()(type()); + hash_ = std::hash()(optype()); hash_combine(hash_, left()->hash()); hash_combine(hash_, right()->hash()); } return hash_; } - enum Sass_OP type() const { return op_.operand; } + enum Sass_OP optype() const { return op_.operand; } ATTACH_AST_OPERATIONS(Binary_Expression) ATTACH_OPERATIONS() }; @@ -1269,21 +1279,21 @@ namespace Sass { public: enum Type { PLUS, MINUS, NOT }; private: - ADD_HASHED(Type, type) - ADD_HASHED(Expression_Obj, operand) + HASH_PROPERTY(Type, optype) + HASH_PROPERTY(Expression_Obj, operand) size_t hash_; public: Unary_Expression(ParserState pstate, Type t, Expression_Obj o) - : Expression(pstate), type_(t), operand_(o), hash_(0) + : Expression(pstate), optype_(t), operand_(o), hash_(0) { } Unary_Expression(const Unary_Expression* ptr) : Expression(ptr), - type_(ptr->type_), + optype_(ptr->optype_), operand_(ptr->operand_), hash_(ptr->hash_) { } const std::string type_name() { - switch (type_) { + switch (optype_) { case PLUS: return "plus"; break; case MINUS: return "minus"; break; case NOT: return "not"; break; @@ -1294,10 +1304,10 @@ namespace Sass { { try { - Unary_Expression_Ptr_Const m = dynamic_cast(&rhs); + Unary_Expression_Ptr_Const m = Cast(&rhs); if (m == 0) return false; return type() == m->type() && - operand() == m->operand(); + *operand() == *m->operand(); } catch (std::bad_cast&) { @@ -1308,7 +1318,7 @@ namespace Sass { virtual size_t hash() { if (hash_ == 0) { - hash_ = std::hash()(type_); + hash_ = std::hash()(optype_); hash_combine(hash_, operand()->hash()); }; return hash_; @@ -1321,8 +1331,8 @@ namespace Sass { // Individual argument objects for mixin and function calls. //////////////////////////////////////////////////////////// class Argument : public Expression { - ADD_HASHED(Expression_Obj, value) - ADD_HASHED(std::string, name) + HASH_PROPERTY(Expression_Obj, value) + HASH_CONSTREF(std::string, name) ADD_PROPERTY(bool, is_rest_argument) ADD_PROPERTY(bool, is_keyword_argument) size_t hash_; @@ -1352,7 +1362,7 @@ namespace Sass { { try { - Argument_Ptr_Const m = dynamic_cast(&rhs); + Argument_Ptr_Const m = Cast(&rhs); if (!(m && name() == m->name())) return false; return *value() == *m->value(); } @@ -1416,8 +1426,8 @@ namespace Sass { // Function calls. ////////////////// class Function_Call : public PreValue { - ADD_HASHED(std::string, name) - ADD_HASHED(Arguments_Obj, arguments) + HASH_CONSTREF(std::string, name) + HASH_PROPERTY(Arguments_Obj, arguments) ADD_PROPERTY(bool, via_call) ADD_PROPERTY(void*, cookie) size_t hash_; @@ -1441,11 +1451,11 @@ namespace Sass { { try { - Function_Call_Ptr_Const m = dynamic_cast(&rhs); + Function_Call_Ptr_Const m = Cast(&rhs); if (!(m && name() == m->name())) return false; if (!(m && arguments()->length() == m->arguments()->length())) return false; for (size_t i =0, L = arguments()->length(); i < L; ++i) - if (!((*arguments())[i] == (*m->arguments())[i])) return false; + if (!(*(*arguments())[i] == *(*m->arguments())[i])) return false; return true; } catch (std::bad_cast&) @@ -1491,7 +1501,7 @@ namespace Sass { // Variable references. /////////////////////// class Variable : public PreValue { - ADD_PROPERTY(std::string, name) + ADD_CONSTREF(std::string, name) public: Variable(ParserState pstate, std::string n) : PreValue(pstate), name_(n) @@ -1504,7 +1514,7 @@ namespace Sass { { try { - Variable_Ptr_Const e = dynamic_cast(&rhs); + Variable_Ptr_Const e = Cast(&rhs); return e && name() == e->name(); } catch (std::bad_cast&) @@ -1531,17 +1541,17 @@ namespace Sass { public: enum Type { NUMBER, PERCENTAGE, DIMENSION, HEX }; private: - ADD_HASHED(Type, type) - ADD_HASHED(std::string, value) + HASH_PROPERTY(Type, valtype) + HASH_CONSTREF(std::string, value) size_t hash_; public: Textual(ParserState pstate, Type t, std::string val) - : Expression(pstate, DELAYED), type_(t), value_(val), + : Expression(pstate, DELAYED), valtype_(t), value_(val), hash_(0) { } Textual(const Textual* ptr) : Expression(ptr), - type_(ptr->type_), + valtype_(ptr->valtype_), value_(ptr->value_), hash_(ptr->hash_) { } @@ -1550,7 +1560,7 @@ namespace Sass { { try { - Textual_Ptr_Const e = dynamic_cast(&rhs); + Textual_Ptr_Const e = Cast(&rhs); return e && value() == e->value() && type() == e->type(); } catch (std::bad_cast&) @@ -1564,7 +1574,7 @@ namespace Sass { { if (hash_ == 0) { hash_ = std::hash()(value_); - hash_combine(hash_, std::hash()(type_)); + hash_combine(hash_, std::hash()(valtype_)); } return hash_; } @@ -1577,7 +1587,7 @@ namespace Sass { // Numbers, percentages, dimensions, and colors. //////////////////////////////////////////////// class Number : public Value { - ADD_HASHED(double, value) + HASH_PROPERTY(double, value) ADD_PROPERTY(bool, zero) std::vector numerator_units_; std::vector denominator_units_; @@ -1599,7 +1609,7 @@ namespace Sass { std::vector& denominator_units() { return denominator_units_; } const std::vector& numerator_units() const { return numerator_units_; } const std::vector& denominator_units() const { return denominator_units_; } - std::string type() { return "number"; } + std::string type() const { return "number"; } static std::string type_name() { return "number"; } std::string unit() const; @@ -1624,7 +1634,6 @@ namespace Sass { virtual bool operator< (const Number& rhs) const; virtual bool operator== (const Expression& rhs) const; - virtual bool eq(const Expression& rhs) const; ATTACH_AST_OPERATIONS(Number) ATTACH_OPERATIONS() }; @@ -1633,11 +1642,11 @@ namespace Sass { // Colors. ////////// class Color : public Value { - ADD_HASHED(double, r) - ADD_HASHED(double, g) - ADD_HASHED(double, b) - ADD_HASHED(double, a) - ADD_PROPERTY(std::string, disp) + HASH_PROPERTY(double, r) + HASH_PROPERTY(double, g) + HASH_PROPERTY(double, b) + HASH_PROPERTY(double, a) + ADD_CONSTREF(std::string, disp) size_t hash_; public: Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "") @@ -1653,7 +1662,7 @@ namespace Sass { disp_(ptr->disp_), hash_(ptr->hash_) { concrete_type(COLOR); } - std::string type() { return "color"; } + std::string type() const { return "color"; } static std::string type_name() { return "color"; } virtual size_t hash() @@ -1677,7 +1686,7 @@ namespace Sass { // Errors from Sass_Values. ////////////////////////////// class Custom_Error : public Value { - ADD_PROPERTY(std::string, message) + ADD_CONSTREF(std::string, message) public: Custom_Error(ParserState pstate, std::string msg) : Value(pstate), message_(msg) @@ -1694,7 +1703,7 @@ namespace Sass { // Warnings from Sass_Values. ////////////////////////////// class Custom_Warning : public Value { - ADD_PROPERTY(std::string, message) + ADD_CONSTREF(std::string, message) public: Custom_Warning(ParserState pstate, std::string msg) : Value(pstate), message_(msg) @@ -1711,7 +1720,7 @@ namespace Sass { // Booleans. //////////// class Boolean : public Value { - ADD_HASHED(bool, value) + HASH_PROPERTY(bool, value) size_t hash_; public: Boolean(ParserState pstate, bool val) @@ -1724,7 +1733,7 @@ namespace Sass { hash_(ptr->hash_) { concrete_type(BOOLEAN); } virtual operator bool() { return value_; } - std::string type() { return "bool"; } + std::string type() const { return "bool"; } static std::string type_name() { return "bool"; } virtual bool is_false() { return !value_; } @@ -1757,8 +1766,6 @@ namespace Sass { static std::string type_name() { return "string"; } virtual ~String() = 0; virtual void rtrim() = 0; - virtual void ltrim() = 0; - virtual void trim() = 0; virtual bool operator==(const Expression& rhs) const = 0; virtual bool operator<(const Expression& rhs) const { return this->to_string() < rhs.to_string(); @@ -1785,7 +1792,7 @@ namespace Sass { hash_(ptr->hash_) { concrete_type(STRING); } - std::string type() { return "string"; } + std::string type() const { return "string"; } static std::string type_name() { return "string"; } bool is_left_interpolant(void) const; @@ -1798,8 +1805,6 @@ namespace Sass { return false; } virtual void rtrim(); - virtual void ltrim(); - virtual void trim(); virtual size_t hash() { @@ -1825,7 +1830,7 @@ namespace Sass { class String_Constant : public String { ADD_PROPERTY(char, quote_mark) ADD_PROPERTY(bool, can_compress_whitespace) - ADD_HASHED(std::string, value) + HASH_CONSTREF(std::string, value) protected: size_t hash_; public: @@ -1848,12 +1853,10 @@ namespace Sass { String_Constant(ParserState pstate, const Token& tok) : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end))), hash_(0) { } - std::string type() { return "string"; } + std::string type() const { return "string"; } static std::string type_name() { return "string"; } virtual bool is_invisible() const; virtual void rtrim(); - virtual void ltrim(); - virtual void trim(); virtual size_t hash() { @@ -2103,7 +2106,7 @@ namespace Sass { if (s->statement_type() == Statement::DIRECTIVE) { - if (Directive_Obj dir = SASS_MEMORY_CAST(Directive, s)) + if (Directive_Obj dir = Cast(s)) { std::string keyword(dir->keyword()); if (keyword.length() > 0) keyword.erase(0, 1); @@ -2122,7 +2125,7 @@ namespace Sass { { return expression()->exclude("supports"); } - if (Directive_Obj dir = SASS_MEMORY_CAST(Directive, s)) + if (Directive_Obj dir = Cast(s)) { if (dir->is_keyframes()) return expression()->exclude("keyframes"); } @@ -2139,7 +2142,7 @@ namespace Sass { public: Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); } Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); } - std::string type() { return "null"; } + std::string type() const { return "null"; } static std::string type_name() { return "null"; } bool is_invisible() const { return true; } operator bool() { return false; } @@ -2172,7 +2175,7 @@ namespace Sass { // Individual parameter objects for mixins and functions. ///////////////////////////////////////////////////////// class Parameter : public AST_Node { - ADD_PROPERTY(std::string, name) + ADD_CONSTREF(std::string, name) ADD_PROPERTY(Expression_Obj, default_value) ADD_PROPERTY(bool, is_rest_parameter) public: @@ -2180,9 +2183,11 @@ namespace Sass { std::string n, Expression_Obj def = 0, bool rest = false) : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) { - if (default_value_ && is_rest_parameter_) { - error("variable-length parameter may not have a default value", pstate_); - } + // tried to come up with a spec test for this, but it does no longer + // get past the parser (it error out earlier). A spec test was added! + // if (default_value_ && is_rest_parameter_) { + // error("variable-length parameter may not have a default value", pstate_); + // } } Parameter(const Parameter* ptr) : AST_Node(ptr), @@ -2190,9 +2195,11 @@ namespace Sass { default_value_(ptr->default_value_), is_rest_parameter_(ptr->is_rest_parameter_) { - if (default_value_ && is_rest_parameter_) { - error("variable-length parameter may not have a default value", pstate_); - } + // tried to come up with a spec test for this, but it does no longer + // get past the parser (it error out earlier). A spec test was added! + // if (default_value_ && is_rest_parameter_) { + // error("variable-length parameter may not have a default value", pstate_); + // } } ATTACH_AST_OPERATIONS(Parameter) ATTACH_OPERATIONS() @@ -2266,9 +2273,8 @@ namespace Sass { protected: size_t hash_; public: - Selector(ParserState pstate, bool r = false, bool h = false) + Selector(ParserState pstate) : Expression(pstate), - // has_reference_(r), has_line_feed_(false), has_line_break_(false), is_optional_(false), @@ -2286,21 +2292,19 @@ namespace Sass { { concrete_type(SELECTOR); } virtual ~Selector() = 0; virtual size_t hash() = 0; - virtual unsigned long specificity() { - return 0; - } + virtual unsigned long specificity() const = 0; virtual void set_media_block(Media_Block_Ptr mb) { media_block(mb); } - virtual bool has_parent_ref() { + virtual bool has_parent_ref() const { return false; } - virtual bool has_real_parent_ref() { + virtual bool has_real_parent_ref() const { return false; } // dispatch to correct handlers - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; + virtual bool operator<(const Selector& rhs) const = 0; + virtual bool operator==(const Selector& rhs) const = 0; ATTACH_VIRTUAL_AST_OPERATIONS(Selector); }; inline Selector::~Selector() { } @@ -2309,20 +2313,34 @@ namespace Sass { // Interpolated selectors -- the interpolated String will be expanded and // re-parsed into a normal selector class. ///////////////////////////////////////////////////////////////////////// - class Selector_Schema : public Selector { + class Selector_Schema : public AST_Node { ADD_PROPERTY(String_Obj, contents) - ADD_PROPERTY(bool, at_root); + ADD_PROPERTY(bool, connect_parent); + // must not be a reference counted object + // otherwise we create circular references + ADD_PROPERTY(Media_Block_Ptr, media_block) + // store computed hash + size_t hash_; public: Selector_Schema(ParserState pstate, String_Obj c) - : Selector(pstate), contents_(c), at_root_(false) + : AST_Node(pstate), + contents_(c), + connect_parent_(true), + media_block_(NULL) { } Selector_Schema(const Selector_Schema* ptr) - : Selector(ptr), + : AST_Node(ptr), contents_(ptr->contents_), - at_root_(ptr->at_root_) + connect_parent_(ptr->connect_parent_), + media_block_(ptr->media_block_) { } - virtual bool has_parent_ref(); - virtual bool has_real_parent_ref(); + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + // selector schema is not yet a final selector, so we do not + // have a specificity for it yet. We need to + virtual unsigned long specificity() const { return 0; } virtual size_t hash() { if (hash_ == 0) { hash_combine(hash_, contents_->hash()); @@ -2337,8 +2355,8 @@ namespace Sass { // Abstract base class for simple selectors. //////////////////////////////////////////// class Simple_Selector : public Selector { - ADD_PROPERTY(std::string, ns) - ADD_PROPERTY(std::string, name) + ADD_CONSTREF(std::string, ns) + ADD_CONSTREF(std::string, name) ADD_PROPERTY(Simple_Type, simple_type) ADD_PROPERTY(bool, has_ns) public: @@ -2360,10 +2378,6 @@ namespace Sass { name_(ptr->name_), has_ns_(ptr->has_ns_) { simple_type(SIMPLE); } - virtual bool unique() const - { - return false; - } virtual std::string ns_name() const { std::string name(""); @@ -2380,6 +2394,8 @@ namespace Sass { } return hash_; } + // namespace compare functions + bool is_ns_eq(const Simple_Selector& r) const; // namespace query functions bool is_universal_ns() const { @@ -2413,10 +2429,9 @@ namespace Sass { virtual ~Simple_Selector() = 0; virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); - virtual bool has_parent_ref() { return false; }; - virtual bool has_real_parent_ref() { return false; }; - virtual bool is_pseudo_element() { return false; } - virtual bool is_pseudo_class() { return false; } + virtual bool has_parent_ref() const { return false; }; + virtual bool has_real_parent_ref() const { return false; }; + virtual bool is_pseudo_element() const { return false; } virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; } @@ -2448,14 +2463,14 @@ namespace Sass { Parent_Selector(const Parent_Selector* ptr) : Simple_Selector(ptr), real_(ptr->real_) { /* has_reference(true); */ } - bool is_real_parent_ref() { return real(); }; - virtual bool has_parent_ref() { return true; }; - virtual bool has_real_parent_ref() { return is_real_parent_ref(); }; - virtual unsigned long specificity() + bool is_real_parent_ref() const { return real(); }; + virtual bool has_parent_ref() const { return true; }; + virtual bool has_real_parent_ref() const { return is_real_parent_ref(); }; + virtual unsigned long specificity() const { return 0; } - std::string type() { return "selector"; } + std::string type() const { return "selector"; } static std::string type_name() { return "selector"; } ATTACH_AST_OPERATIONS(Parent_Selector) ATTACH_OPERATIONS() @@ -2472,7 +2487,7 @@ namespace Sass { Placeholder_Selector(const Placeholder_Selector* ptr) : Simple_Selector(ptr) { } - virtual unsigned long specificity() + virtual unsigned long specificity() const { return Constants::Specificity_Base; } @@ -2495,7 +2510,7 @@ namespace Sass { Element_Selector(const Element_Selector* ptr) : Simple_Selector(ptr) { } - virtual unsigned long specificity() + virtual unsigned long specificity() const { if (name() == "*") return 0; else return Constants::Specificity_Element; @@ -2517,11 +2532,7 @@ namespace Sass { Class_Selector(const Class_Selector* ptr) : Simple_Selector(ptr) { } - virtual bool unique() const - { - return false; - } - virtual unsigned long specificity() + virtual unsigned long specificity() const { return Constants::Specificity_Class; } @@ -2541,11 +2552,7 @@ namespace Sass { Id_Selector(const Id_Selector* ptr) : Simple_Selector(ptr) { } - virtual bool unique() const - { - return true; - } - virtual unsigned long specificity() + virtual unsigned long specificity() const { return Constants::Specificity_ID; } @@ -2558,7 +2565,7 @@ namespace Sass { // Attribute selectors -- e.g., [src*=".jpg"], etc. /////////////////////////////////////////////////// class Attribute_Selector : public Simple_Selector { - ADD_PROPERTY(std::string, matcher) + ADD_CONSTREF(std::string, matcher) // this cannot be changed to obj atm!!!!!!????!!!!!!! ADD_PROPERTY(String_Obj, value) // might be interpolated public: @@ -2579,7 +2586,7 @@ namespace Sass { } return hash_; } - virtual unsigned long specificity() + virtual unsigned long specificity() const { return Constants::Specificity_Attr; } @@ -2617,14 +2624,6 @@ namespace Sass { : Simple_Selector(ptr), expression_(ptr->expression_) { simple_type(PSEUDO_SEL); } - // A pseudo-class always consists of a "colon" (:) followed by the name - // of the pseudo-class and optionally by a value between parentheses. - virtual bool is_pseudo_class() - { - return (name_[0] == ':' && name_[1] != ':') - && ! is_pseudo_class_element(name_); - } - // A pseudo-element is made of two colons (::) followed by the name. // The `::` notation is introduced by the current document in order to // establish a discrimination between pseudo-classes and pseudo-elements. @@ -2633,7 +2632,7 @@ namespace Sass { // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and // :after). This compatibility is not allowed for the new pseudo-elements // introduced in this specification. - virtual bool is_pseudo_element() + virtual bool is_pseudo_element() const { return (name_[0] == ':' && name_[1] == ':') || is_pseudo_class_element(name_); @@ -2646,7 +2645,7 @@ namespace Sass { } return hash_; } - virtual unsigned long specificity() + virtual unsigned long specificity() const { if (is_pseudo_element()) return Constants::Specificity_Element; @@ -2665,9 +2664,9 @@ namespace Sass { // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir) ///////////////////////////////////////////////// class Wrapped_Selector : public Simple_Selector { - ADD_PROPERTY(Selector_Obj, selector) + ADD_PROPERTY(Selector_List_Obj, selector) public: - Wrapped_Selector(ParserState pstate, std::string n, Selector_Obj sel) + Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel) : Simple_Selector(pstate, n), selector_(sel) { simple_type(WRAPPED_SEL); } Wrapped_Selector(const Wrapped_Selector* ptr) @@ -2676,28 +2675,10 @@ namespace Sass { virtual bool is_superselector_of(Wrapped_Selector_Obj sub); // Selectors inside the negation pseudo-class are counted like any // other, but the negation itself does not count as a pseudo-class. - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (selector_) hash_combine(hash_, selector_->hash()); - } - return hash_; - } - virtual bool has_parent_ref() { - // if (has_reference()) return true; - if (!selector()) return false; - return selector()->has_parent_ref(); - } - virtual bool has_real_parent_ref() { - // if (has_reference()) return true; - if (!selector()) return false; - return selector()->has_real_parent_ref(); - } - virtual unsigned long specificity() - { - return selector_ ? selector_->specificity() : 0; - } + virtual size_t hash(); + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + virtual unsigned long specificity() const; virtual bool operator==(const Simple_Selector& rhs) const; virtual bool operator==(const Wrapped_Selector& rhs) const; virtual bool operator<(const Simple_Selector& rhs) const; @@ -2707,18 +2688,13 @@ namespace Sass { ATTACH_OPERATIONS() }; - struct Complex_Selector_Pointer_Compare { - bool operator() (const Complex_Selector_Obj& pLeft, const Complex_Selector_Obj& pRight) const; - }; - //////////////////////////////////////////////////////////////////////////// // Simple selector sequences. Maintains flags indicating whether it contains // any parent references or placeholders, to simplify expansion. //////////////////////////////////////////////////////////////////////////// - typedef std::set SourcesSet; class Compound_Selector : public Selector, public Vectorized { private: - SourcesSet sources_; + ComplexSelectorSet sources_; ADD_PROPERTY(bool, extended); ADD_PROPERTY(bool, has_parent_reference); protected: @@ -2757,18 +2733,13 @@ namespace Sass { Complex_Selector_Obj to_complex(); Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs, Context& ctx); // virtual Placeholder_Selector_Ptr find_placeholder(); - virtual bool has_parent_ref(); - virtual bool has_real_parent_ref(); - Simple_Selector_Ptr base() - { - // Implement non-const in terms of const. Safe to const_cast since this method is non-const - return const_cast(static_cast(this)->base()); - } - Simple_Selector_Ptr_Const base() const { + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + Simple_Selector_Ptr base() const { if (length() == 0) return 0; // ToDo: why is this needed? - if (SASS_MEMORY_CAST(Element_Selector, (*this)[0])) - return &(*this)[0]; + if (Cast((*this)[0])) + return (*this)[0]; return 0; } virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = ""); @@ -2782,7 +2753,7 @@ namespace Sass { } return Selector::hash_; } - virtual unsigned long specificity() + virtual unsigned long specificity() const { int sum = 0; for (size_t i = 0, L = length(); i < L; ++i) @@ -2802,17 +2773,18 @@ namespace Sass { bool is_empty_reference() { return length() == 1 && - SASS_MEMORY_CAST(Parent_Selector, (*this)[0]); + Cast((*this)[0]); } - std::vector to_str_vec(); // sometimes need to convert to a flat "by-value" data structure + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; virtual bool operator<(const Compound_Selector& rhs) const; virtual bool operator==(const Compound_Selector& rhs) const; inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); } - SourcesSet& sources() { return sources_; } + ComplexSelectorSet& sources() { return sources_; } void clearSources() { sources_.clear(); } - void mergeSources(SourcesSet& sources, Context& ctx); + void mergeSources(ComplexSelectorSet& sources, Context& ctx); Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs, Context& ctx); virtual void cloneChildren(); @@ -2829,10 +2801,10 @@ namespace Sass { public: enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE }; private: - ADD_PROPERTY(Combinator, combinator) - ADD_PROPERTY(Compound_Selector_Obj, head) - ADD_PROPERTY(Complex_Selector_Obj, tail) - ADD_PROPERTY(String_Obj, reference); + HASH_CONSTREF(Combinator, combinator) + HASH_PROPERTY(Compound_Selector_Obj, head) + HASH_PROPERTY(Complex_Selector_Obj, tail) + HASH_PROPERTY(String_Obj, reference); public: bool contains_placeholder() { if (head() && head()->contains_placeholder()) return true; @@ -2855,8 +2827,8 @@ namespace Sass { head_(ptr->head_), tail_(ptr->tail_), reference_(ptr->reference_) {}; - virtual bool has_parent_ref(); - virtual bool has_real_parent_ref(); + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; Complex_Selector_Obj skip_empty_reference() { @@ -2878,9 +2850,6 @@ namespace Sass { combinator() == Combinator::ANCESTOR_OF; } - Complex_Selector_Obj context(Context&); - - Selector_List_Ptr tails(Context& ctx, Selector_List_Ptr tails); // front returns the first real tail @@ -2928,55 +2897,57 @@ namespace Sass { if (tail_ && tail_->has_placeholder()) return true; return false; } + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; virtual bool operator<(const Complex_Selector& rhs) const; virtual bool operator==(const Complex_Selector& rhs) const; inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); } - SourcesSet sources() + const ComplexSelectorSet sources() { //s = Set.new //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} //s - SourcesSet srcs; + ComplexSelectorSet srcs; Compound_Selector_Obj pHead = head(); Complex_Selector_Obj pTail = tail(); if (pHead) { - SourcesSet& headSources = pHead->sources(); + const ComplexSelectorSet& headSources = pHead->sources(); srcs.insert(headSources.begin(), headSources.end()); } if (pTail) { - SourcesSet tailSources = pTail->sources(); + const ComplexSelectorSet& tailSources = pTail->sources(); srcs.insert(tailSources.begin(), tailSources.end()); } return srcs; } - void addSources(SourcesSet& sources, Context& ctx) { + void addSources(ComplexSelectorSet& sources, Context& ctx) { // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} Complex_Selector_Ptr pIter = this; while (pIter) { - Compound_Selector_Ptr pHead = &pIter->head(); + Compound_Selector_Ptr pHead = pIter->head(); if (pHead) { pHead->mergeSources(sources, ctx); } - pIter = &pIter->tail(); + pIter = pIter->tail(); } } void clearSources() { Complex_Selector_Ptr pIter = this; while (pIter) { - Compound_Selector_Ptr pHead = &pIter->head(); + Compound_Selector_Ptr pHead = pIter->head(); if (pHead) { pHead->clearSources(); } - pIter = &pIter->tail(); + pIter = pIter->tail(); } } @@ -2985,29 +2956,32 @@ namespace Sass { ATTACH_OPERATIONS() }; - typedef std::deque ComplexSelectorDeque; - /////////////////////////////////// // Comma-separated selector groups. /////////////////////////////////// class Selector_List : public Selector, public Vectorized { - ADD_PROPERTY(std::vector, wspace) + ADD_PROPERTY(Selector_Schema_Obj, schema) + ADD_CONSTREF(std::vector, wspace) protected: void adjust_after_pushing(Complex_Selector_Obj c); public: Selector_List(ParserState pstate, size_t s = 0) - : Selector(pstate), Vectorized(s), wspace_(0) + : Selector(pstate), + Vectorized(s), + schema_(NULL), + wspace_(0) { } Selector_List(const Selector_List* ptr) : Selector(ptr), Vectorized(*ptr), + schema_(ptr->schema_), wspace_(ptr->wspace_) { } - std::string type() { return "list"; } + std::string type() const { return "list"; } // remove parent selector references // basically unwraps parsed selectors - virtual bool has_parent_ref(); - virtual bool has_real_parent_ref(); + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; void remove_parent_selectors(); Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); @@ -3015,6 +2989,7 @@ namespace Sass { virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); Selector_List_Ptr unify_with(Selector_List_Ptr, Context&); void populate_extends(Selector_List_Obj, Context&, Subset_Map&); + Selector_List_Obj eval(Eval& eval); virtual size_t hash() { if (Selector::hash_ == 0) { @@ -3023,7 +2998,7 @@ namespace Sass { } return Selector::hash_; } - virtual unsigned long specificity() + virtual unsigned long specificity() const { unsigned long sum = 0; unsigned long specificity = 0; @@ -3057,24 +3032,6 @@ namespace Sass { ATTACH_OPERATIONS() }; - template - bool selectors_equal(const SelectorType& one, const SelectorType& two, bool simpleSelectorOrderDependent) { - // Test for equality among selectors while differentiating between checks that demand the underlying Simple_Selector - // ordering to be the same or not. This works because operator< (which doesn't make a whole lot of sense for selectors, but - // is required for proper stl collection ordering) is implemented using string comparision. This gives stable sorting - // behavior, and can be used to determine if the selectors would have exactly idential output. operator== matches the - // ruby sass implementations for eql, which sometimes perform order independent comparisions (like set comparisons of the - // members of a SimpleSequence (Compound_Selector)). - // - // Due to the reliance on operator== and operater< behavior, this templated method is currently only intended for - // use with Compound_Selector and Complex_Selector objects. - if (simpleSelectorOrderDependent) { - return !(one < two) && !(two < one); - } else { - return one == two; - } - } - // compare function for sorting and probably other other uses struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } }; struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } }; diff --git a/src/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp index f8124629a..0ff30d1e3 100755 --- a/src/libsass/src/ast_def_macros.hpp +++ b/src/libsass/src/ast_def_macros.hpp @@ -44,7 +44,7 @@ public:\ type name(type name##__) { return name##_ = name##__; }\ private: -#define ADD_HASHED(type, name)\ +#define HASH_PROPERTY(type, name)\ protected:\ type name##_;\ public:\ @@ -52,4 +52,20 @@ public:\ type name(type name##__) { hash_ = 0; return name##_ = name##__; }\ private: +#define ADD_CONSTREF(type, name) \ +protected: \ + type name##_; \ +public: \ + const type& name() const { return name##_; } \ + void name(type name##__) { name##_ = name##__; } \ +private: + +#define HASH_CONSTREF(type, name) \ +protected: \ + type name##_; \ +public: \ + const type& name() const { return name##_; } \ + void name(type name##__) { hash_ = 0; name##_ = name##__; } \ +private: + #endif diff --git a/src/libsass/src/ast_fwd_decl.cpp b/src/libsass/src/ast_fwd_decl.cpp new file mode 100755 index 000000000..c9c76727a --- /dev/null +++ b/src/libsass/src/ast_fwd_decl.cpp @@ -0,0 +1,29 @@ +#include "ast.hpp" + +namespace Sass { + + #define IMPLEMENT_BASE_CAST(T) \ + template<> \ + T* Cast(AST_Node* ptr) { \ + return dynamic_cast(ptr); \ + }; \ + \ + template<> \ + const T* Cast(const AST_Node* ptr) { \ + return dynamic_cast(ptr); \ + }; \ + + IMPLEMENT_BASE_CAST(AST_Node) + IMPLEMENT_BASE_CAST(Expression) + IMPLEMENT_BASE_CAST(Statement) + IMPLEMENT_BASE_CAST(Has_Block) + IMPLEMENT_BASE_CAST(PreValue) + IMPLEMENT_BASE_CAST(Value) + IMPLEMENT_BASE_CAST(List) + IMPLEMENT_BASE_CAST(String) + IMPLEMENT_BASE_CAST(String_Constant) + IMPLEMENT_BASE_CAST(Supports_Condition) + IMPLEMENT_BASE_CAST(Selector) + IMPLEMENT_BASE_CAST(Simple_Selector) + +} diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp index 374ca9198..a92cac2b3 100755 --- a/src/libsass/src/ast_fwd_decl.hpp +++ b/src/libsass/src/ast_fwd_decl.hpp @@ -1,7 +1,11 @@ #ifndef SASS_AST_FWD_DECL_H #define SASS_AST_FWD_DECL_H +#include +#include +#include #include +#include #include #include #include @@ -353,37 +357,94 @@ namespace Sass { IMPL_MEM_OBJ(Complex_Selector); IMPL_MEM_OBJ(Selector_List); + // ########################################################################### + // Implement compare, order and hashing operations for AST Nodes + // ########################################################################### - struct HashExpression { - size_t operator() (Expression_Obj ex) const; + struct HashNodes { + template + size_t operator() (const T& ex) const { + return ex.isNull() ? 0 : ex->hash(); + } }; - struct CompareExpression { - bool operator()(const Expression_Obj& lhs, const Expression_Obj& rhs) const; + struct OrderNodes { + template + bool operator() (const T& lhs, const T& rhs) const { + return !lhs.isNull() && !rhs.isNull() && *lhs < *rhs; + } }; - - struct HashSimpleSelector { - size_t operator() (Simple_Selector_Obj ex) const; + struct CompareNodes { + template + bool operator() (const T& lhs, const T& rhs) const { + return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs; + } }; - struct CompareSimpleSelector { - bool operator()(Simple_Selector_Obj lhs, Simple_Selector_Obj rhs) const; - }; + // ########################################################################### + // some often used typedefs + // ########################################################################### typedef std::unordered_map< Expression_Obj, // key Expression_Obj, // value - HashExpression, // hasher - CompareExpression // compare + HashNodes, // hasher + CompareNodes // compare > ExpressionMap; typedef std::unordered_set< Expression_Obj, // value - HashExpression, // hasher - CompareExpression // compare + HashNodes, // hasher + CompareNodes // compare > ExpressionSet; - typedef std::string Subset_Map_Key; - typedef std::vector Subset_Map_Arr; - typedef std::pair Subset_Map_Val; + typedef std::string SubSetMapKey; + typedef std::vector SubSetMapKeys; + + typedef std::pair SubSetMapPair; + typedef std::pair SubSetMapLookup; + typedef std::vector SubSetMapPairs; + typedef std::vector SubSetMapLookups; + + typedef std::pair SubSetMapResult; + typedef std::vector SubSetMapResults; + + typedef std::deque ComplexSelectorDeque; + typedef std::set SimpleSelectorSet; + typedef std::set ComplexSelectorSet; + typedef std::unordered_set SimpleSelectorDict; + + // ########################################################################### + // explicit type conversion functions + // ########################################################################### + + template + T* Cast(AST_Node* ptr); + + template + const T* Cast(const AST_Node* ptr); + + // sometimes you know the class you want to cast to is final + // in this case a simple typeid check is faster and safe to use + + #define DECLARE_BASE_CAST(T) \ + template<> T* Cast(AST_Node* ptr); \ + template<> const T* Cast(const AST_Node* ptr); \ + + // ########################################################################### + // implement specialization for final classes + // ########################################################################### + + DECLARE_BASE_CAST(AST_Node) + DECLARE_BASE_CAST(Expression) + DECLARE_BASE_CAST(Statement) + DECLARE_BASE_CAST(Has_Block) + DECLARE_BASE_CAST(PreValue) + DECLARE_BASE_CAST(Value) + DECLARE_BASE_CAST(List) + DECLARE_BASE_CAST(String) + DECLARE_BASE_CAST(String_Constant) + DECLARE_BASE_CAST(Supports_Condition) + DECLARE_BASE_CAST(Selector) + DECLARE_BASE_CAST(Simple_Selector) } diff --git a/src/libsass/src/backtrace.hpp b/src/libsass/src/backtrace.hpp index 884413589..213da2f86 100755 --- a/src/libsass/src/backtrace.hpp +++ b/src/libsass/src/backtrace.hpp @@ -21,7 +21,7 @@ namespace Sass { caller(c) { } - std::string to_string(bool warning = false) + const std::string to_string(bool warning = false) { size_t i = -1; std::stringstream ss; diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp index bd9d7ad34..ea11041c2 100755 --- a/src/libsass/src/bind.cpp +++ b/src/libsass/src/bind.cpp @@ -16,7 +16,7 @@ namespace Sass { std::map param_map; for (size_t i = 0, L = as->length(); i < L; ++i) { - if (auto str = SASS_MEMORY_CAST(String_Quoted, (*as)[i]->value())) { + if (auto str = Cast((*as)[i]->value())) { // force optional quotes (only if needed) if (str->quote_mark()) { str->quote_mark('*'); @@ -42,7 +42,7 @@ namespace Sass { if (ip >= LP) { // skip empty rest arguments if (a->is_rest_argument()) { - if (List_Obj l = SASS_MEMORY_CAST(List, a->value())) { + if (List_Obj l = Cast(a->value())) { if (l->length() == 0) { ++ ia; continue; } @@ -61,7 +61,7 @@ namespace Sass { if (a->is_rest_argument()) { // We should always get a list for rest arguments - if (List_Obj rest = SASS_MEMORY_CAST(List, a->value())) { + if (List_Obj rest = Cast(a->value())) { // create a new list object for wrapped items List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), @@ -70,12 +70,12 @@ namespace Sass { true); // wrap each item from list as an argument for (Expression_Obj item : rest->elements()) { - if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, item)) { + if (Argument_Obj arg = Cast(item)) { arglist->append(SASS_MEMORY_COPY(arg)); // copy } else { arglist->append(SASS_MEMORY_NEW(Argument, item->pstate(), - &item, + item, "", false, false)); @@ -93,9 +93,9 @@ namespace Sass { // expand keyword arguments into their parameters List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); env->local_frame()[p->name()] = arglist; - Map_Obj argmap = SASS_MEMORY_CAST(Map, a->value()); + Map_Obj argmap = Cast(a->value()); for (auto key : argmap->keys()) { - std::string name = unquote(SASS_MEMORY_CAST(String_Constant, key)->value()); + std::string name = unquote(Cast(key)->value()); arglist->append(SASS_MEMORY_NEW(Argument, key->pstate(), argmap->at(key), @@ -117,25 +117,25 @@ namespace Sass { // get and post inc a = (*as)[ia++]; // maybe we have another list as argument - List_Obj ls = SASS_MEMORY_CAST(List, a->value()); + List_Obj ls = Cast(a->value()); // skip any list completely if empty if (ls && ls->empty() && a->is_rest_argument()) continue; Expression_Obj value = a->value(); - if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, value)) { - arglist->append(&arg); + if (Argument_Obj arg = Cast(value)) { + arglist->append(arg); } // check if we have rest argument else if (a->is_rest_argument()) { // preserve the list separator from rest args - if (List_Obj rest = SASS_MEMORY_CAST(List, a->value())) { + if (List_Obj rest = Cast(a->value())) { arglist->separator(rest->separator()); for (size_t i = 0, L = rest->size(); i < L; ++i) { Expression_Obj obj = rest->at(i); arglist->append(SASS_MEMORY_NEW(Argument, obj->pstate(), - &obj, + obj, "", false, false)); @@ -155,7 +155,7 @@ namespace Sass { } } // assign new arglist to environment - env->local_frame()[p->name()] = &arglist; + env->local_frame()[p->name()] = arglist; } // consumed parameter ++ip; @@ -166,7 +166,7 @@ namespace Sass { // If the current argument is the rest argument, extract a value for processing else if (a->is_rest_argument()) { // normal param and rest arg - List_Obj arglist = SASS_MEMORY_CAST(List, a->value()); + List_Obj arglist = Cast(a->value()); // empty rest arg - treat all args as default values if (!arglist->length()) { break; @@ -187,8 +187,8 @@ namespace Sass { } // otherwise move one of the rest args into the param, converting to argument if necessary Expression_Obj obj = arglist->at(0); - if (!(a = SASS_MEMORY_CAST(Argument, obj))) { - Expression_Ptr a_to_convert = &obj; + if (!(a = Cast(obj))) { + Expression_Ptr a_to_convert = obj; a = SASS_MEMORY_NEW(Argument, a_to_convert->pstate(), a_to_convert, @@ -202,17 +202,17 @@ namespace Sass { } } else if (a->is_keyword_argument()) { - Map_Obj argmap = SASS_MEMORY_CAST(Map, a->value()); + Map_Obj argmap = Cast(a->value()); for (auto key : argmap->keys()) { - std::string name = "$" + unquote(SASS_MEMORY_CAST(String_Constant, key)->value()); + std::string name = "$" + unquote(Cast(key)->value()); if (!param_map.count(name)) { std::stringstream msg; msg << callee << " has no parameter named " << name; error(msg.str(), a->pstate()); } - env->local_frame()[name] = &argmap->at(&key); + env->local_frame()[name] = argmap->at(key); } ++ia; continue; @@ -228,7 +228,7 @@ namespace Sass { error(msg.str(), a->pstate()); } // ordinal arg -- bind it to the next param - env->local_frame()[p->name()] = &a->value(); + env->local_frame()[p->name()] = a->value(); ++ip; } else { @@ -250,7 +250,7 @@ namespace Sass { << "provided more than once in call to " << callee; error(msg.str(), a->pstate()); } - env->local_frame()[a->name()] = &a->value(); + env->local_frame()[a->name()] = a->value(); } } // EO while ia diff --git a/src/libsass/src/bind.hpp b/src/libsass/src/bind.hpp index 4d17d0197..93a503aa6 100755 --- a/src/libsass/src/bind.hpp +++ b/src/libsass/src/bind.hpp @@ -6,7 +6,6 @@ #include "ast_fwd_decl.hpp" namespace Sass { - typedef Environment Env; void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Context*, Env*, Eval*); } diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp index 5fc8542e3..5d0a8366e 100755 --- a/src/libsass/src/check_nesting.cpp +++ b/src/libsass/src/check_nesting.cpp @@ -15,7 +15,7 @@ namespace Sass { { Statement_Ptr old_parent = this->parent; - if (At_Root_Block_Ptr root = SASS_MEMORY_CAST_PTR(At_Root_Block, parent)) { + if (At_Root_Block_Ptr root = Cast(parent)) { std::vector old_parents = this->parents; std::vector new_parents; @@ -39,8 +39,8 @@ namespace Sass { } } - At_Root_Block_Ptr ar = SASS_MEMORY_CAST_PTR(At_Root_Block, parent); - Statement_Ptr ret = this->visit_children(&ar->block()); + At_Root_Block_Ptr ar = Cast(parent); + Statement_Ptr ret = this->visit_children(ar->block()); this->parent = old_parent; this->parents = old_parents; @@ -54,11 +54,11 @@ namespace Sass { this->parents.push_back(parent); - Block_Ptr b = SASS_MEMORY_CAST_PTR(Block, parent); + Block_Ptr b = Cast(parent); if (!b) { - if (Has_Block_Ptr bb = SASS_MEMORY_CAST(Has_Block, *parent)) { - b = &bb->block(); + if (Has_Block_Ptr bb = Cast(parent)) { + b = bb->block(); } } @@ -99,8 +99,8 @@ namespace Sass { Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) { - Block_Ptr b1 = SASS_MEMORY_CAST_PTR(Block, s); - Has_Block_Ptr b2 = SASS_MEMORY_CAST_PTR(Has_Block, s); + Block_Ptr b1 = Cast(s); + Has_Block_Ptr b2 = Cast(s); return b1 || b2 ? visit_children(s) : s; } @@ -108,16 +108,16 @@ namespace Sass { { if (!this->parent) return true; - if (SASS_MEMORY_CAST_PTR(Content, node)) + if (Cast(node)) { this->invalid_content_parent(this->parent); } if (is_charset(node)) { this->invalid_charset_parent(this->parent); } - if (SASS_MEMORY_CAST_PTR(Extension, node)) + if (Cast(node)) { this->invalid_extend_parent(this->parent); } - // if (SASS_MEMORY_CAST(Import, node)) + // if (Cast(node)) // { this->invalid_import_parent(this->parent); } if (this->is_mixin(node)) @@ -129,13 +129,13 @@ namespace Sass { if (this->is_function(this->parent)) { this->invalid_function_child(node); } - if (SASS_MEMORY_CAST_PTR(Declaration, node)) + if (Cast(node)) { this->invalid_prop_parent(this->parent); } - if (SASS_MEMORY_CAST_PTR(Declaration, this->parent)) + if (Cast(this->parent)) { this->invalid_prop_child(node); } - if (SASS_MEMORY_CAST_PTR(Return, node)) + if (Cast(node)) { this->invalid_return_parent(this->parent); } return true; @@ -166,8 +166,8 @@ namespace Sass { void CheckNesting::invalid_extend_parent(Statement_Ptr parent) { if (!( - SASS_MEMORY_CAST_PTR(Ruleset, parent) || - SASS_MEMORY_CAST_PTR(Mixin_Call, parent) || + Cast(parent) || + Cast(parent) || is_mixin(parent) )) { throw Exception::InvalidSass( @@ -181,12 +181,12 @@ namespace Sass { // { // for (auto pp : this->parents) { // if ( - // SASS_MEMORY_CAST(Each, pp) || - // SASS_MEMORY_CAST(For, pp) || - // SASS_MEMORY_CAST(If, pp) || - // SASS_MEMORY_CAST(While, pp) || - // SASS_MEMORY_CAST(Trace, pp) || - // SASS_MEMORY_CAST(Mixin_Call, pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || // is_mixin(pp) // ) { // throw Exception::InvalidSass( @@ -212,12 +212,12 @@ namespace Sass { { for (Statement_Ptr pp : this->parents) { if ( - SASS_MEMORY_CAST_PTR(Each, pp) || - SASS_MEMORY_CAST_PTR(For, pp) || - SASS_MEMORY_CAST_PTR(If, pp) || - SASS_MEMORY_CAST_PTR(While, pp) || - SASS_MEMORY_CAST_PTR(Trace, pp) || - SASS_MEMORY_CAST_PTR(Mixin_Call, pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || is_mixin(pp) ) { throw Exception::InvalidSass( @@ -232,12 +232,12 @@ namespace Sass { { for (Statement_Ptr pp : this->parents) { if ( - SASS_MEMORY_CAST_PTR(Each, pp) || - SASS_MEMORY_CAST_PTR(For, pp) || - SASS_MEMORY_CAST_PTR(If, pp) || - SASS_MEMORY_CAST_PTR(While, pp) || - SASS_MEMORY_CAST_PTR(Trace, pp) || - SASS_MEMORY_CAST_PTR(Mixin_Call, pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || is_mixin(pp) ) { throw Exception::InvalidSass( @@ -251,19 +251,19 @@ namespace Sass { void CheckNesting::invalid_function_child(Statement_Ptr child) { if (!( - SASS_MEMORY_CAST_PTR(Each, child) || - SASS_MEMORY_CAST_PTR(For, child) || - SASS_MEMORY_CAST_PTR(If, child) || - SASS_MEMORY_CAST_PTR(While, child) || - SASS_MEMORY_CAST_PTR(Trace, child) || - SASS_MEMORY_CAST_PTR(Comment, child) || - SASS_MEMORY_CAST_PTR(Debug, child) || - SASS_MEMORY_CAST_PTR(Return, child) || - SASS_MEMORY_CAST_PTR(Variable, child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || // Ruby Sass doesn't distinguish variables and assignments - SASS_MEMORY_CAST_PTR(Assignment, child) || - SASS_MEMORY_CAST_PTR(Warning, child) || - SASS_MEMORY_CAST_PTR(Error, child) + Cast(child) || + Cast(child) || + Cast(child) )) { throw Exception::InvalidSass( child->pstate(), @@ -275,14 +275,14 @@ namespace Sass { void CheckNesting::invalid_prop_child(Statement_Ptr child) { if (!( - SASS_MEMORY_CAST_PTR(Each, child) || - SASS_MEMORY_CAST_PTR(For, child) || - SASS_MEMORY_CAST_PTR(If, child) || - SASS_MEMORY_CAST_PTR(While, child) || - SASS_MEMORY_CAST_PTR(Trace, child) || - SASS_MEMORY_CAST_PTR(Comment, child) || - SASS_MEMORY_CAST_PTR(Declaration, child) || - SASS_MEMORY_CAST_PTR(Mixin_Call, child) + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) )) { throw Exception::InvalidSass( child->pstate(), @@ -296,10 +296,10 @@ namespace Sass { if (!( is_mixin(parent) || is_directive_node(parent) || - SASS_MEMORY_CAST_PTR(Ruleset, parent) || - SASS_MEMORY_CAST_PTR(Keyframe_Rule, parent) || - SASS_MEMORY_CAST_PTR(Declaration, parent) || - SASS_MEMORY_CAST_PTR(Mixin_Call, parent) + Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) )) { throw Exception::InvalidSass( parent->pstate(), @@ -326,51 +326,51 @@ namespace Sass { !is_root_node(grandparent) && !is_at_root_node(grandparent); - return SASS_MEMORY_CAST_PTR(Import, parent) || - SASS_MEMORY_CAST_PTR(Each, parent) || - SASS_MEMORY_CAST_PTR(For, parent) || - SASS_MEMORY_CAST_PTR(If, parent) || - SASS_MEMORY_CAST_PTR(While, parent) || - SASS_MEMORY_CAST_PTR(Trace, parent) || + return Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || valid_bubble_node; } bool CheckNesting::is_charset(Statement_Ptr n) { - Directive_Ptr d = SASS_MEMORY_CAST_PTR(Directive, n); + Directive_Ptr d = Cast(n); return d && d->keyword() == "charset"; } bool CheckNesting::is_mixin(Statement_Ptr n) { - Definition_Ptr def = SASS_MEMORY_CAST_PTR(Definition, n); + Definition_Ptr def = Cast(n); return def && def->type() == Definition::MIXIN; } bool CheckNesting::is_function(Statement_Ptr n) { - Definition_Ptr def = SASS_MEMORY_CAST_PTR(Definition, n); + Definition_Ptr def = Cast(n); return def && def->type() == Definition::FUNCTION; } bool CheckNesting::is_root_node(Statement_Ptr n) { - if (SASS_MEMORY_CAST_PTR(Ruleset, n)) return false; + if (Cast(n)) return false; - Block_Ptr b = SASS_MEMORY_CAST_PTR(Block, n); + Block_Ptr b = Cast(n); return b && b->is_root(); } bool CheckNesting::is_at_root_node(Statement_Ptr n) { - return SASS_MEMORY_CAST_PTR(At_Root_Block, n) != NULL; + return Cast(n) != NULL; } bool CheckNesting::is_directive_node(Statement_Ptr n) { - return SASS_MEMORY_CAST_PTR(Directive, n) || - SASS_MEMORY_CAST_PTR(Import, n) || - SASS_MEMORY_CAST_PTR(Media_Block, n) || - SASS_MEMORY_CAST_PTR(Supports_Block, n); + return Cast(n) || + Cast(n) || + Cast(n) || + Cast(n); } } diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp index c3a165a81..ec9ee2ae0 100755 --- a/src/libsass/src/check_nesting.hpp +++ b/src/libsass/src/check_nesting.hpp @@ -6,8 +6,6 @@ namespace Sass { - typedef Environment Env; - class CheckNesting : public Operation_CRTP { std::vector parents; @@ -27,7 +25,7 @@ namespace Sass { template Statement_Ptr fallback(U x) { - Statement_Ptr n = SASS_MEMORY_CAST_PTR(Statement, x); + Statement_Ptr n = Cast(x); if (this->should_visit(n)) { return fallback_impl(n); } diff --git a/src/libsass/src/color_maps.hpp b/src/libsass/src/color_maps.hpp index a225f42d9..d4fd41607 100755 --- a/src/libsass/src/color_maps.hpp +++ b/src/libsass/src/color_maps.hpp @@ -1,3 +1,4 @@ + #ifndef SASS_COLOR_MAPS_H #define SASS_COLOR_MAPS_H @@ -319,14 +320,11 @@ namespace Sass { extern const Color transparent; } - extern const std::map colors_to_names; - extern const std::map names_to_colors; - - extern Color_Ptr_Const name_to_color(const char*); - extern Color_Ptr_Const name_to_color(const std::string&); - extern const char* color_to_name(const int); - extern const char* color_to_name(const Color&); - extern const char* color_to_name(const double); + Color_Ptr_Const name_to_color(const char*); + Color_Ptr_Const name_to_color(const std::string&); + const char* color_to_name(const int); + const char* color_to_name(const Color&); + const char* color_to_name(const double); } diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp index 91b69f310..42b41c364 100755 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -87,8 +87,9 @@ namespace Sass { { - // add cwd to include paths - include_paths.push_back(CWD); + // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. + // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. + // include_paths.push_back(CWD); // collect more paths from different options collect_include_paths(c_options.include_path); @@ -391,8 +392,8 @@ namespace Sass { String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); - loc_args->append(&loc_arg); - Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", &loc_args); + loc_args->append(loc_arg); + Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); imp->urls().push_back(new_url); } else { @@ -528,11 +529,11 @@ namespace Sass { Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); // dispatch headers which will add custom functions // custom headers are added to the import instance - call_headers(entry_path, ctx_path, pstate, &imp); + call_headers(entry_path, ctx_path, pstate, imp); // increase head count to skip later head_imports += resources.size() - 1; // add the statement if we have urls - if (!imp->urls().empty()) root->append(&imp); + if (!imp->urls().empty()) root->append(imp); // process all other resources (add Import_Stub nodes) for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); @@ -650,24 +651,24 @@ namespace Sass { Cssize cssize(*this, &backtrace); CheckNesting check_nesting; // check nesting - check_nesting(&root); + check_nesting(root); // expand and eval the tree - root = expand(&root); + root = expand(root); // check nesting - check_nesting(&root); + check_nesting(root); // merge and bubble certain rules - root = cssize(&root); + root = cssize(root); // should we extend something? if (!subset_map.empty()) { // create crtp visitor object Extend extend(*this, subset_map); // extend tree nodes - extend(&root); + extend(root); } // clean up by removing empty placeholders // ToDo: maybe we can do this somewhere else? - Remove_Placeholders remove_placeholders(*this); + Remove_Placeholders remove_placeholders; root->perform(&remove_placeholders); // return processed tree return root; @@ -810,6 +811,7 @@ namespace Sass { register_function(ctx, append_sig, append, env); register_function(ctx, zip_sig, zip, env); register_function(ctx, list_separator_sig, list_separator, env); + register_function(ctx, is_bracketed_sig, is_bracketed, env); // Map Functions register_function(ctx, map_get_sig, map_get, env); register_function(ctx, map_merge_sig, map_merge, env); diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp index 86c2e87cb..44a32ed06 100755 --- a/src/libsass/src/context.hpp +++ b/src/libsass/src/context.hpp @@ -50,6 +50,7 @@ namespace Sass { std::map sheets; Subset_Map subset_map; std::vector import_stack; + std::vector callee_stack; struct Sass_Compiler* c_compiler; diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp index 63e6a7d58..ae112f391 100755 --- a/src/libsass/src/cssize.cpp +++ b/src/libsass/src/cssize.cpp @@ -38,10 +38,10 @@ namespace Sass { Statement_Ptr Cssize::operator()(Declaration_Ptr d) { - String_Obj property = SASS_MEMORY_CAST(String, d->property()); + String_Obj property = Cast(d->property()); - if (Declaration_Ptr dd = dynamic_cast(parent())) { - String_Obj parent_property = SASS_MEMORY_CAST(String, dd->property()); + if (Declaration_Ptr dd = Cast(parent())) { + String_Obj parent_property = Cast(dd->property()); property = SASS_MEMORY_NEW(String_Constant, d->property()->pstate(), parent_property->to_string() + "-" + property->to_string()); @@ -58,13 +58,13 @@ namespace Sass { dd->is_indented(d->is_indented()); dd->tabs(d->tabs()); - p_stack.push_back(&dd); - Block_Obj bb = d->block() ? operator()(&d->block()) : NULL; + p_stack.push_back(dd); + Block_Obj bb = d->block() ? operator()(d->block()) : NULL; p_stack.pop_back(); if (bb && bb->length()) { if (dd->value() && !dd->value()->is_invisible()) { - bb->unshift(&dd); + bb->unshift(dd); } return bb.detach(); } @@ -89,7 +89,7 @@ namespace Sass { r->pstate(), r->keyword(), r->selector(), - r->block() ? operator()(&r->block()) : 0); + r->block() ? operator()(r->block()) : 0); if (r->value()) rr->value(r->value()); p_stack.pop_back(); @@ -99,10 +99,10 @@ namespace Sass { Statement_Obj s = r->block()->at(i); if (s->statement_type() != Statement::BUBBLE) directive_exists = true; else { - Bubble_Obj s_obj = SASS_MEMORY_CAST(Bubble, s); + Bubble_Obj s_obj = Cast(s); s = s_obj->node(); if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; - else directive_exists = (static_cast(&s)->keyword() == rr->keyword()); + else directive_exists = (Cast(s)->keyword() == rr->keyword()); } } @@ -110,12 +110,14 @@ namespace Sass { Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); if (!(directive_exists || rr->is_keyframes())) { - Directive_Ptr empty_node = SASS_MEMORY_CAST(Directive, rr); + Directive_Ptr empty_node = Cast(rr); empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); result->append(empty_node); } - Block_Obj ss = debubble(rr->block() ? &rr->block() : SASS_MEMORY_NEW(Block, rr->pstate()), &rr); + Block_Obj db = rr->block(); + if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); + Block_Obj ss = debubble(db, rr); for (size_t i = 0, L = ss->length(); i < L; ++i) { result->append(ss->at(i)); } @@ -129,10 +131,10 @@ namespace Sass { Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), - operator()(&r->block())); - if (&r->name()) rr->name(r->name()); + operator()(r->block())); + if (!r->name().isNull()) rr->name(r->name()); - return debubble(&rr->block(), &rr); + return debubble(rr->block(), rr); } Statement_Ptr Cssize::operator()(Ruleset_Ptr r) @@ -142,10 +144,10 @@ namespace Sass { // string schema is not a statement! // r->block() is already a string schema // and that is comming from propset expand - Block_Ptr bb = operator()(&r->block()); + Block_Ptr bb = operator()(r->block()); // this should protect us (at least a bit) from our mess // fixing this properly is harder that it should be ... - if (dynamic_cast(bb) == NULL) { + if (Cast(bb) == NULL) { error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); } Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, @@ -165,7 +167,7 @@ namespace Sass { Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); for (size_t i = 0, L = rr->block()->length(); i < L; i++) { - Statement_Ptr s = &rr->block()->at(i); + Statement_Ptr s = rr->block()->at(i); if (bubblable(s)) rules->append(s); if (!bubblable(s)) props->append(s); } @@ -173,16 +175,16 @@ namespace Sass { if (props->length()) { Block_Obj bb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - bb->concat(&props); + bb->concat(props); rr->block(bb); for (size_t i = 0, L = rules->length(); i < L; i++) { - Statement_Ptr stm = &rules->at(i); + Statement_Ptr stm = rules->at(i); stm->tabs(stm->tabs() + 1); } - rules->unshift(&rr); + rules->unshift(rr); } Block_Ptr ptr = rules; @@ -194,7 +196,7 @@ namespace Sass { } if (!(!rules->length() || - !bubblable(&rules->last()) || + !bubblable(rules->last()) || parent()->statement_type() == Statement::RULESET)) { rules->last()->group_end(true); @@ -219,13 +221,13 @@ namespace Sass { Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, m->pstate(), - &m->media_queries(), - operator()(&m->block())); + m->media_queries(), + operator()(m->block())); mm->tabs(m->tabs()); p_stack.pop_back(); - return debubble(&mm->block(), &mm); + return debubble(mm->block(), mm); } Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) @@ -240,13 +242,13 @@ namespace Sass { Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, m->pstate(), - &m->condition(), - operator()(&m->block())); + m->condition(), + operator()(m->block())); mm->tabs(m->tabs()); p_stack.pop_back(); - return debubble(&mm->block(), &mm); + return debubble(mm->block(), mm); } Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) @@ -259,13 +261,13 @@ namespace Sass { if (!tmp) { - Block_Ptr bb = operator()(&m->block()); + Block_Ptr bb = operator()(m->block()); for (size_t i = 0, L = bb->length(); i < L; ++i) { // (bb->elements())[i]->tabs(m->tabs()); Statement_Obj stm = bb->at(i); - if (bubblable(&stm)) stm->tabs(stm->tabs() + m->tabs()); + if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); } - if (bb->length() && bubblable(&bb->last())) bb->last()->group_end(m->group_end()); + if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); return bb; } @@ -280,13 +282,13 @@ namespace Sass { Statement_Ptr Cssize::bubble(Directive_Ptr m) { Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - Has_Block_Obj new_rule = static_cast(SASS_MEMORY_COPY(this->parent())); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); new_rule->block(bb); new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(&m->block()); + new_rule->block()->concat(m->block()); Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); - wrapper_block->append(&new_rule); + wrapper_block->append(new_rule); Directive_Obj mm = SASS_MEMORY_NEW(Directive, m->pstate(), m->keyword(), @@ -294,20 +296,20 @@ namespace Sass { wrapper_block); if (m->value()) mm->value(m->value()); - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), &mm); + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); return bubble; } Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) { Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - Has_Block_Obj new_rule = static_cast(SASS_MEMORY_COPY(this->parent())); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); new_rule->block(bb); new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(&m->block()); + new_rule->block()->concat(m->block()); Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(&new_rule); + wrapper_block->append(new_rule); At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, m->pstate(), wrapper_block, @@ -318,7 +320,7 @@ namespace Sass { Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) { - Ruleset_Obj parent = static_cast(SASS_MEMORY_COPY(this->parent())); + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, @@ -326,13 +328,13 @@ namespace Sass { parent->selector(), bb); new_rule->tabs(parent->tabs()); - new_rule->block()->concat(&m->block()); + new_rule->block()->concat(m->block()); Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); wrapper_block->append(new_rule); Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, m->pstate(), - &m->condition(), + m->condition(), wrapper_block); mm->tabs(m->tabs()); @@ -343,7 +345,7 @@ namespace Sass { Statement_Ptr Cssize::bubble(Media_Block_Ptr m) { - Ruleset_Obj parent = static_cast(SASS_MEMORY_COPY(this->parent())); + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, @@ -351,32 +353,31 @@ namespace Sass { parent->selector(), bb); new_rule->tabs(parent->tabs()); - new_rule->block()->concat(&m->block()); + new_rule->block()->concat(m->block()); Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); wrapper_block->append(new_rule); Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, m->pstate(), - &m->media_queries(), - wrapper_block, - 0); + m->media_queries(), + wrapper_block); mm->tabs(m->tabs()); - return SASS_MEMORY_NEW(Bubble, mm->pstate(), &mm); + return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); } bool Cssize::bubblable(Statement_Ptr s) { - return dynamic_cast(s) || s->bubbles(); + return Cast(s) || s->bubbles(); } Block_Ptr Cssize::flatten(Block_Ptr b) { Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr ss = &b->at(i); - if (Block_Ptr bb = SASS_MEMORY_CAST_PTR(Block, ss)) { + Statement_Ptr ss = b->at(i); + if (Block_Ptr bb = Cast(ss)) { Block_Obj bs = flatten(bb); for (size_t j = 0, K = bs->length(); j < K; ++j) { result->append(bs->at(j)); @@ -395,7 +396,7 @@ namespace Sass { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj value = b->at(i); - bool key = dynamic_cast(&value) != NULL; + bool key = Cast(value) != NULL; if (!results.empty() && results.back().first == key) { @@ -424,17 +425,17 @@ namespace Sass { if (!is_bubble) { if (!parent) { - result->append(&slice); + result->append(slice); } else if (previous_parent) { - previous_parent->block()->concat(&slice); + previous_parent->block()->concat(slice); } else { - previous_parent = static_cast(SASS_MEMORY_COPY(parent)); + previous_parent = Cast(SASS_MEMORY_COPY(parent)); previous_parent->block(slice); previous_parent->tabs(parent->tabs()); - result->append(&previous_parent); + result->append(previous_parent); } continue; } @@ -444,25 +445,30 @@ namespace Sass { Statement_Ptr ss = NULL; Statement_Obj stm = slice->at(j); // this has to go now here (too bad) - Bubble_Obj node = SASS_MEMORY_CAST(Bubble, stm); + Bubble_Obj node = Cast(stm); Media_Block_Ptr m1 = NULL; Media_Block_Ptr m2 = NULL; - if (parent) m1 = SASS_MEMORY_CAST(Media_Block, *parent); - if (node) m2 = SASS_MEMORY_CAST(Media_Block, node->node()); + if (parent) m1 = Cast(parent); + if (node) m2 = Cast(node->node()); if (!parent || parent->statement_type() != Statement::MEDIA || node->node()->statement_type() != Statement::MEDIA || - (m1 && m2 && &m1->media_queries() == &m2->media_queries()) + (m1 && m2 && *m1->media_queries() == *m2->media_queries()) ) { - ss = &node->node(); + ss = node->node(); } else { - List_Obj mq = merge_media_queries(static_cast(&node->node()), static_cast(parent)); + List_Obj mq = merge_media_queries( + Cast(node->node()), + Cast(parent) + ); if (!mq->length()) continue; - static_cast(&node->node())->media_queries(mq); - ss = &node->node(); + if (Media_Block* b = Cast(node->node())) { + b->media_queries(mq); + } + ss = node->node(); } if (!ss) continue; @@ -483,7 +489,7 @@ namespace Sass { children->length(), children->is_root()); - Block_Ptr wrapper = flatten(&bb); + Block_Ptr wrapper = flatten(bb); wrapper_block->append(wrapper); if (wrapper->length()) { @@ -491,12 +497,12 @@ namespace Sass { } if (wrapper_block) { - result->append(&wrapper_block); + result->append(wrapper_block); } } } - return flatten(&result); + return flatten(result); } Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) @@ -508,7 +514,7 @@ namespace Sass { { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj ith = b->at(i)->perform(this); - if (Block_Ptr bb = SASS_MEMORY_CAST(Block, ith)) { + if (Block_Ptr bb = Cast(ith)) { for (size_t j = 0, K = bb->length(); j < K; ++j) { cur->append(bb->at(j)); } @@ -530,8 +536,8 @@ namespace Sass { for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { Expression_Obj l1 = m1->media_queries()->at(i); Expression_Obj l2 = m2->media_queries()->at(j); - Media_Query_Ptr mq1 = SASS_MEMORY_CAST(Media_Query, l1); - Media_Query_Ptr mq2 = SASS_MEMORY_CAST(Media_Query, l2); + Media_Query_Ptr mq1 = Cast(l1); + Media_Query_Ptr mq2 = Cast(l2); Media_Query_Ptr mq = merge_media_query(mq1, mq2); if (mq) qq->append(mq); } @@ -548,9 +554,9 @@ namespace Sass { std::string mod; std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); - std::string t1 = &mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; + std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; std::string m2 = std::string(mq2->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); - std::string t2 = &mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; + std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; if (t1.empty()) t1 = t2; diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp index 20b79668f..506b075f7 100755 --- a/src/libsass/src/cssize.hpp +++ b/src/libsass/src/cssize.hpp @@ -8,7 +8,6 @@ namespace Sass { - typedef Environment Env; struct Backtrace; class Cssize : public Operation_CRTP { diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp index 9d38dd2ce..ea21ddbbc 100755 --- a/src/libsass/src/debugger.hpp +++ b/src/libsass/src/debugger.hpp @@ -10,11 +10,15 @@ using namespace Sass; inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); -inline void debug_sources_set(SourcesSet& set, std::string ind = "") +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { + debug_ast(const_cast(node), ind, env); +} + +inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") { if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; for(auto const &pair : set) { - debug_ast(&pair, ind + ""); + debug_ast(pair, ind + ""); // debug_ast(set[pair], ind + "first: "); } if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; @@ -64,30 +68,30 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) { if (node == 0) return; if (ind == "") std::cerr << "####################################################################\n"; - if (dynamic_cast(node)) { - Bubble_Ptr bubble = dynamic_cast(node); + if (Cast(node)) { + Bubble_Ptr bubble = Cast(node); std::cerr << ind << "Bubble " << bubble; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << bubble->tabs(); std::cerr << std::endl; - debug_ast(&bubble->node(), ind + " ", env); - } else if (dynamic_cast(node)) { - Trace_Ptr trace = dynamic_cast(node); + debug_ast(bubble->node(), ind + " ", env); + } else if (Cast(node)) { + Trace_Ptr trace = Cast(node); std::cerr << ind << "Trace " << trace; std::cerr << " (" << pstate_source_position(node) << ")" << " [name:" << trace->name() << "]" << std::endl; - debug_ast(&trace->block(), ind + " ", env); - } else if (dynamic_cast(node)) { - At_Root_Block_Ptr root_block = dynamic_cast(node); + debug_ast(trace->block(), ind + " ", env); + } else if (Cast(node)) { + At_Root_Block_Ptr root_block = Cast(node); std::cerr << ind << "At_Root_Block " << root_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << root_block->tabs(); std::cerr << std::endl; - debug_ast(&root_block->expression(), ind + ":", env); - debug_ast(&root_block->block(), ind + " ", env); - } else if (dynamic_cast(node)) { - Selector_List_Ptr selector = dynamic_cast(node); + debug_ast(root_block->expression(), ind + ":", env); + debug_ast(root_block->block(), ind + " ", env); + } else if (Cast(node)) { + Selector_List_Ptr selector = Cast(node); std::cerr << ind << "Selector_List " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -99,15 +103,16 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; + debug_ast(selector->schema(), "#{} "); - for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(&i, ind + " ", env); } + for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } -// } else if (dynamic_cast(node)) { -// Expression_Ptr expression = dynamic_cast(node); +// } else if (Cast(node)) { +// Expression_Ptr expression = Cast(node); // std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; - } else if (dynamic_cast(node)) { - Parent_Selector_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Parent_Selector_Ptr selector = Cast(node); std::cerr << ind << "Parent_Selector " << selector; // if (selector->not_selector()) cerr << " [in_declaration]"; std::cerr << " (" << pstate_source_position(node) << ")"; @@ -116,8 +121,8 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; // debug_ast(selector->selector(), ind + "->", env); - } else if (dynamic_cast(node)) { - Complex_Selector_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Complex_Selector_Ptr selector = Cast(node); std::cerr << ind << "Complex_Selector " << selector << " (" << pstate_source_position(node) << ")" << " <" << selector->hash() << ">" @@ -141,16 +146,16 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) } // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(&selector->head(), ind + " " /* + "[" + del + "]" */, env); + debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); if (selector->tail()) { - debug_ast(&selector->tail(), ind + "{" + del + "}", env); + debug_ast(selector->tail(), ind + "{" + del + "}", env); } else if(del != " ") { std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; } - SourcesSet set = selector->sources(); + ComplexSelectorSet set = selector->sources(); // debug_sources_set(set, ind + " @--> "); - } else if (dynamic_cast(node)) { - Compound_Selector_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Compound_Selector_Ptr selector = Cast(node); std::cerr << ind << "Compound_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -162,9 +167,9 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Wrapped_Selector_Ptr selector = dynamic_cast(node); + for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Wrapped_Selector_Ptr selector = Cast(node); std::cerr << ind << "Wrapped_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -174,9 +179,9 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - debug_ast(&selector->selector(), ind + " () ", env); - } else if (dynamic_cast(node)) { - Pseudo_Selector_Ptr selector = dynamic_cast(node); + debug_ast(selector->selector(), ind + " () ", env); + } else if (Cast(node)) { + Pseudo_Selector_Ptr selector = Cast(node); std::cerr << ind << "Pseudo_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -186,9 +191,9 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - debug_ast(&selector->expression(), ind + " <= ", env); - } else if (dynamic_cast(node)) { - Attribute_Selector_Ptr selector = dynamic_cast(node); + debug_ast(selector->expression(), ind + " <= ", env); + } else if (Cast(node)) { + Attribute_Selector_Ptr selector = Cast(node); std::cerr << ind << "Attribute_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -198,9 +203,9 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - debug_ast(&selector->value(), ind + "[" + selector->matcher() + "] ", env); - } else if (dynamic_cast(node)) { - Class_Selector_Ptr selector = dynamic_cast(node); + debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); + } else if (Cast(node)) { + Class_Selector_Ptr selector = Cast(node); std::cerr << ind << "Class_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -210,8 +215,8 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - } else if (dynamic_cast(node)) { - Id_Selector_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Id_Selector_Ptr selector = Cast(node); std::cerr << ind << "Id_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -221,8 +226,8 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - } else if (dynamic_cast(node)) { - Element_Selector_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Element_Selector_Ptr selector = Cast(node); std::cerr << ind << "Element_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; @@ -233,9 +238,9 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; std::cerr << std::endl; - } else if (dynamic_cast(node)) { + } else if (Cast(node)) { - Placeholder_Selector_Ptr selector = dynamic_cast(node); + Placeholder_Selector_Ptr selector = Cast(node); std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; std::cerr << " (" << pstate_source_position(selector) << ")" << " <" << selector->hash() << ">" @@ -245,212 +250,210 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; - } else if (dynamic_cast(node)) { - Simple_Selector* selector = dynamic_cast(node); + } else if (Cast(node)) { + Simple_Selector* selector = Cast(node); std::cerr << ind << "Simple_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; - } else if (dynamic_cast(node)) { - Selector_Schema_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Selector_Schema_Ptr selector = Cast(node); std::cerr << ind << "Selector_Schema " << selector; std::cerr << " (" << pstate_source_position(node) << ")" - << (selector->at_root() && selector->at_root() ? " [@ROOT]" : "") << " [@media:" << selector->media_block() << "]" - << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->connect_parent() ? " [connect-parent]": " -") << std::endl; - debug_ast(&selector->contents(), ind + " "); + debug_ast(selector->contents(), ind + " "); // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Selector_Ptr selector = dynamic_cast(node); + } else if (Cast(node)) { + Selector_Ptr selector = Cast(node); std::cerr << ind << "Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; - } else if (dynamic_cast(node)) { - Media_Query_Expression_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Media_Query_Expression_Ptr block = Cast(node); std::cerr << ind << "Media_Query_Expression " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") << std::endl; - debug_ast(&block->feature(), ind + " feature) "); - debug_ast(&block->value(), ind + " value) "); + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); - } else if (dynamic_cast(node)) { - Media_Query_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Media_Query_Ptr block = Cast(node); std::cerr << ind << "Media_Query " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << (block->is_negated() ? " [is_negated]": " -") << (block->is_restricted() ? " [is_restricted]": " -") << std::endl; - debug_ast(&block->media_type(), ind + " "); - for(const auto& i : block->elements()) { debug_ast(&i, ind + " ", env); } + debug_ast(block->media_type(), ind + " "); + for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Media_Block_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Media_Block_Ptr block = Cast(node); std::cerr << ind << "Media_Block " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->media_queries(), ind + " =@ "); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Supports_Block_Ptr block = dynamic_cast(node); + debug_ast(block->media_queries(), ind + " =@ "); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Supports_Block_Ptr block = Cast(node); std::cerr << ind << "Supports_Block " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->condition(), ind + " =@ "); - debug_ast(&block->block(), ind + " <>"); - } else if (dynamic_cast(node)) { - Supports_Operator_Ptr block = dynamic_cast(node); + debug_ast(block->condition(), ind + " =@ "); + debug_ast(block->block(), ind + " <>"); + } else if (Cast(node)) { + Supports_Operator_Ptr block = Cast(node); std::cerr << ind << "Supports_Operator " << block; std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; - debug_ast(&block->left(), ind + " left) "); - debug_ast(&block->right(), ind + " right) "); - } else if (dynamic_cast(node)) { - Supports_Negation_Ptr block = dynamic_cast(node); + debug_ast(block->left(), ind + " left) "); + debug_ast(block->right(), ind + " right) "); + } else if (Cast(node)) { + Supports_Negation_Ptr block = Cast(node); std::cerr << ind << "Supports_Negation " << block; std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; - debug_ast(&block->condition(), ind + " condition) "); - } else if (dynamic_cast(node)) { - At_Root_Query_Ptr block = dynamic_cast(node); + debug_ast(block->condition(), ind + " condition) "); + } else if (Cast(node)) { + At_Root_Query_Ptr block = Cast(node); std::cerr << ind << "At_Root_Query " << block; std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; - debug_ast(&block->feature(), ind + " feature) "); - debug_ast(&block->value(), ind + " value) "); - } else if (dynamic_cast(node)) { - Supports_Declaration_Ptr block = dynamic_cast(node); + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Supports_Declaration_Ptr block = Cast(node); std::cerr << ind << "Supports_Declaration " << block; std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; - debug_ast(&block->feature(), ind + " feature) "); - debug_ast(&block->value(), ind + " value) "); - } else if (dynamic_cast(node)) { - Block_Ptr root_block = dynamic_cast(node); + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Block_Ptr root_block = Cast(node); std::cerr << ind << "Block " << root_block; std::cerr << " (" << pstate_source_position(node) << ")"; if (root_block->is_root()) std::cerr << " [root]"; std::cerr << " " << root_block->tabs() << std::endl; - for(const Statement_Obj& i : root_block->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Warning_Ptr block = dynamic_cast(node); + for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Warning_Ptr block = Cast(node); std::cerr << ind << "Warning " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->message(), ind + " : "); - } else if (dynamic_cast(node)) { - Error_Ptr block = dynamic_cast(node); + debug_ast(block->message(), ind + " : "); + } else if (Cast(node)) { + Error_Ptr block = Cast(node); std::cerr << ind << "Error " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - } else if (dynamic_cast(node)) { - Debug_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Debug_Ptr block = Cast(node); std::cerr << ind << "Debug " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->value(), ind + " "); - } else if (dynamic_cast(node)) { - Comment_Ptr block = dynamic_cast(node); + debug_ast(block->value(), ind + " "); + } else if (Cast(node)) { + Comment_Ptr block = Cast(node); std::cerr << ind << "Comment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(&block->text(), ind + "// ", env); - } else if (dynamic_cast(node)) { - If_Ptr block = dynamic_cast(node); + debug_ast(block->text(), ind + "// ", env); + } else if (Cast(node)) { + If_Ptr block = Cast(node); std::cerr << ind << "If " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->predicate(), ind + " = "); - debug_ast(&block->block(), ind + " <>"); - debug_ast(&block->alternative(), ind + " ><"); - } else if (dynamic_cast(node)) { - Return_Ptr block = dynamic_cast(node); + debug_ast(block->predicate(), ind + " = "); + debug_ast(block->block(), ind + " <>"); + debug_ast(block->alternative(), ind + " ><"); + } else if (Cast(node)) { + Return_Ptr block = Cast(node); std::cerr << ind << "Return " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - } else if (dynamic_cast(node)) { - Extension_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Extension_Ptr block = Cast(node); std::cerr << ind << "Extension " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->selector(), ind + "-> ", env); - } else if (dynamic_cast(node)) { - Content_Ptr block = dynamic_cast(node); + debug_ast(block->selector(), ind + "-> ", env); + } else if (Cast(node)) { + Content_Ptr block = Cast(node); std::cerr << ind << "Content " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [@media:" << block->media_block() << "]"; std::cerr << " " << block->tabs() << std::endl; - } else if (dynamic_cast(node)) { - Import_Stub_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Import_Stub_Ptr block = Cast(node); std::cerr << ind << "Import_Stub " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << block->imp_path() << "] "; std::cerr << " " << block->tabs() << std::endl; - } else if (dynamic_cast(node)) { - Import_Ptr block = dynamic_cast(node); + } else if (Cast(node)) { + Import_Ptr block = Cast(node); std::cerr << ind << "Import " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; // std::vector files_; - for (auto imp : block->urls()) debug_ast(&imp, ind + "@: ", env); - debug_ast(&block->import_queries(), ind + "@@ "); - } else if (dynamic_cast(node)) { - Assignment_Ptr block = dynamic_cast(node); + for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); + debug_ast(block->import_queries(), ind + "@@ "); + } else if (Cast(node)) { + Assignment_Ptr block = Cast(node); std::cerr << ind << "Assignment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; - debug_ast(&block->value(), ind + "=", env); - } else if (dynamic_cast(node)) { - Declaration_Ptr block = dynamic_cast(node); + debug_ast(block->value(), ind + "=", env); + } else if (Cast(node)) { + Declaration_Ptr block = Cast(node); std::cerr << ind << "Declaration " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->property(), ind + " prop: ", env); - debug_ast(&block->value(), ind + " value: ", env); - debug_ast(&block->block(), ind + " ", env); - } else if (dynamic_cast(node)) { - Keyframe_Rule_Ptr has_block = dynamic_cast(node); + debug_ast(block->property(), ind + " prop: ", env); + debug_ast(block->value(), ind + " value: ", env); + debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Keyframe_Rule_Ptr has_block = Cast(node); std::cerr << ind << "Keyframe_Rule " << has_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << has_block->tabs() << std::endl; - if (has_block->name()) debug_ast(&has_block->name(), ind + "@"); - if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Directive_Ptr block = dynamic_cast(node); + if (has_block->name()) debug_ast(has_block->name(), ind + "@"); + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Directive_Ptr block = Cast(node); std::cerr << ind << "Directive " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; - debug_ast(&block->selector(), ind + "~", env); - debug_ast(&block->value(), ind + "+", env); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Each_Ptr block = dynamic_cast(node); + debug_ast(block->selector(), ind + "~", env); + debug_ast(block->value(), ind + "+", env); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Each_Ptr block = Cast(node); std::cerr << ind << "Each " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - For_Ptr block = dynamic_cast(node); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + For_Ptr block = Cast(node); std::cerr << ind << "For " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - While_Ptr block = dynamic_cast(node); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + While_Ptr block = Cast(node); std::cerr << ind << "While " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Definition_Ptr block = dynamic_cast(node); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Definition_Ptr block = Cast(node); std::cerr << ind << "Definition " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << block->name() << "] "; @@ -459,35 +462,34 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) // std::cerr << " [signature: " << block->signature() << "] "; std::cerr << " [native: " << block->native_function() << "] "; std::cerr << " " << block->tabs() << std::endl; - debug_ast(&block->parameters(), ind + " params: ", env); - if (block->block()) debug_ast(&block->block(), ind + " ", env); - } else if (dynamic_cast(node)) { - Mixin_Call_Ptr block = dynamic_cast(node); + debug_ast(block->parameters(), ind + " params: ", env); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Mixin_Call_Ptr block = Cast(node); std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); std::cerr << " (" << pstate_source_position(block) << ")"; std::cerr << " [" << block->name() << "]"; std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; - debug_ast(&block->arguments(), ind + " args: "); - if (block->block()) debug_ast(&block->block(), ind + " ", env); - } else if (Ruleset_Ptr ruleset = dynamic_cast(node)) { + debug_ast(block->arguments(), ind + " args: "); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Ruleset_Ptr ruleset = Cast(node)) { std::cerr << ind << "Ruleset " << ruleset; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [indent: " << ruleset->tabs() << "]"; std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << (ruleset->at_root() ? " [@ROOT]" : ""); std::cerr << (ruleset->is_root() ? " [root]" : ""); std::cerr << std::endl; - debug_ast(&ruleset->selector(), ind + ">"); - debug_ast(&ruleset->block(), ind + " "); - } else if (dynamic_cast(node)) { - Block_Ptr block = dynamic_cast(node); + debug_ast(ruleset->selector(), ind + ">"); + debug_ast(ruleset->block(), ind + " "); + } else if (Cast(node)) { + Block_Ptr block = Cast(node); std::cerr << ind << "Block " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); std::cerr << " [indent: " << block->tabs() << "]" << std::endl; - for(const Statement_Obj& i : block->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Textual_Ptr expression = dynamic_cast(node); + for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Textual_Ptr expression = Cast(node); std::cerr << ind << "Textual " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; if (expression->type() == Textual::NUMBER) std::cerr << " [NUMBER]"; @@ -498,24 +500,24 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; if (expression->is_delayed()) std::cerr << " [delayed]"; std::cerr << std::endl; - } else if (dynamic_cast(node)) { - Variable_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Variable_Ptr expression = Cast(node); std::cerr << ind << "Variable " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->name() << "]" << std::endl; std::string name(expression->name()); - if (env && env->has(name)) debug_ast(SASS_MEMORY_CAST(Expression, (*env)[name]), ind + " -> ", env); - } else if (dynamic_cast(node)) { - Function_Call_Schema_Ptr expression = dynamic_cast(node); + if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); + } else if (Cast(node)) { + Function_Call_Schema_Ptr expression = Cast(node); std::cerr << ind << "Function_Call_Schema " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << "" << std::endl; - debug_ast(&expression->name(), ind + "name: ", env); - debug_ast(&expression->arguments(), ind + " args: ", env); - } else if (dynamic_cast(node)) { - Function_Call_Ptr expression = dynamic_cast(node); + debug_ast(expression->name(), ind + "name: ", env); + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (Cast(node)) { + Function_Call_Ptr expression = Cast(node); std::cerr << ind << "Function_Call " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; @@ -523,9 +525,9 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->is_delayed()) std::cerr << " [delayed]"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << std::endl; - debug_ast(&expression->arguments(), ind + " args: ", env); - } else if (dynamic_cast(node)) { - Arguments_Ptr expression = dynamic_cast(node); + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (Cast(node)) { + Arguments_Ptr expression = Cast(node); std::cerr << ind << "Arguments " << expression; if (expression->is_delayed()) std::cerr << " [delayed]"; std::cerr << " (" << pstate_source_position(node) << ")"; @@ -533,41 +535,41 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; std::cerr << std::endl; - for(const Argument_Obj& i : expression->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Argument_Ptr expression = dynamic_cast(node); + for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Argument_Ptr expression = Cast(node); std::cerr << ind << "Argument " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->value() << "]"; + std::cerr << " [" << expression->value().ptr() << "]"; std::cerr << " [name: " << expression->name() << "] "; std::cerr << " [rest: " << expression->is_rest_argument() << "] "; std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; - debug_ast(&expression->value(), ind + " value: ", env); - } else if (dynamic_cast(node)) { - Parameters_Ptr expression = dynamic_cast(node); + debug_ast(expression->value(), ind + " value: ", env); + } else if (Cast(node)) { + Parameters_Ptr expression = Cast(node); std::cerr << ind << "Parameters " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; std::cerr << std::endl; - for(const Parameter_Obj& i : expression->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Parameter_Ptr expression = dynamic_cast(node); + for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Parameter_Ptr expression = Cast(node); std::cerr << ind << "Parameter " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [default: " << expression->default_value() << "] "; + std::cerr << " [default: " << expression->default_value().ptr() << "] "; std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; - } else if (dynamic_cast(node)) { - Unary_Expression_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Unary_Expression_Ptr expression = Cast(node); std::cerr << ind << "Unary_Expression " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [delayed: " << expression->is_delayed() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->type() << "]" << std::endl; - debug_ast(&expression->operand(), ind + " operand: ", env); - } else if (dynamic_cast(node)) { - Binary_Expression_Ptr expression = dynamic_cast(node); + debug_ast(expression->operand(), ind + " operand: ", env); + } else if (Cast(node)) { + Binary_Expression_Ptr expression = Cast(node); std::cerr << ind << "Binary_Expression " << expression; if (expression->is_interpolant()) std::cerr << " [is interpolant] "; if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; @@ -577,67 +579,69 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " [ws_after: " << expression->op().ws_after << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->type_name() << "]" << std::endl; - debug_ast(&expression->left(), ind + " left: ", env); - debug_ast(&expression->right(), ind + " right: ", env); - } else if (dynamic_cast(node)) { - Map_Ptr expression = dynamic_cast(node); + debug_ast(expression->left(), ind + " left: ", env); + debug_ast(expression->right(), ind + " right: ", env); + } else if (Cast(node)) { + Map_Ptr expression = Cast(node); std::cerr << ind << "Map " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [Hashed]" << std::endl; for (const auto& i : expression->elements()) { - debug_ast(&i.first, ind + " key: "); - debug_ast(&i.second, ind + " val: "); + debug_ast(i.first, ind + " key: "); + debug_ast(i.second, ind + " val: "); } - } else if (dynamic_cast(node)) { - List_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + List_Ptr expression = Cast(node); std::cerr << ind << "List " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " (" << expression->length() << ") " << - (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map" : "Space ") << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << " [delayed: " << expression->is_delayed() << "] " << " [interpolant: " << expression->is_interpolant() << "] " << " [listized: " << expression->from_selector() << "] " << " [arglist: " << expression->is_arglist() << "] " << + " [bracketed: " << expression->is_bracketed() << "] " << + " [expanded: " << expression->is_expanded() << "] " << " [hash: " << expression->hash() << "] " << std::endl; - for(const auto& i : expression->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Content_Ptr expression = dynamic_cast(node); + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Content_Ptr expression = Cast(node); std::cerr << ind << "Content " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [@media:" << expression->media_block() << "]"; std::cerr << " [Statement]" << std::endl; - } else if (dynamic_cast(node)) { - Boolean_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Boolean_Ptr expression = Cast(node); std::cerr << ind << "Boolean " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << "]" << std::endl; - } else if (dynamic_cast(node)) { - Color_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Color_Ptr expression = Cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [delayed: " << expression->is_delayed() << "] "; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; - } else if (dynamic_cast(node)) { - Number_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Number_Ptr expression = Cast(node); std::cerr << ind << "Number " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << expression->unit() << "]" << " [hash: " << expression->hash() << "] " << std::endl; - } else if (dynamic_cast(node)) { - Null_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Null_Ptr expression = Cast(node); std::cerr << ind << "Null " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [interpolant: " << expression->is_interpolant() << "] " // " [hash: " << expression->hash() << "] " << std::endl; - } else if (dynamic_cast(node)) { - String_Quoted_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + String_Quoted_Ptr expression = Cast(node); std::cerr << ind << "String_Quoted " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << prettyprint(expression->value()) << "]"; @@ -645,8 +649,8 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->is_interpolant()) std::cerr << " [interpolant]"; if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (dynamic_cast(node)) { - String_Constant_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + String_Constant_Ptr expression = Cast(node); std::cerr << ind << "String_Constant " << expression; if (expression->concrete_type()) { std::cerr << " " << expression->concrete_type(); @@ -656,8 +660,8 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->is_delayed()) std::cerr << " [delayed]"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (dynamic_cast(node)) { - String_Schema_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + String_Schema_Ptr expression = Cast(node); std::cerr << ind << "String_Schema " << expression; std::cerr << " (" << pstate_source_position(expression) << ")"; std::cerr << " " << expression->concrete_type(); @@ -668,16 +672,16 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - for(const auto& i : expression->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - String_Ptr expression = dynamic_cast(node); + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + String_Ptr expression = Cast(node); std::cerr << ind << "String " << expression; std::cerr << " " << expression->concrete_type(); std::cerr << " (" << pstate_source_position(node) << ")"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (dynamic_cast(node)) { - Expression_Ptr expression = dynamic_cast(node); + } else if (Cast(node)) { + Expression_Ptr expression = Cast(node); std::cerr << ind << "Expression " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; switch (expression->concrete_type()) { @@ -696,14 +700,14 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; } std::cerr << std::endl; - } else if (dynamic_cast(node)) { - Has_Block_Ptr has_block = dynamic_cast(node); + } else if (Cast(node)) { + Has_Block_Ptr has_block = Cast(node); std::cerr << ind << "Has_Block " << has_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << has_block->tabs() << std::endl; - if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(&i, ind + " ", env); } - } else if (dynamic_cast(node)) { - Statement_Ptr statement = dynamic_cast(node); + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Statement_Ptr statement = Cast(node); std::cerr << ind << "Statement " << statement; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << statement->tabs() << std::endl; @@ -735,7 +739,7 @@ inline void debug_node(Node* node, std::string ind = "") std::cerr << node << " "; if (node->got_line_feed) std::cerr << "[LF] "; std::cerr << std::endl; - debug_ast(&node->selector(), ind + " "); + debug_ast(node->selector(), ind + " "); } else if (node->isCollection()) { std::cerr << ind; std::cerr << "Collection "; @@ -776,21 +780,18 @@ inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") { if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; for(auto const &it : map.values()) { - debug_ast(&it.first, ind + "first: "); - debug_ast(&it.second, ind + "second: "); + debug_ast(it.first, ind + "first: "); + debug_ast(it.second, ind + "second: "); } if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; } -typedef std::pair ExtensionPair; -typedef std::vector SubsetMapEntries; - -inline void debug_subset_entries(SubsetMapEntries* entries, std::string ind = "") +inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") { if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; for(auto const &pair : *entries) { - debug_ast(&pair.first, ind + "first: "); - debug_ast(&pair.second, ind + "second: "); + debug_ast(pair.first, ind + "first: "); + debug_ast(pair.second, ind + "second: "); } if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; } diff --git a/src/libsass/src/environment.cpp b/src/libsass/src/environment.cpp index 083a53cc6..7a7e9b1d1 100755 --- a/src/libsass/src/environment.cpp +++ b/src/libsass/src/environment.cpp @@ -178,8 +178,8 @@ namespace Sass { std::cerr << prefix << std::string(indent, ' ') << "== " << this << std::endl; for (typename std::map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { - std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second; - if (Value_Ptr val = SASS_MEMORY_CAST_PTR(Value, i->second)) + std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second; + if (Value_Ptr val = Cast(i->second)) { std::cerr << " : " << val->to_string(); } std::cerr << std::endl; } diff --git a/src/libsass/src/environment.hpp b/src/libsass/src/environment.hpp index 199b690db..dd985b15c 100755 --- a/src/libsass/src/environment.hpp +++ b/src/libsass/src/environment.hpp @@ -87,6 +87,9 @@ namespace Sass { }; + // define typedef for our use case + typedef Environment Env; + } #endif diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp index 2ab20c90e..219d47e45 100755 --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -24,6 +24,7 @@ #include "parser.hpp" #include "expand.hpp" #include "color_maps.hpp" +#include "sass_functions.hpp" namespace Sass { @@ -86,7 +87,7 @@ namespace Sass { if (a->is_global()) { if (a->is_default()) { if (env->has_global(var)) { - Expression_Ptr e = SASS_MEMORY_CAST(Expression, env->get_global(var)); + Expression_Ptr e = Cast(env->get_global(var)); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(this)); } @@ -105,7 +106,7 @@ namespace Sass { while (cur && cur->is_lexical()) { if (cur->has_local(var)) { if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Ptr e = SASS_MEMORY_CAST(Expression, node); + Expression_Ptr e = Cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { cur->set_local(var, a->value()->perform(this)); } @@ -121,7 +122,7 @@ namespace Sass { } else if (env->has_global(var)) { if (AST_Node_Obj node = env->get_global(var)) { - Expression_Ptr e = SASS_MEMORY_CAST(Expression, node); + Expression_Ptr e = Cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(this)); } @@ -170,8 +171,8 @@ namespace Sass { if (high->concrete_type() != Expression::NUMBER) { throw Exception::TypeMismatch(*high, "integer"); } - Number_Obj sass_start = SASS_MEMORY_CAST(Number, low); - Number_Obj sass_end = SASS_MEMORY_CAST(Number, high); + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); // check if units are valid for sequence if (sass_start->unit() != sass_end->unit()) { std::stringstream msg; msg << "Incompatible units: '" @@ -224,19 +225,19 @@ namespace Sass { List_Obj list = 0; Map_Ptr map = 0; if (expr->concrete_type() == Expression::MAP) { - map = SASS_MEMORY_CAST(Map, expr); + map = Cast(expr); } - else if (Selector_List_Ptr ls = SASS_MEMORY_CAST(Selector_List, expr)) { + else if (Selector_List_Ptr ls = Cast(expr)) { Listize listize; Expression_Obj rv = ls->perform(&listize); - list = dynamic_cast(&rv); + list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); list->append(expr); } else { - list = SASS_MEMORY_CAST(List, expr); + list = Cast(expr); } Block_Obj body = e->block(); @@ -252,8 +253,8 @@ namespace Sass { variable->append(value); env.set_local(variables[0], variable); } else { - env.set_local(variables[0], &key); - env.set_local(variables[1], &value); + env.set_local(variables[0], key); + env.set_local(variables[1], value); } val = body->perform(this); @@ -261,15 +262,15 @@ namespace Sass { } } else { - if (list->length() == 1 && SASS_MEMORY_CAST(Selector_List, list)) { - list = SASS_MEMORY_CAST(List, list); + if (list->length() == 1 && Cast(list)) { + list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Ptr e = &list->at(i); + Expression_Ptr e = list->at(i); // unwrap value if the expression is an argument - if (Argument_Ptr arg = SASS_MEMORY_CAST_PTR(Argument, e)) e = &arg->value(); + if (Argument_Ptr arg = Cast(e)) e = arg->value(); // check if we got passed a list of args (investigate) - if (List_Ptr scalars = SASS_MEMORY_CAST_PTR(List, e)) { + if (List_Ptr scalars = Cast(e)) { if (variables.size() == 1) { Expression_Ptr var = scalars; env.set_local(variables[0], var); @@ -278,7 +279,7 @@ namespace Sass { for (size_t j = 0, K = variables.size(); j < K; ++j) { Expression_Ptr res = j >= scalars->length() ? SASS_MEMORY_NEW(Null, expr->pstate()) - : &scalars->at(j); + : scalars->at(j); env.set_local(variables[j], res); } } @@ -334,17 +335,28 @@ namespace Sass { // try to use generic function if (env->has("@warn[f]")) { - Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@warn[f]"]); + // add call stack entry + ctx.callee_stack.push_back({ + "@warn", + w->pstate().path, + w->pstate().line + 1, + w->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@warn[f]"]); // Block_Obj body = def->block(); // Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Sass_Function_Fn c_func = sass_function_get_function(c_function); To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); sass_list_set_value(c_args, 0, message->perform(&to_c)); union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); sass_delete_value(c_args); sass_delete_value(c_val); return 0; @@ -370,17 +382,28 @@ namespace Sass { // try to use generic function if (env->has("@error[f]")) { - Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@error[f]"]); + // add call stack entry + ctx.callee_stack.push_back({ + "@error", + e->pstate().path, + e->pstate().line + 1, + e->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@error[f]"]); // Block_Obj body = def->block(); // Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Sass_Function_Fn c_func = sass_function_get_function(c_function); To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); sass_list_set_value(c_args, 0, message->perform(&to_c)); union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); sass_delete_value(c_args); sass_delete_value(c_val); return 0; @@ -403,17 +426,28 @@ namespace Sass { // try to use generic function if (env->has("@debug[f]")) { - Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@debug[f]"]); + // add call stack entry + ctx.callee_stack.push_back({ + "@debug", + d->pstate().path, + d->pstate().line + 1, + d->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@debug[f]"]); // Block_Obj body = def->block(); // Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Sass_Function_Fn c_func = sass_function_get_function(c_function); To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); sass_list_set_value(c_args, 0, message->perform(&to_c)); union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); sass_delete_value(c_args); sass_delete_value(c_val); return 0; @@ -441,8 +475,8 @@ namespace Sass { l->length() / 2); for (size_t i = 0, L = l->length(); i < L; i += 2) { - Expression_Ptr key = (*l)[i+0]->perform(this); - Expression_Ptr val = (*l)[i+1]->perform(this); + Expression_Obj key = (*l)[i+0]->perform(this); + Expression_Obj val = (*l)[i+1]->perform(this); // make sure the color key never displays its real name key->is_delayed(true); // verified *lm << std::make_pair(key, val); @@ -461,7 +495,8 @@ namespace Sass { l->pstate(), l->length(), l->separator(), - l->is_arglist()); + l->is_arglist(), + l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { ll->append((*l)[i]->perform(this)); } @@ -504,14 +539,14 @@ namespace Sass { String_Schema_Obj ret_schema; Binary_Expression_Obj b = b_in; - enum Sass_OP op_type = b->type(); + enum Sass_OP op_type = b->optype(); // only the last item will be used to eval the binary expression - if (String_Schema_Ptr s_l = SASS_MEMORY_CAST(String_Schema, b->left())) { + if (String_Schema_Ptr s_l = Cast(b->left())) { if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), &s_l->last(), b->right()); + b->op(), s_l->last(), b->right()); bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified for (size_t i = 0; i < s_l->length() - 1; ++i) { ret_schema->append(s_l->at(i)->perform(this)); @@ -520,12 +555,12 @@ namespace Sass { return ret_schema->perform(this); } } - if (String_Schema_Ptr s_r = SASS_MEMORY_CAST(String_Schema, b->right())) { + if (String_Schema_Ptr s_r = Cast(b->right())) { if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), b->left(), &s_r->first()); + b->op(), b->left(), s_r->first()); bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified ret_schema->append(bin_ex->perform(this)); for (size_t i = 1; i < s_r->length(); ++i) { @@ -580,17 +615,17 @@ namespace Sass { } // not a logical connective, so go ahead and eval the rhs rhs = rhs->perform(this); - AST_Node_Obj lu = &lhs; - AST_Node_Obj ru = &rhs; + AST_Node_Obj lu = lhs; + AST_Node_Obj ru = rhs; Expression::Concrete_Type l_type = lhs->concrete_type(); Expression::Concrete_Type r_type = rhs->concrete_type(); // Is one of the operands an interpolant? - String_Schema_Obj s1 = SASS_MEMORY_CAST(String_Schema, b->left()); - String_Schema_Obj s2 = SASS_MEMORY_CAST(String_Schema, b->right()); - Binary_Expression_Obj b1 = SASS_MEMORY_CAST(Binary_Expression, b->left()); - Binary_Expression_Obj b2 = SASS_MEMORY_CAST(Binary_Expression, b->right()); + String_Schema_Obj s1 = Cast(b->left()); + String_Schema_Obj s2 = Cast(b->right()); + Binary_Expression_Obj b1 = Cast(b->left()); + Binary_Expression_Obj b2 = Cast(b->right()); bool schema_op = false; @@ -604,7 +639,7 @@ namespace Sass { if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || op_type == Sass_OP::EQ) { // If possible upgrade LHS to a number (for number to string compare) - if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, lhs)) { + if (String_Constant_Ptr str = Cast(lhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { @@ -613,7 +648,7 @@ namespace Sass { } } // If possible upgrade RHS to a number (for string to number compare) - if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, rhs)) { + if (String_Constant_Ptr str = Cast(rhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { @@ -624,13 +659,13 @@ namespace Sass { } To_Value to_value(ctx); - Value_Obj v_l = dynamic_cast(lhs->perform(&to_value)); - Value_Obj v_r = dynamic_cast(rhs->perform(&to_value)); + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); l_type = lhs->concrete_type(); r_type = rhs->concrete_type(); if (s2 && s2->has_interpolants() && s2->length()) { - Textual_Obj front = SASS_MEMORY_CAST(Textual, s2->elements().front()); + Textual_Obj front = Cast(s2->elements().front()); if (front && !front->is_interpolant()) { // XXX: this is never hit via spec tests @@ -679,30 +714,30 @@ namespace Sass { try { ParserState pstate(b->pstate()); if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { - Number_Ptr l_n = SASS_MEMORY_CAST(Number, lhs); - Number_Ptr r_n = SASS_MEMORY_CAST(Number, rhs); + Number_Ptr l_n = Cast(lhs); + Number_Ptr r_n = Cast(rhs); rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, &pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { - Number_Ptr l_n = SASS_MEMORY_CAST(Number, lhs); - Color_Ptr r_c = SASS_MEMORY_CAST(Color, rhs); + Number_Ptr l_n = Cast(lhs); + Color_Ptr r_c = Cast(rhs); rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, &pstate); } else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { - Color_Ptr l_c = SASS_MEMORY_CAST(Color, lhs); - Number_Ptr r_n = SASS_MEMORY_CAST(Number, rhs); + Color_Ptr l_c = Cast(lhs); + Number_Ptr r_n = Cast(rhs); rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, &pstate); } else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { - Color_Ptr l_c = SASS_MEMORY_CAST(Color, lhs); - Color_Ptr r_c = SASS_MEMORY_CAST(Color, rhs); + Color_Ptr l_c = Cast(lhs); + Color_Ptr r_c = Cast(rhs); rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, &pstate); } else { To_Value to_value(ctx); // this will leak if perform does not return a value! - Value_Obj v_l = SASS_MEMORY_CAST_PTR(Value, lhs->perform(&to_value)); - Value_Obj v_r = SASS_MEMORY_CAST_PTR(Value, rhs->perform(&to_value)); + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); bool interpolant = b->is_right_interpolant() || b->is_left_interpolant() || b->is_interpolant(); @@ -716,12 +751,12 @@ namespace Sass { throw Exception::InvalidValue(*v_r); } Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, &pstate, !interpolant); // pass true to compress - if (String_Constant_Ptr str = dynamic_cast(ex)) + if (String_Constant_Ptr str = Cast(ex)) { if (str->concrete_type() == Expression::STRING) { - String_Constant_Ptr lstr = SASS_MEMORY_CAST(String_Constant, lhs); - String_Constant_Ptr rstr = SASS_MEMORY_CAST(String_Constant, rhs); + String_Constant_Ptr lstr = Cast(lhs); + String_Constant_Ptr rstr = Cast(rhs); if (op_type != Sass_OP::SUB) { if (String_Constant_Ptr org = lstr ? lstr : rstr) { str->quote_mark(org->quote_mark()); } @@ -753,31 +788,32 @@ namespace Sass { Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) { Expression_Obj operand = u->operand()->perform(this); - if (u->type() == Unary_Expression::NOT) { + if (u->optype() == Unary_Expression::NOT) { Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); result->value(!result->value()); return result; } - else if (operand->concrete_type() == Expression::NUMBER) { - Number_Ptr result = SASS_MEMORY_NEW(Number, static_cast(&operand)); - result->value(u->type() == Unary_Expression::MINUS - ? -result->value() - : result->value()); - return result; + else if (Number_Obj nr = Cast(operand)) { + // negate value for minus unary expression + if (u->optype() == Unary_Expression::MINUS) { + Number_Obj cpy = SASS_MEMORY_COPY(nr); + cpy->value( - cpy->value() ); // negate value + return cpy.detach(); // return the copy + } + // nothing for positive + return nr.detach(); } else { // Special cases: +/- variables which evaluate to null ouput just +/-, // but +/- null itself outputs the string - if (operand->concrete_type() == Expression::NULL_VAL && SASS_MEMORY_CAST(Variable, u->operand())) { + if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); } // Never apply unary opertions on colors @see #2140 - else if (operand->concrete_type() == Expression::COLOR) { - Color_Ptr c = dynamic_cast(&operand); - + else if (Color_Ptr color = Cast(operand)) { // Use the color name if this was eval with one - if (c->disp().length() > 0) { - operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), c->disp()); + if (color->disp().length() > 0) { + operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); u->operand(operand); } } @@ -785,10 +821,9 @@ namespace Sass { u->operand(operand); } - String_Constant_Ptr result = SASS_MEMORY_NEW(String_Quoted, - u->pstate(), - u->inspect()); - return result; + return SASS_MEMORY_NEW(String_Quoted, + u->pstate(), + u->inspect()); } // unreachable return u; @@ -811,15 +846,15 @@ namespace Sass { if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { if (!env->has("*[f]")) { for (Argument_Obj arg : args->elements()) { - if (List_Obj ls = SASS_MEMORY_CAST(List, arg->value())) { + if (List_Obj ls = Cast(arg->value())) { if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate()); } } - args = SASS_MEMORY_CAST_PTR(Arguments, args->perform(this)); + args = Cast(args->perform(this)); Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, c->pstate(), c->name(), - &args); + args); if (args->has_named_arguments()) { error("Function " + c->name() + " doesn't support keyword arguments", c->pstate()); } @@ -839,9 +874,9 @@ namespace Sass { args->set_delayed(false); // verified } if (full_name != "if[f]") { - args = SASS_MEMORY_CAST_PTR(Arguments, args->perform(this)); + args = Cast(args->perform(this)); } - Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)[full_name]); + Definition_Ptr def = Cast((*env)[full_name]); if (def->is_overload_stub()) { std::stringstream ss; @@ -849,7 +884,7 @@ namespace Sass { // account for rest arguments if (args->has_rest_argument() && args->length() > 0) { // get the rest arguments list - List_Ptr rest = SASS_MEMORY_CAST(List, args->last()->value()); + List_Ptr rest = Cast(args->last()->value()); // arguments before rest argument plus rest if (rest) L += rest->length() - 1; } @@ -857,7 +892,7 @@ namespace Sass { full_name = ss.str(); std::string resolved_name(full_name); if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate()); - def = SASS_MEMORY_CAST(Definition, (*env)[resolved_name]); + def = Cast((*env)[resolved_name]); } Expression_Obj result = c; @@ -873,6 +908,15 @@ namespace Sass { bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); exp.backtrace_stack.push_back(&here); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + // eval the body if user-defined or special, invoke underlying CPP function if native if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { result = body->perform(this); @@ -884,6 +928,7 @@ namespace Sass { error(std::string("Function ") + c->name() + " did not return a value", c->pstate()); } exp.backtrace_stack.pop_back(); + ctx.callee_stack.pop_back(); } // else if it's a user-defined c function @@ -893,8 +938,8 @@ namespace Sass { if (full_name == "*[f]") { String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); - new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), &str)); - new_args->concat(&args); + new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); + new_args->concat(args); args = new_args; } @@ -904,14 +949,22 @@ namespace Sass { Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); exp.backtrace_stack.push_back(&here); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_C_FUNCTION, + { env } + }); To_C to_c; - union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA); + union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); for(size_t i = 0; i < params->length(); i++) { Parameter_Obj param = params->at(i); std::string key = param->name(); AST_Node_Obj node = fn_env.get_local(key); - Expression_Obj arg = SASS_MEMORY_CAST(Expression, node); + Expression_Obj arg = Cast(node); sass_list_set_value(c_args, i, arg->perform(&to_c)); } union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); @@ -923,6 +976,7 @@ namespace Sass { result = cval_to_astnode(c_val, backtrace(), c->pstate()); exp.backtrace_stack.pop_back(); + ctx.callee_stack.pop_back(); sass_delete_value(c_args); if (c_val != c_args) sass_delete_value(c_val); @@ -955,15 +1009,15 @@ namespace Sass { Expression_Obj value = 0; Env* env = environment(); if (env->has(name)) { - value = SASS_MEMORY_CAST(Expression, (*env)[name]); + value = Cast((*env)[name]); } else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); - if (typeid(*value) == typeid(Argument)) { - value = SASS_MEMORY_CAST(Argument, value)->value(); + if (Argument* arg = Cast(value)) { + value = arg->value(); } // behave according to as ruby sass (add leading zero) - if (Number_Ptr nr = SASS_MEMORY_CAST(Number, value)) { + if (Number_Ptr nr = Cast(value)) { nr->zero(true); } @@ -971,7 +1025,7 @@ namespace Sass { if (force) value->is_expanded(false); value->set_delayed(false); // verified value = value->perform(this); - if(!force) (*env)[name] = &value; + if(!force) (*env)[name] = value; return value.detach(); } @@ -993,7 +1047,7 @@ namespace Sass { if (unit_pos == std::string::npos) unit_pos = text.length(); const std::string& num = text.substr(num_pos, unit_pos - num_pos); - switch (t->type()) + switch (t->valtype()) { case Textual::NUMBER: result = SASS_MEMORY_NEW(Number, @@ -1068,7 +1122,7 @@ namespace Sass { bool needs_closing_brace = false; - if (Arguments_Ptr args = SASS_MEMORY_CAST(Arguments, ex)) { + if (Arguments_Ptr args = Cast(ex)) { List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); for(auto arg : args->elements()) { ll->append(arg->value()); @@ -1078,15 +1132,15 @@ namespace Sass { res += "("; ex = ll; } - if (Number_Ptr nr = SASS_MEMORY_CAST(Number, ex)) { + if (Number_Ptr nr = Cast(ex)) { if (!nr->is_valid_css_unit()) { throw Exception::InvalidValue(*nr); } } - if (Argument_Ptr arg = SASS_MEMORY_CAST(Argument, ex)) { - ex = &arg->value(); + if (Argument_Ptr arg = Cast(ex)) { + ex = arg->value(); } - if (String_Quoted_Ptr sq = SASS_MEMORY_CAST(String_Quoted, ex)) { + if (String_Quoted_Ptr sq = Cast(ex)) { if (was_itpl) { bool was_interpolant = ex->is_interpolant(); ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); @@ -1094,22 +1148,22 @@ namespace Sass { } } - if (SASS_MEMORY_CAST(Null, ex)) { return; } + if (Cast(ex)) { return; } // parent selector needs another go - if (SASS_MEMORY_CAST(Parent_Selector, ex)) { + if (Cast(ex)) { // XXX: this is never hit via spec tests ex = ex->perform(this); } - if (List_Ptr l = SASS_MEMORY_CAST(List, ex)) { + if (List_Ptr l = Cast(ex)) { List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); // this fixes an issue with bourbon sample, not really sure why - // if (l->size() && dynamic_cast((*l)[0])) { res += ""; } + // if (l->size() && Cast((*l)[0])) { res += ""; } for(Expression_Obj item : *l) { item->is_interpolant(l->is_interpolant()); - std::string rl(""); interpolation(ctx, rl, &item, into_quotes, l->is_interpolant()); - bool is_null = dynamic_cast(&item) != 0; // rl != "" + std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); + bool is_null = Cast(item) != 0; // rl != "" if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); } // Check indicates that we probably should not get a list @@ -1151,9 +1205,9 @@ namespace Sass { size_t L = s->length(); bool into_quotes = false; if (L > 1) { - if (!SASS_MEMORY_CAST(String_Quoted, (*s)[0]) && !SASS_MEMORY_CAST(String_Quoted, (*s)[L - 1])) { - if (String_Constant_Ptr l = SASS_MEMORY_CAST(String_Constant, (*s)[0])) { - if (String_Constant_Ptr r = SASS_MEMORY_CAST(String_Constant, (*s)[L - 1])) { + if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { + if (String_Constant_Ptr l = Cast((*s)[0])) { + if (String_Constant_Ptr r = Cast((*s)[L - 1])) { if (r->value().size() > 0) { if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; @@ -1166,12 +1220,12 @@ namespace Sass { bool was_interpolant = false; std::string res(""); for (size_t i = 0; i < L; ++i) { - bool is_quoted = SASS_MEMORY_CAST(String_Quoted, (*s)[i]) != NULL; + bool is_quoted = Cast((*s)[i]) != NULL; if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } Expression_Obj ex = (*s)[i]->perform(this); interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); - was_quoted = SASS_MEMORY_CAST(String_Quoted, (*s)[i]) != NULL; + was_quoted = Cast((*s)[i]) != NULL; was_interpolant = (*s)[i]->is_interpolant(); } @@ -1217,8 +1271,8 @@ namespace Sass { Expression_Ptr right = c->right()->perform(this); Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, c->pstate(), - dynamic_cast(left), - dynamic_cast(right), + Cast(left), + Cast(right), c->operand()); return cc; } @@ -1228,7 +1282,7 @@ namespace Sass { Expression_Ptr condition = c->condition()->perform(this); Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, c->pstate(), - dynamic_cast(condition)); + Cast(condition)); return cc; } @@ -1260,7 +1314,7 @@ namespace Sass { value = (value ? value->perform(this) : 0); Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, e->pstate(), - dynamic_cast(&feature), + Cast(feature), value); return ee; } @@ -1268,10 +1322,10 @@ namespace Sass { Expression_Ptr Eval::operator()(Media_Query_Ptr q) { String_Obj t = q->media_type(); - t = static_cast(&t ? t->perform(this) : 0); + t = static_cast(t.isNull() ? 0 : t->perform(this)); Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, q->pstate(), - &t, + t, q->length(), q->is_negated(), q->is_restricted()); @@ -1285,18 +1339,18 @@ namespace Sass { { Expression_Obj feature = e->feature(); feature = (feature ? feature->perform(this) : 0); - if (feature && SASS_MEMORY_CAST(String_Quoted, feature)) { + if (feature && Cast(feature)) { feature = SASS_MEMORY_NEW(String_Quoted, feature->pstate(), - SASS_MEMORY_CAST(String_Quoted, feature)->value()); + Cast(feature)->value()); } Expression_Obj value = e->value(); value = (value ? value->perform(this) : 0); - if (value && SASS_MEMORY_CAST(String_Quoted, value)) { + if (value && Cast(value)) { // XXX: this is never hit via spec tests value = SASS_MEMORY_NEW(String_Quoted, value->pstate(), - SASS_MEMORY_CAST(String_Quoted, value)->value()); + Cast(value)->value()); } return SASS_MEMORY_NEW(Media_Query_Expression, e->pstate(), @@ -1328,7 +1382,7 @@ namespace Sass { SASS_COMMA, true); wrapper->append(val); - val = &wrapper; + val = wrapper; } } return SASS_MEMORY_NEW(Argument, @@ -1345,7 +1399,7 @@ namespace Sass { if (a->length() == 0) return aa.detach(); for (size_t i = 0, L = a->length(); i < L; ++i) { Expression_Obj rv = (*a)[i]->perform(this); - Argument_Ptr arg = SASS_MEMORY_CAST(Argument, rv); + Argument_Ptr arg = Cast(rv); if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { aa->append(arg); } @@ -1353,11 +1407,11 @@ namespace Sass { if (a->has_rest_argument()) { Expression_Obj rest = a->get_rest_argument()->perform(this); - Expression_Obj splat = SASS_MEMORY_CAST(Argument, rest)->value()->perform(this); + Expression_Obj splat = Cast(rest)->value()->perform(this); Sass_Separator separator = SASS_COMMA; - List_Ptr ls = SASS_MEMORY_CAST(List, splat); - Map_Ptr ms = SASS_MEMORY_CAST(Map, splat); + List_Ptr ls = Cast(splat); + Map_Ptr ms = Cast(splat); List_Obj arglist = SASS_MEMORY_NEW(List, splat->pstate(), @@ -1375,13 +1429,13 @@ namespace Sass { arglist->append(splat); } if (arglist->length()) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), &arglist, "", true)); + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); } } if (a->has_keyword_argument()) { Expression_Obj rv = a->get_keyword_argument()->perform(this); - Argument_Ptr rvarg = SASS_MEMORY_CAST(Argument, rv); + Argument_Ptr rvarg = Cast(rv); Expression_Obj kwarg = rvarg->value()->perform(this); aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); @@ -1409,10 +1463,10 @@ namespace Sass { bool Eval::lt(Expression_Obj lhs, Expression_Obj rhs, std::string op) { - Number_Obj l = SASS_MEMORY_CAST(Number, lhs); - Number_Obj r = SASS_MEMORY_CAST(Number, rhs); + Number_Obj l = Cast(lhs); + Number_Obj r = Cast(rhs); // use compare operator from ast node - if (!l || !r) throw Exception::UndefinedOperation(&lhs, &rhs, op); + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); // use compare operator from ast node return *l < *r; } @@ -1540,8 +1594,8 @@ namespace Sass { Expression::Concrete_Type rtype = rhs.concrete_type(); enum Sass_OP op = operand.operand; - String_Quoted_Ptr lqstr = dynamic_cast(&lhs); - String_Quoted_Ptr rqstr = dynamic_cast(&rhs); + String_Quoted_Ptr lqstr = Cast(&lhs); + String_Quoted_Ptr rqstr = Cast(&rhs); std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); @@ -1612,6 +1666,7 @@ namespace Sass { for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { l->append(cval_to_astnode(sass_list_get_value(v, i), backtrace, pstate)); } + l->is_bracketed(sass_list_get_is_bracketed(v)); e = l; } break; case SASS_MAP: { @@ -1644,7 +1699,7 @@ namespace Sass { sl->media_block(s->media_block()); sl->is_optional(s->is_optional()); for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()(&(*s)[i])); + rv.push_back(operator()((*s)[i])); } // we should actually permutate parent first @@ -1690,19 +1745,20 @@ namespace Sass { // the parser will look for a brace to end the selector Expression_Obj sel = s->contents()->perform(this); std::string result_str(sel->to_string(ctx.c_options)); - result_str = unquote(Util::rtrim(result_str)) + "\n{"; + result_str = unquote(Util::rtrim(result_str)); Parser p = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()); p.last_media_block = s->media_block(); - Selector_List_Obj sl = p.parse_selector_list(exp.block_stack.back()->is_root()); - if (s->has_parent_ref()) sl->remove_parent_selectors(); - return operator()(&sl); + // a selector schema may or may not connect to parent? + bool chroot = s->connect_parent() == false; + Selector_List_Obj sl = p.parse_selector_list(chroot); + return operator()(sl); } Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) { if (Selector_List_Obj pr = selector()) { exp.selector_stack.pop_back(); - Selector_List_Obj rv = operator()(&pr); + Selector_List_Obj rv = operator()(pr); exp.selector_stack.push_back(rv); return rv.detach(); } else { diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp index d38a01296..1057d980f 100755 --- a/src/libsass/src/expand.cpp +++ b/src/libsass/src/expand.cpp @@ -9,6 +9,7 @@ #include "backtrace.hpp" #include "context.hpp" #include "parser.hpp" +#include "sass_functions.hpp" namespace Sass { @@ -40,11 +41,6 @@ namespace Sass { backtrace_stack.push_back(bt); } - Context& Expand::context() - { - return ctx; - } - Env* Expand::environment() { if (env_stack.size() > 0) @@ -78,7 +74,7 @@ namespace Sass { b->length(), b->is_root()); // setup block and env stack - this->block_stack.push_back(&bb); + this->block_stack.push_back(bb); this->env_stack.push_back(&env); // operate on block // this may throw up! @@ -95,12 +91,14 @@ namespace Sass { LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); if (in_keyframes) { - Block_Ptr bb = operator()(&r->block()); + Block_Ptr bb = operator()(r->block()); Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); if (r->selector()) { - selector_stack.push_back(0); - k->name(SASS_MEMORY_CAST_PTR(Selector_List, r->selector()->perform(&eval))); - selector_stack.pop_back(); + if (Selector_List_Ptr s = r->selector()) { + selector_stack.push_back(0); + k->name(s->eval(eval)); + selector_stack.pop_back(); + } } return k.detach(); } @@ -108,50 +106,52 @@ namespace Sass { // reset when leaving scope LOCAL_FLAG(at_root_without_rule, false); - // do some special checks for the base level rules + // `&` is allowed in `@at-root`! + bool has_parent_selector = false; + for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { + Selector_List_Obj ll = selector_stack.at(i); + has_parent_selector = ll != 0 && ll->length() > 0; + } + + Selector_List_Obj sel = r->selector(); + if (sel) sel = sel->eval(eval); + + // check for parent selectors in base level rules if (r->is_root()) { - if (Selector_List_Ptr selector_list = SASS_MEMORY_CAST(Selector_List, r->selector())) { + if (Selector_List_Ptr selector_list = Cast(r->selector())) { for (Complex_Selector_Obj complex_selector : selector_list->elements()) { - Complex_Selector_Ptr tail = &complex_selector; + Complex_Selector_Ptr tail = complex_selector; while (tail) { if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - if (SASS_MEMORY_CAST(Parent_Selector, header) == NULL) continue; // skip all others + Parent_Selector_Ptr ptr = Cast(header); + if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; std::string sel_str(complex_selector->to_string(ctx.c_options)); error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace()); } - tail = &tail->tail(); + tail = tail->tail(); } } } } - - Expression_Obj ex = 0; - if (r->selector()) ex = r->selector()->perform(&eval); - Selector_List_Obj sel = SASS_MEMORY_CAST(Selector_List, ex); - if (sel == 0) throw std::runtime_error("Expanded null selector"); - - if (sel->length() == 0 || sel->has_parent_ref()) { - bool has_parent_selector = false; - for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { - Selector_List_Obj ll = selector_stack.at(i); - has_parent_selector = ll != 0 && ll->length() > 0; - } - if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), backtrace()); + else { + if (sel->length() == 0 || sel->has_parent_ref()) { + if (sel->has_real_parent_ref() && !has_parent_selector) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), backtrace()); + } } } - selector_stack.push_back(&sel); + selector_stack.push_back(sel); Env env(environment()); if (block_stack.back()->is_root()) { env_stack.push_back(&env); } sel->set_media_block(media_block_stack.back()); Block_Obj blk = 0; - if (r->block()) blk = operator()(&r->block()); + if (r->block()) blk = operator()(r->block()); Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, r->pstate(), - &sel, + sel, blk); selector_stack.pop_back(); if (block_stack.back()->is_root()) { @@ -169,8 +169,8 @@ namespace Sass { Expression_Obj condition = f->condition()->perform(&eval); Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, f->pstate(), - SASS_MEMORY_CAST(Supports_Condition, condition), - operator()(&f->block())); + Cast(condition), + operator()(f->block())); return ff.detach(); } @@ -182,14 +182,13 @@ namespace Sass { char* str = sass_copy_c_string(str_mq.c_str()); ctx.strings.push_back(str); Parser p(Parser::from_c_str(str, ctx, mq->pstate())); - mq = &p.parse_media_queries(); // re-assign now - List_Obj ls = SASS_MEMORY_CAST_PTR(List, mq->perform(&eval)); - Block_Obj blk = operator()(&m->block()); + mq = p.parse_media_queries(); // re-assign now + List_Obj ls = Cast(mq->perform(&eval)); + Block_Obj blk = operator()(m->block()); Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, m->pstate(), ls, - blk, - 0); + blk); media_block_stack.pop_back(); mm->tabs(m->tabs()); return mm; @@ -198,7 +197,7 @@ namespace Sass { Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) { Block_Obj ab = a->block(); - Expression_Obj ae = &a->expression(); + Expression_Obj ae = a->expression(); if (ae) ae = ae->perform(&eval); else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); @@ -208,23 +207,23 @@ namespace Sass { ; - Block_Obj bb = ab ? operator()(&ab) : NULL; + Block_Obj bb = ab ? operator()(ab) : NULL; At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, a->pstate(), bb, - SASS_MEMORY_CAST(At_Root_Query, ae)); + Cast(ae)); return aa.detach(); } Statement_Ptr Expand::operator()(Directive_Ptr a) { LOCAL_FLAG(in_keyframes, a->is_keyframes()); - Block_Ptr ab = &a->block(); - Selector_Ptr as = &a->selector(); - Expression_Ptr av = &a->value(); + Block_Ptr ab = a->block(); + Selector_List_Ptr as = a->selector(); + Expression_Ptr av = a->value(); selector_stack.push_back(0); if (av) av = av->perform(&eval); - if (as) as = SASS_MEMORY_CAST_PTR(Selector, as->perform(&eval)); + if (as) as = eval(as); selector_stack.pop_back(); Block_Ptr bb = ab ? operator()(ab) : NULL; Directive_Ptr aa = SASS_MEMORY_NEW(Directive, @@ -241,14 +240,14 @@ namespace Sass { Block_Obj ab = d->block(); String_Obj old_p = d->property(); Expression_Obj prop = old_p->perform(&eval); - String_Obj new_p = SASS_MEMORY_CAST(String, prop); + String_Obj new_p = Cast(prop); // we might get a color back if (!new_p) { std::string str(prop->to_string(ctx.c_options)); new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); } Expression_Obj value = d->value()->perform(&eval); - Block_Obj bb = ab ? operator()(&ab) : NULL; + Block_Obj bb = ab ? operator()(ab) : NULL; if (!bb) { if (!value || (value->is_invisible() && !d->is_important())) return 0; } @@ -269,7 +268,7 @@ namespace Sass { if (a->is_global()) { if (a->is_default()) { if (env->has_global(var)) { - Expression_Obj e = SASS_MEMORY_CAST(Expression, env->get_global(var)); + Expression_Obj e = Cast(env->get_global(var)); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(&eval)); } @@ -288,7 +287,7 @@ namespace Sass { while (cur && cur->is_lexical()) { if (cur->has_local(var)) { if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Obj e = SASS_MEMORY_CAST(Expression, node); + Expression_Obj e = Cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { cur->set_local(var, a->value()->perform(&eval)); } @@ -304,7 +303,7 @@ namespace Sass { } else if (env->has_global(var)) { if (AST_Node_Obj node = env->get_global(var)) { - Expression_Obj e = SASS_MEMORY_CAST(Expression, node); + Expression_Obj e = Cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(&eval)); } @@ -328,7 +327,7 @@ namespace Sass { Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); if (imp->import_queries() && imp->import_queries()->size()) { Expression_Obj ex = imp->import_queries()->perform(&eval); - result->import_queries(SASS_MEMORY_CAST(List, ex)); + result->import_queries(Cast(ex)); } for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { result->urls().push_back(imp->urls()[i]->perform(&eval)); @@ -342,7 +341,7 @@ namespace Sass { { // get parent node from call stack AST_Node_Obj parent = call_stack.back(); - if (SASS_MEMORY_CAST(Block, parent) == NULL) { + if (Cast(parent) == NULL) { error("Import directives may not be used within control directives or mixins.", i->pstate()); } // we don't seem to need that actually afterall @@ -353,7 +352,7 @@ namespace Sass { ); ctx.import_stack.push_back(import); const std::string& abs_path(i->resource().abs_path); - append_block(&ctx.sheets.at(abs_path).root); + append_block(ctx.sheets.at(abs_path).root); sass_delete_import(ctx.import_stack.back()); ctx.import_stack.pop_back(); return 0; @@ -383,7 +382,7 @@ namespace Sass { Statement_Ptr Expand::operator()(Comment_Ptr c) { eval.is_in_comment = true; - Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), SASS_MEMORY_CAST_PTR(String, c->text()->perform(&eval)), c->is_important()); + Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); eval.is_in_comment = false; // TODO: eval the text, once we're parsing/storing it as a String_Schema return rv; @@ -396,10 +395,10 @@ namespace Sass { call_stack.push_back(i); Expression_Obj rv = i->predicate()->perform(&eval); if (*rv) { - append_block(&i->block()); + append_block(i->block()); } else { - Block_Ptr alt = &i->alternative(); + Block_Ptr alt = i->alternative(); if (alt) append_block(alt); } call_stack.pop_back(); @@ -420,8 +419,8 @@ namespace Sass { if (high->concrete_type() != Expression::NUMBER) { throw Exception::TypeMismatch(*high, "integer"); } - Number_Obj sass_start = SASS_MEMORY_CAST(Number, low); - Number_Obj sass_end = SASS_MEMORY_CAST(Number, high); + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); // check if units are valid for sequence if (sass_start->unit() != sass_end->unit()) { std::stringstream msg; msg << "Incompatible units: '" @@ -436,8 +435,8 @@ namespace Sass { env_stack.push_back(&env); call_stack.push_back(f); Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit()); - env.set_local(variable, &it); - Block_Ptr body = &f->block(); + env.set_local(variable, it); + Block_Ptr body = f->block(); if (start < end) { if (f->is_inclusive()) ++end; for (double i = start; @@ -445,7 +444,7 @@ namespace Sass { ++i) { it = SASS_MEMORY_COPY(it); it->value(i); - env.set_local(variable, &it); + env.set_local(variable, it); append_block(body); } } else { @@ -455,7 +454,7 @@ namespace Sass { --i) { it = SASS_MEMORY_COPY(it); it->value(i); - env.set_local(variable, &it); + env.set_local(variable, it); append_block(body); } } @@ -473,25 +472,25 @@ namespace Sass { List_Obj list = 0; Map_Obj map; if (expr->concrete_type() == Expression::MAP) { - map = SASS_MEMORY_CAST(Map, expr); + map = Cast(expr); } - else if (Selector_List_Ptr ls = SASS_MEMORY_CAST(Selector_List, expr)) { + else if (Selector_List_Ptr ls = Cast(expr)) { Listize listize; Expression_Obj rv = ls->perform(&listize); - list = SASS_MEMORY_CAST(List, rv); + list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); list->append(expr); } else { - list = SASS_MEMORY_CAST(List, expr); + list = Cast(expr); } // remember variables and then reset them Env env(environment(), true); env_stack.push_back(&env); call_stack.push_back(e); - Block_Ptr body = &e->block(); + Block_Ptr body = e->block(); if (map) { for (auto key : map->keys()) { @@ -502,43 +501,43 @@ namespace Sass { List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); variable->append(k); variable->append(v); - env.set_local(variables[0], &variable); + env.set_local(variables[0], variable); } else { - env.set_local(variables[0], &k); - env.set_local(variables[1], &v); + env.set_local(variables[0], k); + env.set_local(variables[1], v); } append_block(body); } } else { // bool arglist = list->is_arglist(); - if (list->length() == 1 && SASS_MEMORY_CAST(Selector_List, list)) { - list = SASS_MEMORY_CAST(List, list); + if (list->length() == 1 && Cast(list)) { + list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { Expression_Obj e = list->at(i); // unwrap value if the expression is an argument - if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, e)) e = arg->value(); + if (Argument_Obj arg = Cast(e)) e = arg->value(); // check if we got passed a list of args (investigate) - if (List_Obj scalars = SASS_MEMORY_CAST(List, e)) { + if (List_Obj scalars = Cast(e)) { if (variables.size() == 1) { List_Obj var = scalars; // if (arglist) var = (*scalars)[0]; - env.set_local(variables[0], &var); + env.set_local(variables[0], var); } else { for (size_t j = 0, K = variables.size(); j < K; ++j) { Expression_Obj res = j >= scalars->length() ? SASS_MEMORY_NEW(Null, expr->pstate()) : (*scalars)[j]->perform(&eval); - env.set_local(variables[j], &res); + env.set_local(variables[j], res); } } } else { if (variables.size() > 0) { - env.set_local(variables.at(0), &e); + env.set_local(variables.at(0), e); for (size_t j = 1, K = variables.size(); j < K; ++j) { Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], &res); + env.set_local(variables[j], res); } } } @@ -553,12 +552,12 @@ namespace Sass { Statement_Ptr Expand::operator()(While_Ptr w) { Expression_Obj pred = w->predicate(); - Block_Ptr body = &w->block(); + Block_Ptr body = w->block(); Env env(environment(), true); env_stack.push_back(&env); call_stack.push_back(w); Expression_Obj cond = pred->perform(&eval); - while (*&cond) { + while (!cond->is_false()) { append_block(body); cond = pred->perform(&eval); } @@ -576,12 +575,12 @@ namespace Sass { void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { - if (Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, s)) { + if (Selector_List_Obj sl = Cast(s)) { for (Complex_Selector_Obj complex_selector : sl->elements()) { Complex_Selector_Obj tail = complex_selector; while (tail) { if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - if (SASS_MEMORY_CAST(Parent_Selector, header) == NULL) continue; // skip all others + if (Cast(header) == NULL) continue; // skip all others std::string sel_str(complex_selector->to_string(ctx.c_options)); error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace()); } @@ -591,7 +590,7 @@ namespace Sass { } - Selector_List_Obj contextualized = SASS_MEMORY_CAST_PTR(Selector_List, s->perform(&eval)); + Selector_List_Obj contextualized = Cast(s->perform(&eval)); if (contextualized == false) return; for (auto complex_sel : contextualized->elements()) { Complex_Selector_Obj c = complex_sel; @@ -604,7 +603,7 @@ namespace Sass { for (size_t i = 0, L = extender->length(); i < L; ++i) { Complex_Selector_Obj sel = (*extender)[i]; if (!(sel->head() && sel->head()->length() > 0 && - SASS_MEMORY_CAST(Parent_Selector, (*sel->head())[0]))) + Cast((*sel->head())[0]))) { Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); hh->media_block((*extender)[i]->media_block()); @@ -613,7 +612,7 @@ namespace Sass { if (sel->has_line_feed()) ssel->has_line_feed(true); Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); ps->media_block((*extender)[i]->media_block()); - hh->append(&ps); + hh->append(ps); ssel->tail(sel); ssel->head(hh); sel = ssel; @@ -627,31 +626,30 @@ namespace Sass { Statement* Expand::operator()(Extension_Ptr e) { - if (Selector_List_Obj extender = SASS_MEMORY_CAST(Selector_List, selector())) { - Selector_Obj s = e->selector(); - Selector_List_Obj sl = NULL; - // check if we already have a valid selector list - if ((sl = SASS_MEMORY_CAST(Selector_List, s))) {} - // convert selector schema to a selector list - else if (Selector_Schema_Obj schema = SASS_MEMORY_CAST(Selector_Schema, s)) { + if (Selector_List_Ptr extender = selector()) { + Selector_List_Ptr sl = e->selector(); + // abort on invalid selector + if (sl == NULL) return NULL; + if (Selector_Schema_Ptr schema = sl->schema()) { if (schema->has_real_parent_ref()) { - sl = eval(&schema); + // put root block on stack again (ignore parents) + // selector schema must not connect in eval! + block_stack.push_back(block_stack.at(1)); + sl = eval(sl->schema()); + block_stack.pop_back(); } else { selector_stack.push_back(0); - sl = eval(&schema); - sl->remove_parent_selectors(); + sl = eval(sl->schema()); selector_stack.pop_back(); } } - // abort on invalid selector - if (sl.isNull()) return NULL; for (Complex_Selector_Obj cs : sl->elements()) { if (!cs.isNull() && !cs->head().isNull()) { cs->head()->media_block(media_block_stack.back()); } } selector_stack.push_back(0); - expand_selector_list(&sl, extender); + expand_selector_list(sl, extender); selector_stack.pop_back(); } return 0; @@ -662,7 +660,7 @@ namespace Sass { Env* env = environment(); Definition_Obj dd = SASS_MEMORY_COPY(d); env->local_frame()[d->name() + - (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = ⅆ + (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; if (d->type() == Definition::FUNCTION && ( Prelexer::calc_fn_call(d->name().c_str()) || @@ -696,7 +694,7 @@ namespace Sass { if (!env->has(full_name)) { error("no mixin named " + c->name(), c->pstate(), backtrace()); } - Definition_Obj def = SASS_MEMORY_CAST(Definition, (*env)[full_name]); + Definition_Obj def = Cast((*env)[full_name]); Block_Obj body = def->block(); Parameters_Obj params = def->parameters(); @@ -704,9 +702,18 @@ namespace Sass { error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), backtrace()); } Expression_Obj rv = c->arguments()->perform(&eval); - Arguments_Obj args = SASS_MEMORY_CAST(Arguments, rv); + Arguments_Obj args = Cast(rv); Backtrace new_bt(backtrace(), c->pstate(), ", in mixin `" + c->name() + "`"); backtrace_stack.push_back(&new_bt); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_MIXIN, + { env } + }); + Env new_env(def->environment()); env_stack.push_back(&new_env); if (c->block()) { @@ -718,7 +725,7 @@ namespace Sass { c->block(), Definition::MIXIN); thunk->environment(env); - new_env.local_frame()["@content[m]"] = &thunk; + new_env.local_frame()["@content[m]"] = thunk; } bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); @@ -727,7 +734,7 @@ namespace Sass { Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); - block_stack.push_back(&trace_block); + block_stack.push_back(trace_block); for (auto bb : body->elements()) { Statement_Obj ith = bb->perform(this); if (ith) trace->block()->append(ith); @@ -736,6 +743,7 @@ namespace Sass { env_stack.pop_back(); backtrace_stack.pop_back(); + ctx.callee_stack.pop_back(); recursions --; return trace.detach(); @@ -756,7 +764,7 @@ namespace Sass { "@content", SASS_MEMORY_NEW(Arguments, c->pstate())); - Trace_Obj trace = SASS_MEMORY_CAST_PTR(Trace, call->perform(this)); + Trace_Obj trace = Cast(call->perform(this)); if (block_stack.back()->is_root()) { selector_stack.pop_back(); @@ -771,7 +779,7 @@ namespace Sass { std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace()); - return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), &msg); + return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); } // process and add to last block on stack @@ -779,7 +787,7 @@ namespace Sass { { if (b->is_root()) call_stack.push_back(b); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr stm = &b->at(i); + Statement_Ptr stm = b->at(i); Statement_Obj ith = stm->perform(this); if (ith) block_stack.back()->append(ith); } diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp index 1f93d2acb..2d0d5ecea 100755 --- a/src/libsass/src/expand.hpp +++ b/src/libsass/src/expand.hpp @@ -13,14 +13,12 @@ namespace Sass { class Listize; class Context; class Eval; - typedef Environment Env; struct Backtrace; class Expand : public Operation_CRTP { public: Env* environment(); - Context& context(); Selector_List_Obj selector(); Backtrace* backtrace(); diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp index 8582de8a9..618d979db 100755 --- a/src/libsass/src/extend.cpp +++ b/src/libsass/src/extend.cpp @@ -61,8 +61,6 @@ namespace Sass { - typedef std::pair ExtensionPair; - typedef std::vector SubsetMapEntries; #ifdef DEBUG @@ -111,11 +109,6 @@ namespace Sass { } } - // Print a string representation of a Compound_Selector - typedef std::pair SelsNewSeqPair; - typedef std::vector SelsNewSeqPairCollection; - - // Print a string representation of a Compound_Selector static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) { @@ -186,14 +179,14 @@ namespace Sass { } } - static void printSelsNewSeqPairCollection(SelsNewSeqPairCollection& collection, const char* message=NULL, bool newline=true) { + static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) { if (message) { std::cerr << message; } bool first = true; std::cerr << "["; - for(SelsNewSeqPair& pair : collection) { + for(SubSetMapLookup& pair : collection) { if (first) { first = false; } else { @@ -212,8 +205,8 @@ namespace Sass { } } - // Print a string representation of a SourcesSet - static void printSourcesSet(SourcesSet& sources, Context& ctx, const char* message=NULL, bool newline=true) { + // Print a string representation of a ComplexSelectorSet + static void printSourcesSet(ComplexSelectorSet& sources, Context& ctx, const char* message=NULL, bool newline=true) { if (message) { std::cerr << message; @@ -223,7 +216,7 @@ namespace Sass { // the differences we see when debug printing. typedef std::deque SourceStrings; SourceStrings sourceStrings; - for (SourcesSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { + for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { Complex_Selector_Ptr pSource = *iterator; std::stringstream sstream; sstream << complexSelectorToNode(pSource, ctx); @@ -233,7 +226,7 @@ namespace Sass { // Sort to get consistent output std::sort(sourceStrings.begin(), sourceStrings.end()); - std::cerr << "SourcesSet["; + std::cerr << "ComplexSelectorSet["; for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) { std::string source = *iterator; if (iterator != sourceStrings.begin()) { @@ -249,10 +242,10 @@ namespace Sass { } - std::ostream& operator<<(std::ostream& os, SubsetMapEntries& entries) { + std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) { os << "SUBSET_MAP_ENTRIES["; - for (SubsetMapEntries::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { + for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge) Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend @@ -292,11 +285,11 @@ namespace Sass { Position noPosition(-1, -1, -1); Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(&fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/); + fakeHead->elements().push_back(fakeParent); + Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); - pOne->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); - pTwo->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); + pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); bool isSuperselector = pOne->is_superselector_of(pTwo); @@ -318,7 +311,7 @@ namespace Sass { for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { Complex_Selector_Obj pChild = *iter; - result.collection()->push_back(complexSelectorToNode(&pChild, ctx)); + result.collection()->push_back(complexSelectorToNode(pChild, ctx)); } return result; @@ -341,7 +334,7 @@ namespace Sass { end */ - if (selectors_equal(*pOne, *pTwo, true /*simpleSelectorOrderDependent*/)) { + if (*pOne == *pTwo) { pOut = pOne; return true; } @@ -350,12 +343,12 @@ namespace Sass { return false; } - if (parentSuperselector(&pOne, &pTwo, mCtx)) { + if (parentSuperselector(pOne, pTwo, mCtx)) { pOut = pTwo; return true; } - if (parentSuperselector(&pTwo, &pOne, mCtx)) { + if (parentSuperselector(pTwo, pOne, mCtx)) { pOut = pOne; return true; } @@ -420,7 +413,7 @@ namespace Sass { for (size_t j = 1; j < y.size(); j++) { Complex_Selector_Obj pCompareOut; - if (comparator(&x[i], &y[j], pCompareOut)) { + if (comparator(x[i], y[j], pCompareOut)) { c[i][j] = c[i - 1][j - 1] + 1; } else { c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); @@ -569,12 +562,12 @@ namespace Sass { // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely // a guess though. unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0; - SourcesSet sources = pSeq1->sources(); + ComplexSelectorSet sources = pSeq1->sources(); DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIM SOURCES: ")) - for (SourcesSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { + for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity()); } @@ -655,13 +648,13 @@ namespace Sass { Position noPosition(-1, -1, -1); Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(&fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/); + fakeHead->elements().push_back(fakeParent); + Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one, ctx); - pOneWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two, ctx); - pTwoWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF); + pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); } @@ -848,7 +841,7 @@ namespace Sass { DefaultLcsComparator lcsDefaultComparator; Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx); - if (!(nodesEqual(opsLcs, ops1, true) || nodesEqual(opsLcs, ops2, true))) { + if (!(opsLcs == ops1 || opsLcs == ops2)) { return Node::createNil(); } @@ -945,7 +938,7 @@ namespace Sass { // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. - if (!(nodesEqual(opsLcs, ops1, true) || nodesEqual(opsLcs, ops2, true))) { + if (!(opsLcs == ops1 || opsLcs == ops2)) { return Node::createNil(); } @@ -988,7 +981,7 @@ namespace Sass { Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(&sel2.selector()->head(), ctx); + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx); pMergedWrapper->head(pMerged); DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) @@ -1011,7 +1004,7 @@ namespace Sass { if (pMerged) { Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(&pMergedWrapper, ctx)); + mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx)); mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); newRes.collection()->push_back(mergedPerm); } @@ -1047,7 +1040,7 @@ namespace Sass { Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(&tildeSel.selector()->head(), ctx); + Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head(), ctx); pMergedWrapper->head(pMerged); DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) @@ -1063,7 +1056,7 @@ namespace Sass { if (pMerged) { Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(&pMergedWrapper, ctx)); + mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx)); mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); newRes.collection()->push_back(mergedPerm); } @@ -1096,7 +1089,7 @@ namespace Sass { Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(&sel2.selector()->head(), ctx); + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx); pMergedWrapper->head(pMerged); DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) @@ -1106,7 +1099,7 @@ namespace Sass { } res.collection()->push_front(op1); - res.collection()->push_front(Node::createSelector(&pMergedWrapper, ctx)); + res.collection()->push_front(Node::createSelector(pMergedWrapper, ctx)); DEBUG_PRINTLN(ALL, "RESULT: " << res) @@ -1530,9 +1523,9 @@ namespace Sass { template class GroupByToAFunctor { public: - KeyType operator()(ExtensionPair& extPair) const { + KeyType operator()(SubSetMapPair& extPair) const { Complex_Selector_Obj pSelector = extPair.first; - return &pSelector; + return pSelector; } }; static Node extendCompoundSelector( @@ -1547,48 +1540,42 @@ namespace Sass { Node extendedSelectors = Node::createCollection(); // extendedSelectors.got_line_feed = true; - SubsetMapEntries entries = subset_map.get_v(pSelector); - - typedef std::vector > > GroupedByToAResult; + SubSetMapPairs entries = subset_map.get_v(pSelector); GroupByToAFunctor extPairKeyFunctor; - GroupedByToAResult arr; + SubSetMapResults arr; group_by_to_a(entries, extPairKeyFunctor, arr); - typedef std::pair SelsNewSeqPair; - typedef std::vector SelsNewSeqPairCollection; - - - SelsNewSeqPairCollection holder; + SubSetMapLookups holder; - for (GroupedByToAResult::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) { - std::pair >& groupedPair = *groupedIter; + for (SubSetMapResults::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) { + SubSetMapResult& groupedPair = *groupedIter; Complex_Selector_Obj seq = groupedPair.first; - std::vector& group = groupedPair.second; + SubSetMapPairs& group = groupedPair.second; - DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(&seq, "SEQ: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) // changing this makes aua Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - for (std::vector::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) { - ExtensionPair& pair = *groupIter; + for (SubSetMapPairs::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) { + SubSetMapPair& pair = *groupIter; Compound_Selector_Obj pCompound = pair.second; for (size_t index = 0; index < pCompound->length(); index++) { Simple_Selector_Obj pSimpleSelector = (*pCompound)[index]; - pSels->append(&pSimpleSelector); + pSels->append(pSimpleSelector); pCompound->extended(true); } } - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(&pSels, "SELS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) - Complex_Selector_Ptr pExtComplexSelector = &seq; // The selector up to where the @extend is (ie, the thing to merge) + Complex_Selector_Ptr pExtComplexSelector = seq; // The selector up to where the @extend is (ie, the thing to merge) // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) - Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(&pSels, ctx); + Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels, ctx); DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) @@ -1598,7 +1585,7 @@ namespace Sass { if (!pInnermostCompoundSelector) { pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); } - Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(&pSelectorWithoutExtendSelectors, ctx); + Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors, ctx); DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) @@ -1621,10 +1608,10 @@ namespace Sass { Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); - pNewSelector->set_innermost(&pNewInnerMost, combinator); + pNewSelector->set_innermost(pNewInnerMost, combinator); #ifdef DEBUG - SourcesSet debugSet; + ComplexSelectorSet debugSet; debugSet = pNewSelector->sources(); if (debugSet.size() > 0) { throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this."); @@ -1640,9 +1627,10 @@ namespace Sass { // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector, ctx)) - DEBUG_EXEC(EXTEND_COMPOUND, SourcesSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: ")) + DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: ")) - SourcesSet newSourcesSet = pSelector->sources(); + // I actually want to create a copy here (performance!) + ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES THIS EXTEND: ")) newSourcesSet.insert(pExtComplexSelector); @@ -1651,7 +1639,7 @@ namespace Sass { // RUBY: new_seq.add_sources!(sources + [seq]) pNewSelector->addSources(newSourcesSet, ctx); - DEBUG_EXEC(EXTEND_COMPOUND, SourcesSet newSet = pNewSelector->sources(); printSourcesSet(newSet, ctx, "SOURCES ON NEW SELECTOR AFTER ADD: ")) + DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, ctx, "SOURCES ON NEW SELECTOR AFTER ADD: ")) DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), ctx, "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) @@ -1661,8 +1649,8 @@ namespace Sass { } - for (SelsNewSeqPairCollection::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) { - SelsNewSeqPair& pair = *holderIter; + for (SubSetMapLookups::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) { + SubSetMapLookup& pair = *holderIter; Compound_Selector_Obj pSels = pair.first; Complex_Selector_Obj pNewSelector = pair.second; @@ -1679,7 +1667,7 @@ namespace Sass { DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector, ctx)) - Node recurseExtendedSelectors = extendComplexSelector(&pNewSelector, ctx, subset_map, recurseSeen, isReplace, false); // !:isOriginal + Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, ctx, subset_map, recurseSeen, isReplace, false); // !:isOriginal DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) @@ -1717,8 +1705,8 @@ namespace Sass { Compound_Selector_Obj pHead = pIter->head(); if (pHead) { - SubsetMapEntries entries = subset_map.get_v(pHead); - for (ExtensionPair ext : entries) { + SubSetMapPairs entries = subset_map.get_v(pHead); + for (SubSetMapPair ext : entries) { // check if both selectors have the same media block parent // if (ext.first->media_block() == pComplexSelector->media_block()) continue; if (ext.second->media_block() == 0) continue; @@ -1799,7 +1787,7 @@ namespace Sass { Compound_Selector_Obj pCompoundSelector = sseqOrOp.selector()->head(); // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) - Node extended = extendCompoundSelector(&pCompoundSelector, ctx, subset_map, seen, isReplace); + Node extended = extendCompoundSelector(pCompoundSelector, ctx, subset_map, seen, isReplace); if (sseqOrOp.got_line_feed) extended.got_line_feed = true; DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) @@ -1810,7 +1798,7 @@ namespace Sass { // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? if (isOriginal && !pComplexSelector->has_placeholder()) { - SourcesSet srcset; + ComplexSelectorSet srcset; srcset.insert(pComplexSelector); pJustCurrentCompoundSelector->addSources(srcset, ctx); DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) @@ -1829,7 +1817,7 @@ namespace Sass { if (!isSuperselector) { if (sseqOrOp.got_line_feed) pJustCurrentCompoundSelector->has_line_feed(sseqOrOp.got_line_feed); - extended.collection()->push_front(complexSelectorToNode(&pJustCurrentCompoundSelector, ctx)); + extended.collection()->push_front(complexSelectorToNode(pJustCurrentCompoundSelector, ctx)); } DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) @@ -1909,16 +1897,16 @@ namespace Sass { // run through the extend code (which does a data model transformation), check if there is anything to extend before doing // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps // when debugging). - if (!complexSelectorHasExtension(&pSelector, ctx, subset_map, seen)) { - pNewSelectors->append(&pSelector); + if (!complexSelectorHasExtension(pSelector, ctx, subset_map, seen)) { + pNewSelectors->append(pSelector); continue; } extendedSomething = true; - Node extendedSelectors = extendComplexSelector(&pSelector, ctx, subset_map, seen, isReplace, true); + Node extendedSelectors = extendComplexSelector(pSelector, ctx, subset_map, seen, isReplace, true); if (!pSelector->has_placeholder()) { - if (!extendedSelectors.contains(complexSelectorToNode(&pSelector, ctx), true /*simpleSelectorOrderDependent*/)) { + if (!extendedSelectors.contains(complexSelectorToNode(pSelector, ctx), true /*simpleSelectorOrderDependent*/)) { pNewSelectors->append(pSelector); continue; } @@ -1933,10 +1921,10 @@ namespace Sass { } } - Remove_Placeholders remove_placeholders(ctx); + Remove_Placeholders remove_placeholders; // it seems that we have to remove the place holders early here // normally we do this as the very last step (compare to ruby sass) - pNewSelectors = remove_placeholders.remove_placeholders(&pNewSelectors); + pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors); // unwrap all wrapped selectors with inner lists for (Complex_Selector_Obj cur : pNewSelectors->elements()) { @@ -1949,9 +1937,9 @@ namespace Sass { // create a copy since we add multiple items if stuff get unwrapped Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); for (Simple_Selector_Obj hs : *cur->head()) { - if (Wrapped_Selector_Obj ws = SASS_MEMORY_CAST(Wrapped_Selector, hs)) { + if (Wrapped_Selector_Obj ws = Cast(hs)) { ws->selector(SASS_MEMORY_CLONE(ws->selector())); - if (Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) { + if (Selector_List_Obj sl = Cast(ws->selector())) { // special case for ruby ass if (sl->empty()) { // this seems inconsistent but it is how ruby sass seems to remove parentheses @@ -1964,34 +1952,34 @@ namespace Sass { for (size_t i = 0; i < ext_sl->length(); i += 1) { if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { // create clones for wrapped selector and the inner list - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(&ws); + Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); // remove parent selectors from inner selector if (ext_cs->first() && ext_cs->first()->head()->length() > 0) { - Wrapped_Selector_Ptr ext_ws = SASS_MEMORY_CAST(Wrapped_Selector, ext_cs->first()->head()->first()); + Wrapped_Selector_Ptr ext_ws = Cast(ext_cs->first()->head()->first()); if (ext_ws/* && ext_cs->length() == 1*/) { - Selector_List_Obj ws_cs = SASS_MEMORY_CAST(Selector_List, ext_ws->selector()); + Selector_List_Obj ws_cs = Cast(ext_ws->selector()); Compound_Selector_Obj ws_ss = ws_cs->first()->head(); if (!( - SASS_MEMORY_CAST(Pseudo_Selector, ws_ss->first()) || - SASS_MEMORY_CAST(Element_Selector, ws_ss->first()) || - SASS_MEMORY_CAST(Placeholder_Selector, ws_ss->first()) + Cast(ws_ss->first()) || + Cast(ws_ss->first()) || + Cast(ws_ss->first()) )) continue; } cpy_ws_sl->append(ext_cs->first()); } // assign list to clone - cpy_ws->selector(&cpy_ws_sl); + cpy_ws->selector(cpy_ws_sl); // append the clone - cpy_head->append(&cpy_ws); + cpy_head->append(cpy_ws); } } } } else { - cpy_head->append(&hs); + cpy_head->append(hs); } } else { - cpy_head->append(&hs); + cpy_head->append(hs); } } // replace header @@ -2024,8 +2012,9 @@ namespace Sass { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm)) { - // Do nothing. This doesn't count as a statement that causes extension since we'll iterate over this rule set in a future visit and try to extend it. + if (Cast(stm)) { + // Do nothing. This doesn't count as a statement that causes extension since we'll + // iterate over this rule set in a future visit and try to extend it. } else { return true; @@ -2041,7 +2030,7 @@ namespace Sass { template static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, Subset_Map& subset_map) { - DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << static_cast(pObject->selector())->to_string(ctx.c_options)) + DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string(ctx.c_options)) // Ruby sass seems to filter nodes that don't have any content well before we get here. I'm not sure the repercussions // of doing so, so for now, let's just not extend things that won't be output later. @@ -2051,13 +2040,13 @@ namespace Sass { } bool extendedSomething = false; - Selector_List_Obj pNewSelectorList = Extend::extendSelectorList(SASS_MEMORY_CAST(Selector_List, pObject->selector()), ctx, subset_map, false, extendedSomething); + Selector_List_Obj pNewSelectorList = Extend::extendSelectorList(Cast(pObject->selector()), ctx, subset_map, false, extendedSomething); if (extendedSomething && pNewSelectorList) { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << static_cast(pObject->selector())->to_string(ctx.c_options)) + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << Cast(pObject->selector())->to_string(ctx.c_options)) DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string(ctx.c_options)) pNewSelectorList->remove_parent_selectors(); - pObject->selector(&pNewSelectorList); + pObject->selector(pNewSelectorList); } else { DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING") } @@ -2080,8 +2069,10 @@ namespace Sass { if (b->is_root()) { // debug_subset_map(subset_map); for(auto const &it : subset_map.values()) { - Complex_Selector_Ptr sel = it.first ? &it.first->first() : NULL; - Compound_Selector_Ptr ext = it.second ? &it.second : NULL; + Complex_Selector_Ptr sel = NULL; + Compound_Selector_Ptr ext = NULL; + if (it.first) sel = it.first->first(); + if (it.second) ext = it.second; if (ext && (ext->extended() || ext->is_optional())) continue; std::string str_sel(sel->to_string({ NESTED, 5 })); std::string str_ext(ext->to_string({ NESTED, 5 })); @@ -2114,7 +2105,7 @@ namespace Sass { void Extend::operator()(Directive_Ptr a) { - // Selector_List_Ptr ls = dynamic_cast(a->selector()); + // Selector_List_Ptr ls = Cast(a->selector()); // selector_stack.push_back(ls); if (a->block()) a->block()->perform(this); // exp.selector_stack.pop_back(); diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp index 8b2d19498..cac5fb61d 100755 --- a/src/libsass/src/file.cpp +++ b/src/libsass/src/file.cpp @@ -20,6 +20,7 @@ #include "context.hpp" #include "prelexer.hpp" #include "utf8_string.hpp" +#include "sass_functions.hpp" #include "sass2scss.h" #ifdef _WIN32 @@ -301,13 +302,9 @@ namespace Sass { // (2) underscore + given // (3) underscore + given + extension // (4) given + extension - std::vector resolve_includes(const std::string& root, const std::string& file) + std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) { std::string filename = join_paths(root, file); - // supported extensions - const std::vector exts = { - ".scss", ".sass", ".css" - }; // split the filename std::string base(dir_name(file)); std::string name(base_name(file)); @@ -336,8 +333,41 @@ namespace Sass { return includes; } - // helper function to resolve a filename + std::vector find_files(const std::string& file, const std::vector paths) + { + std::vector includes; + for (std::string path : paths) { + std::string abs_path(join_paths(path, file)); + if (file_exists(abs_path)) includes.push_back(abs_path); + } + return includes; + } + + std::vector find_files(const std::string& file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + // struct Sass_Options* options = sass_compiler_get_options(compiler); + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(dir_name(import->abs_path)); + paths.insert(paths.end(), incs.begin(), incs.end()); + // dispatch to find files in paths + return find_files(file, paths); + } + + // helper function to search one file in all include paths + // this is normally not used internally by libsass (C-API sugar) std::string find_file(const std::string& file, const std::vector paths) + { + if (file.empty()) return file; + auto res = find_files(file, paths); + return res.empty() ? "" : res.front(); + } + + // helper function to resolve a filename + std::string find_include(const std::string& file, const std::vector paths) { // search in every include path for a match for (size_t i = 0, S = paths.size(); i < S; ++i) @@ -349,20 +379,6 @@ namespace Sass { return std::string(""); } - // inc paths can be directly passed from C code - std::string find_file(const std::string& file, const char* paths[]) - { - if (paths == 0) return std::string(""); - std::vector includes(0); - // includes.push_back("."); - const char** it = paths; - while (it && *it) { - includes.push_back(*it); - ++it; - } - return find_file(file, includes); - } - // try to load the given filename // returned memory must be freed // will auto convert .sass files @@ -414,5 +430,25 @@ namespace Sass { } } + // split a path string delimited by semicolons or colons (OS dependent) + std::vector split_path_list(const char* str) + { + std::vector paths; + if (str == NULL) return paths; + // find delimiter via prelexer (return zero at end) + const char* end = Prelexer::find_first(str); + // search until null delimiter + while (end) { + // add path from current position to delimiter + paths.push_back(std::string(str, end - str)); + str = end + 1; // skip delimiter + end = Prelexer::find_first(str); + } + // add path from current position to end + paths.push_back(std::string(str)); + // return back + return paths; + } + } } diff --git a/src/libsass/src/file.hpp b/src/libsass/src/file.hpp index 8e5a0b041..279b9e9f6 100755 --- a/src/libsass/src/file.hpp +++ b/src/libsass/src/file.hpp @@ -4,6 +4,7 @@ #include #include +#include "sass/context.h" #include "ast_fwd_decl.hpp" namespace Sass { @@ -47,9 +48,16 @@ namespace Sass { std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); // helper function to resolve a filename + // searching without variations in all paths + std::string find_file(const std::string& file, struct Sass_Compiler* options); std::string find_file(const std::string& file, const std::vector paths); - // inc paths can be directly passed from C code - std::string find_file(const std::string& file, const char** paths); + + // helper function to resolve a include filename + // this has the original resolve logic for sass include + std::string find_include(const std::string& file, const std::vector paths); + + // split a path string delimited by semicolons or colons (OS dependent) + std::vector split_path_list(const char* paths); // try to load the given filename // returned memory must be freed @@ -113,7 +121,10 @@ namespace Sass { namespace File { - std::vector resolve_includes(const std::string& root, const std::string& file); + static std::vector defaultExtensions = { ".scss", ".sass", ".css" }; + + std::vector resolve_includes(const std::string& root, const std::string& file, + const std::vector& exts = defaultExtensions); } diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp index fcbd6cc59..d1c648207 100755 --- a/src/libsass/src/functions.cpp +++ b/src/libsass/src/functions.cpp @@ -108,7 +108,7 @@ namespace Sass { T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - T* val = dynamic_cast(&env[argname]); + T* val = Cast(env[argname]); if (!val) { std::string msg("argument `"); msg += argname; @@ -124,10 +124,10 @@ namespace Sass { Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Map_Ptr val = SASS_MEMORY_CAST(Map, env[argname]); + Map_Ptr val = Cast(env[argname]); if (val) return val; - List_Ptr lval = SASS_MEMORY_CAST(List, env[argname]); + List_Ptr lval = Cast(env[argname]); if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); // fallback on get_arg for error handling @@ -163,10 +163,10 @@ namespace Sass { msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; error(msg.str(), pstate); } - if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) { + if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } - std::string exp_src = exp->to_string(ctx.c_options) + "{"; + std::string exp_src = exp->to_string(ctx.c_options); return Parser::parse_selector(exp_src.c_str(), ctx); } @@ -178,12 +178,13 @@ namespace Sass { msg << argname << ": null is not a string for `" << function_name(sig) << "'"; error(msg.str(), pstate); } - if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) { + if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } - std::string exp_src = exp->to_string(ctx.c_options) + "{"; + std::string exp_src = exp->to_string(ctx.c_options); Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx); - return (sel_list->length() > 0) ? &sel_list->first()->tail()->head() : 0; + if (sel_list->length() == 0) return NULL; + return sel_list->first()->tail()->head(); } #ifdef __MINGW32__ @@ -285,13 +286,7 @@ namespace Sass { BUILT_IN(blue) { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } - Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; - BUILT_IN(mix) - { - Color_Ptr color1 = ARG("$color-1", Color); - Color_Ptr color2 = ARG("$color-2", Color); - Number_Ptr weight = ARGR("$weight", Number, 0, 100); - + Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, Number* weight) { double p = weight->value()/100; double w = 2*p - 1; double a = color1->a() - color2->a(); @@ -307,6 +302,16 @@ namespace Sass { color1->a()*p + color2->a()*(1-p)); } + Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; + BUILT_IN(mix) + { + Color_Obj color1 = ARG("$color-1", Color); + Color_Obj color2 = ARG("$color-2", Color); + Number_Obj weight = ARGR("$weight", Number, 0, 100); + return colormix(ctx, pstate, color1, color2, weight); + + } + //////////////// // HSL FUNCTIONS //////////////// @@ -504,7 +509,7 @@ namespace Sass { BUILT_IN(saturate) { // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$amount"]); + Number_Ptr amount = Cast(env["$amount"]); if (!amount) { return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); } @@ -564,7 +569,7 @@ namespace Sass { BUILT_IN(grayscale) { // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]); + Number_Ptr amount = Cast(env["$color"]); if (amount) { return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); } @@ -596,22 +601,24 @@ namespace Sass { pstate); } - Signature invert_sig = "invert($color)"; + Signature invert_sig = "invert($color, $weight: 100%)"; BUILT_IN(invert) { // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]); + Number_Ptr amount = Cast(env["$color"]); if (amount) { return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); } + Number_Obj weight = ARGR("$weight", Number, 0, 100); Color_Ptr rgb_color = ARG("$color", Color); - return SASS_MEMORY_NEW(Color, + Color_Obj inv = SASS_MEMORY_NEW(Color, pstate, 255 - rgb_color->r(), 255 - rgb_color->g(), 255 - rgb_color->b(), rgb_color->a()); + return colormix(ctx, pstate, inv, rgb_color, weight); } //////////////////// @@ -621,13 +628,13 @@ namespace Sass { Signature opacity_sig = "opacity($color)"; BUILT_IN(alpha) { - String_Constant_Ptr ie_kwd = SASS_MEMORY_CAST(String_Constant, env["$color"]); + String_Constant_Ptr ie_kwd = Cast(env["$color"]); if (ie_kwd) { return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); } // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]); + Number_Ptr amount = Cast(env["$color"]); if (amount) { return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); } @@ -673,13 +680,13 @@ namespace Sass { BUILT_IN(adjust_color) { Color_Ptr color = ARG("$color", Color); - Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]); - Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]); - Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]); - Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]); - Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]); - Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]); - Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); bool rgb = r || g || b; bool hsl = h || s || l; @@ -728,13 +735,13 @@ namespace Sass { BUILT_IN(scale_color) { Color_Ptr color = ARG("$color", Color); - Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]); - Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]); - Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]); - Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]); - Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]); - Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]); - Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); bool rgb = r || g || b; bool hsl = h || s || l; @@ -784,13 +791,13 @@ namespace Sass { BUILT_IN(change_color) { Color_Ptr color = ARG("$color", Color); - Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]); - Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]); - Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]); - Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]); - Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]); - Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]); - Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); bool rgb = r || g || b; bool hsl = h || s || l; @@ -866,25 +873,26 @@ namespace Sass { BUILT_IN(sass_unquote) { AST_Node_Obj arg = env["$string"]; - if (String_Quoted_Ptr string_quoted = SASS_MEMORY_CAST(String_Quoted, arg)) { + if (String_Quoted_Ptr string_quoted = Cast(arg)) { String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); // remember if the string was quoted (color tokens) result->is_delayed(true); // delay colors return result; } - else if (SASS_MEMORY_CAST(String_Constant, arg)) { - return (Expression_Ptr) &arg; + else if (String_Constant_Ptr str = Cast(arg)) { + return str; } - else { + else if (Expression_Ptr ex = Cast(arg)) { Sass_Output_Style oldstyle = ctx.c_options.output_style; ctx.c_options.output_style = SASS_STYLE_NESTED; std::string val(arg->to_string(ctx.c_options)); - val = SASS_MEMORY_CAST(Null, arg) ? "null" : val; + val = Cast(arg) ? "null" : val; ctx.c_options.output_style = oldstyle; deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); - return (Expression_Ptr) &arg; + return ex; } + throw std::runtime_error("Invalid Data Type for unquote"); } Signature quote_sig = "quote($string)"; @@ -892,7 +900,7 @@ namespace Sass { { AST_Node_Obj arg = env["$string"]; // only set quote mark to true if already a string - if (String_Quoted_Ptr qstr = SASS_MEMORY_CAST(String_Quoted, arg)) { + if (String_Quoted_Ptr qstr = Cast(arg)) { qstr->quote_mark('*'); return qstr; } @@ -956,7 +964,7 @@ namespace Sass { str = ins + str; } - if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) { + if (String_Quoted_Ptr ss = Cast(s)) { if (ss->quote_mark()) str = quote(str); } } @@ -999,13 +1007,13 @@ namespace Sass { String_Constant_Ptr s = ARG("$string", String_Constant); double start_at = ARG("$start-at", Number)->value(); double end_at = ARG("$end-at", Number)->value(); - String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s); + String_Quoted_Ptr ss = Cast(s); std::string str = unquote(s->value()); size_t size = utf8::distance(str.begin(), str.end()); - if (!SASS_MEMORY_CAST(Number, env["$end-at"])) { + if (!Cast(env["$end-at"])) { end_at = -1; } @@ -1055,7 +1063,7 @@ namespace Sass { } } - if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) { + if (String_Quoted_Ptr ss = Cast(s)) { String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); cpy->value(str); return cpy; @@ -1076,7 +1084,7 @@ namespace Sass { } } - if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) { + if (String_Quoted_Ptr ss = Cast(s)) { String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); cpy->value(str); return cpy; @@ -1144,7 +1152,7 @@ namespace Sass { Number_Obj least = NULL; for (size_t i = 0, L = arglist->length(); i < L; ++i) { Expression_Obj val = arglist->value_at_index(i); - Number_Obj xi = SASS_MEMORY_CAST(Number, val); + Number_Obj xi = Cast(val); if (!xi) { error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate); } @@ -1162,7 +1170,7 @@ namespace Sass { Number_Obj greatest = NULL; for (size_t i = 0, L = arglist->length(); i < L; ++i) { Expression_Obj val = arglist->value_at_index(i); - Number_Obj xi = SASS_MEMORY_CAST(Number, val); + Number_Obj xi = Cast(val); if (!xi) { error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate); } @@ -1177,9 +1185,9 @@ namespace Sass { BUILT_IN(random) { AST_Node_Obj arg = env["$limit"]; - Value_Ptr v = SASS_MEMORY_CAST(Value, arg); - Number_Ptr l = SASS_MEMORY_CAST(Number, arg); - Boolean_Ptr b = SASS_MEMORY_CAST(Boolean, arg); + Value_Ptr v = Cast(arg); + Number_Ptr l = Cast(arg); + Boolean_Ptr b = Cast(arg); if (l) { double v = l->value(); if (v < 1) { @@ -1216,25 +1224,25 @@ namespace Sass { Signature length_sig = "length($list)"; BUILT_IN(length) { - if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) { + if (Selector_List_Ptr sl = Cast(env["$list"])) { return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); } Expression_Ptr v = ARG("$list", Expression); if (v->concrete_type() == Expression::MAP) { - Map_Ptr map = SASS_MEMORY_CAST(Map, env["$list"]); + Map_Ptr map = Cast(env["$list"]); return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); } if (v->concrete_type() == Expression::SELECTOR) { - if (Compound_Selector_Ptr h = dynamic_cast(v)) { + if (Compound_Selector_Ptr h = Cast(v)) { return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (Selector_List_Ptr ls = SASS_MEMORY_CAST_PTR(Selector_List, v)) { + } else if (Selector_List_Ptr ls = Cast(v)) { return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); } else { return SASS_MEMORY_NEW(Number, pstate, 1); } } - List_Ptr list = SASS_MEMORY_CAST(List, env["$list"]); + List_Ptr list = Cast(env["$list"]); return SASS_MEMORY_NEW(Number, pstate, (double)(list ? list->size() : 1)); @@ -1244,8 +1252,8 @@ namespace Sass { BUILT_IN(nth) { Number_Ptr n = ARG("$n", Number); - Map_Ptr m = SASS_MEMORY_CAST(Map, env["$list"]); - if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) { + Map_Ptr m = Cast(env["$list"]); + if (Selector_List_Ptr sl = Cast(env["$list"])) { size_t len = m ? m->length() : sl->length(); bool empty = m ? m->empty() : sl->empty(); if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); @@ -1255,7 +1263,7 @@ namespace Sass { Listize listize; return (*sl)[static_cast(index)]->perform(&listize); } - List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + List_Obj l = Cast(env["$list"]); if (n->value() == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate); // if the argument isn't a list, then wrap it in a singleton list if (!m && !l) { @@ -1284,8 +1292,8 @@ namespace Sass { Signature set_nth_sig = "set-nth($list, $n, $value)"; BUILT_IN(set_nth) { - Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]); - List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); Number_Obj n = ARG("$n", Number); Expression_Obj v = ARG("$value", Expression); if (!l) { @@ -1298,7 +1306,7 @@ namespace Sass { if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator()); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { result->append(((i == index) ? v : (*l)[i])); } @@ -1308,8 +1316,8 @@ namespace Sass { Signature index_sig = "index($list, $value)"; BUILT_IN(index) { - Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]); - List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); Expression_Obj v = ARG("$value", Expression); if (!l) { l = SASS_MEMORY_NEW(List, pstate, 1); @@ -1324,19 +1332,22 @@ namespace Sass { return SASS_MEMORY_NEW(Null, pstate); } - Signature join_sig = "join($list1, $list2, $separator: auto)"; + Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; BUILT_IN(join) { - Map_Obj m1 = SASS_MEMORY_CAST(Map, env["$list1"]); - Map_Obj m2 = SASS_MEMORY_CAST(Map, env["$list2"]); - List_Obj l1 = SASS_MEMORY_CAST(List, env["$list1"]); - List_Obj l2 = SASS_MEMORY_CAST(List, env["$list2"]); + Map_Obj m1 = Cast(env["$list1"]); + Map_Obj m2 = Cast(env["$list2"]); + List_Obj l1 = Cast(env["$list1"]); + List_Obj l2 = Cast(env["$list2"]); String_Constant_Obj sep = ARG("$separator", String_Constant); enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); + Value* bracketed = ARG("$bracketed", Value); + bool is_bracketed = (l1 ? l1->is_bracketed() : false); if (!l1) { l1 = SASS_MEMORY_NEW(List, pstate, 1); l1->append(ARG("$list1", Expression)); sep_val = (l2 ? l2->separator() : SASS_SPACE); + is_bracketed = (l2 ? l2->is_bracketed() : false); } if (!l2) { l2 = SASS_MEMORY_NEW(List, pstate, 1); @@ -1354,21 +1365,26 @@ namespace Sass { if (sep_str == "space") sep_val = SASS_SPACE; else if (sep_str == "comma") sep_val = SASS_COMMA; else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); - List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val); - result->concat(&l1); - result->concat(&l2); + String_Constant_Obj bracketed_as_str = Cast(bracketed); + bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; + if (!bracketed_is_auto) { + is_bracketed = !bracketed->is_false(); + } + List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); + result->concat(l1); + result->concat(l2); return result.detach(); } Signature append_sig = "append($list, $val, $separator: auto)"; BUILT_IN(append) { - Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]); - List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); Expression_Obj v = ARG("$val", Expression); - if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) { + if (Selector_List_Ptr sl = Cast(env["$list"])) { Listize listize; - l = SASS_MEMORY_CAST_PTR(List, sl->perform(&listize)); + l = Cast(sl->perform(&listize)); } String_Constant_Obj sep = ARG("$separator", String_Constant); if (!l) { @@ -1405,8 +1421,8 @@ namespace Sass { List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); size_t shortest = 0; for (size_t i = 0, L = arglist->length(); i < L; ++i) { - List_Obj ith = SASS_MEMORY_CAST(List, arglist->value_at_index(i)); - Map_Obj mith = SASS_MEMORY_CAST(Map, arglist->value_at_index(i)); + List_Obj ith = Cast(arglist->value_at_index(i)); + Map_Obj mith = Cast(arglist->value_at_index(i)); if (!ith) { if (mith) { ith = mith->to_list(ctx, pstate); @@ -1415,10 +1431,10 @@ namespace Sass { ith->append(arglist->value_at_index(i)); } if (arglist->is_arglist()) { - Argument_Obj arg = (Argument_Ptr)(&arglist->at(i)); - arg->value(&ith); + Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX + arg->value(ith); } else { - (*arglist)[i] = &ith; + (*arglist)[i] = ith; } } shortest = (i ? std::min(shortest, ith->length()) : ith->length()); @@ -1428,7 +1444,7 @@ namespace Sass { for (size_t i = 0; i < shortest; ++i) { List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); for (size_t j = 0; j < L; ++j) { - zipper->append(SASS_MEMORY_CAST(List, arglist->value_at_index(j))->at(i)); + zipper->append(Cast(arglist->value_at_index(j))->at(i)); } zippers->append(zipper); } @@ -1438,7 +1454,7 @@ namespace Sass { Signature list_separator_sig = "list_separator($list)"; BUILT_IN(list_separator) { - List_Obj l = SASS_MEMORY_CAST(List, env["$list"]); + List_Obj l = Cast(env["$list"]); if (!l) { l = SASS_MEMORY_NEW(List, pstate, 1); l->append(ARG("$list", Expression)); @@ -1460,7 +1476,7 @@ namespace Sass { Map_Obj m = ARGM("$map", Map, ctx); Expression_Obj v = ARG("$key", Expression); try { - Expression_Obj val = m->at(v); // XXX + Expression_Obj val = m->at(v); return val ? val.detach() : SASS_MEMORY_NEW(Null, pstate); } catch (const std::out_of_range&) { return SASS_MEMORY_NEW(Null, pstate); @@ -1507,8 +1523,8 @@ namespace Sass { size_t len = m1->length() + m2->length(); Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); // concat not implemented for maps - *result += &m1; - *result += &m2; + *result += m1; + *result += m2; return result; } @@ -1536,7 +1552,7 @@ namespace Sass { Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr)&obj; + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX std::string name = std::string(arg->name()); name = name.erase(0, 1); // sanitize name (remove dollar sign) *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, @@ -1651,19 +1667,19 @@ namespace Sass { Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); // std::string full_name(name + "[f]"); - // Definition_Ptr def = d_env.has(full_name) ? static_cast((d_env)[full_name]) : 0; + // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; // Parameters_Ptr params = def ? def->parameters() : 0; // size_t param_size = params ? params->length() : 0; for (size_t i = 0, L = arglist->length(); i < L; ++i) { Expression_Obj expr = arglist->value_at_index(i); // if (params && params->has_rest_parameter()) { // Parameter_Obj p = param_size > i ? (*params)[i] : 0; - // List_Ptr list = SASS_MEMORY_CAST(List, expr); + // List_Ptr list = Cast(expr); // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; // } if (arglist->is_arglist()) { Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr)&obj; + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX args->append(SASS_MEMORY_NEW(Argument, pstate, expr, @@ -1674,7 +1690,7 @@ namespace Sass { args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); } } - Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, &args); + Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); Expand expand(ctx, &d_env, backtrace, &selector_stack); func->via_call(true); // calc invoke is allowed return func->perform(&expand.eval); @@ -1705,17 +1721,6 @@ namespace Sass { return res; } - //////////////// - // URL FUNCTIONS - //////////////// - - Signature image_url_sig = "image-url($path, $only-path: false, $cache-buster: false)"; - BUILT_IN(image_url) - { - error("`image_url` has been removed from libsass because it's not part of the Sass spec", pstate); - return 0; // suppress warning, error will exit anyway - } - ////////////////////////// // MISCELLANEOUS FUNCTIONS ////////////////////////// @@ -1759,19 +1764,19 @@ namespace Sass { // Parse args into vector of selectors std::vector parsedSelectors; for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = SASS_MEMORY_CAST(Expression, arglist->value_at_index(i)); + Expression_Obj exp = Cast(arglist->value_at_index(i)); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << "$selectors: null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; error(msg.str(), pstate); } - if (String_Constant_Obj str = SASS_MEMORY_CAST(String_Constant, exp)) { + if (String_Constant_Obj str = Cast(exp)) { str->quote_mark(0); } - std::string exp_src = exp->to_string(ctx.c_options) + "{"; + std::string exp_src = exp->to_string(ctx.c_options); Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); - parsedSelectors.push_back(&sel); + parsedSelectors.push_back(sel); } // Nothing to do @@ -1787,7 +1792,7 @@ namespace Sass { for(;itr != parsedSelectors.end(); ++itr) { Selector_List_Obj child = *itr; std::vector exploded; - selector_stack.push_back(&result); + selector_stack.push_back(result); Selector_List_Obj rv = child->resolve_parent_refs(ctx, selector_stack); selector_stack.pop_back(); for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { @@ -1812,19 +1817,19 @@ namespace Sass { // Parse args into vector of selectors std::vector parsedSelectors; for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = SASS_MEMORY_CAST(Expression, arglist->value_at_index(i)); + Expression_Obj exp = Cast(arglist->value_at_index(i)); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << "$selectors: null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for 'selector-append'"; error(msg.str(), pstate); } - if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) { + if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } - std::string exp_src = exp->to_string() + "{"; + std::string exp_src = exp->to_string(); Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); - parsedSelectors.push_back(&sel); + parsedSelectors.push_back(sel); } // Nothing to do @@ -1865,7 +1870,7 @@ namespace Sass { } // Cannot be a Universal selector - Element_Selector_Obj pType = SASS_MEMORY_CAST(Element_Selector, childSeq->head()->first()); + Element_Selector_Obj pType = Cast(childSeq->head()->first()); if(pType && pType->name() == "*") { std::string msg("Can't append \""); msg += childSeq->to_string(); @@ -1878,12 +1883,12 @@ namespace Sass { // TODO: Add check for namespace stuff // append any selectors in childSeq's head - parentSeqClone->innermost()->head()->concat(&base->head()); + parentSeqClone->innermost()->head()->concat(base->head()); // Set parentSeqClone new tail parentSeqClone->innermost()->tail( base->tail() ); - newElements.push_back(&parentSeqClone); + newElements.push_back(parentSeqClone); } } @@ -1900,7 +1905,7 @@ namespace Sass { Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); - Selector_List_Obj result = selector1->unify_with(&selector2, ctx); + Selector_List_Obj result = selector1->unify_with(selector2, ctx); Listize listize; return result->perform(&listize); } @@ -1981,5 +1986,12 @@ namespace Sass { return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); } + Signature is_bracketed_sig = "is-bracketed($list)"; + BUILT_IN(is_bracketed) + { + Value_Obj value = ARG("$list", Value); + List_Obj list = Cast(value); + return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); + } } } diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp index ef4f60ef4..f2cc0af50 100755 --- a/src/libsass/src/functions.hpp +++ b/src/libsass/src/functions.hpp @@ -12,7 +12,6 @@ name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Back namespace Sass { struct Backtrace; - typedef Environment Env; typedef const char* Signature; typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); @@ -89,7 +88,6 @@ namespace Sass { extern Signature call_sig; extern Signature not_sig; extern Signature if_sig; - extern Signature image_url_sig; extern Signature map_get_sig; extern Signature map_merge_sig; extern Signature map_remove_sig; @@ -107,6 +105,7 @@ namespace Sass { extern Signature is_superselector_sig; extern Signature simple_selectors_sig; extern Signature selector_parse_sig; + extern Signature is_bracketed_sig; BUILT_IN(rgb); BUILT_IN(rgba_4); @@ -171,7 +170,6 @@ namespace Sass { BUILT_IN(call); BUILT_IN(sass_not); BUILT_IN(sass_if); - BUILT_IN(image_url); BUILT_IN(map_get); BUILT_IN(map_merge); BUILT_IN(map_remove); @@ -189,6 +187,7 @@ namespace Sass { BUILT_IN(is_superselector); BUILT_IN(simple_selectors); BUILT_IN(selector_parse); + BUILT_IN(is_bracketed); } } diff --git a/src/libsass/src/inspect.cpp b/src/libsass/src/inspect.cpp index 3b68900d5..4ace72484 100755 --- a/src/libsass/src/inspect.cpp +++ b/src/libsass/src/inspect.cpp @@ -166,7 +166,7 @@ namespace Sass { import->urls().front()->perform(this); if (import->urls().size() == 1) { - if (&import->import_queries()) { + if (import->import_queries()) { append_mandatory_space(); import->import_queries()->perform(this); } @@ -179,7 +179,7 @@ namespace Sass { import->urls()[i]->perform(this); if (import->urls().size() - 1 == i) { - if (&import->import_queries()) { + if (import->import_queries()) { append_mandatory_space(); import->import_queries()->perform(this); } @@ -360,10 +360,19 @@ namespace Sass { append_string(")"); } + std::string Inspect::lbracket(List_Ptr list) { + return list->is_bracketed() ? "[" : "("; + } + + std::string Inspect::rbracket(List_Ptr list) { + return list->is_bracketed() ? "]" : ")"; + } + void Inspect::operator()(List_Ptr list) { - if (output_style() == TO_SASS && list->empty()) { - append_string("()"); + if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { + append_string(lbracket(list)); + append_string(rbracket(list)); return; } std::string sep(list->separator() == SASS_SPACE ? " " : ","); @@ -374,19 +383,24 @@ namespace Sass { bool was_space_array = in_space_array; bool was_comma_array = in_comma_array; + // if the list is bracketed, always include the left bracket + if (list->is_bracketed()) { + append_string(lbracket(list)); + } // probably ruby sass eqivalent of element_needs_parens - if (output_style() == TO_SASS && + else if (output_style() == TO_SASS && list->length() == 1 && !list->from_selector() && - !SASS_MEMORY_CAST(List, list->at(0)) && - !SASS_MEMORY_CAST(Selector_List, list->at(0))) { - append_string("("); + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(lbracket(list)); } else if (!in_declaration && (list->separator() == SASS_HASH || (list->separator() == SASS_SPACE && in_space_array) || (list->separator() == SASS_COMMA && in_comma_array) )) { - append_string("("); + append_string(lbracket(list)); } if (list->separator() == SASS_SPACE) in_space_array = true; @@ -399,7 +413,7 @@ namespace Sass { if (output_style() != TO_SASS) { if (list_item->is_invisible()) { // this fixes an issue with "" in a list - if (!SASS_MEMORY_CAST(String_Constant, list_item)) { + if (!Cast(list_item)) { continue; } } @@ -415,19 +429,29 @@ namespace Sass { in_comma_array = was_comma_array; in_space_array = was_space_array; + + // if the list is bracketed, always include the right bracket + if (list->is_bracketed()) { + if (list->separator() == SASS_COMMA && list->size() == 1) { + append_string(","); + } + append_string(rbracket(list)); + } // probably ruby sass eqivalent of element_needs_parens - if (output_style() == TO_SASS && + else if (output_style() == TO_SASS && list->length() == 1 && !list->from_selector() && - !SASS_MEMORY_CAST(List, list->at(0)) && - !SASS_MEMORY_CAST(Selector_List, list->at(0))) { - append_string(",)"); + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(","); + append_string(rbracket(list)); } else if (!in_declaration && (list->separator() == SASS_HASH || (list->separator() == SASS_SPACE && in_space_array) || (list->separator() == SASS_COMMA && in_comma_array) )) { - append_string(")"); + append_string(rbracket(list)); } } @@ -443,7 +467,7 @@ namespace Sass { expr->is_right_interpolant()) )) append_string(" "); - switch (expr->type()) { + switch (expr->optype()) { case Sass_OP::AND: append_string("&&"); break; case Sass_OP::OR: append_string("||"); break; case Sass_OP::EQ: append_string("=="); break; @@ -471,8 +495,8 @@ namespace Sass { void Inspect::operator()(Unary_Expression_Ptr expr) { - if (expr->type() == Unary_Expression::PLUS) append_string("+"); - else append_string("-"); + if (expr->optype() == Unary_Expression::PLUS) append_string("+"); + else append_string("-"); expr->operand()->perform(this); } @@ -744,9 +768,9 @@ namespace Sass { { append_token("not", sn); append_mandatory_space(); - if (sn->needs_parens(&sn->condition())) append_string("("); + if (sn->needs_parens(sn->condition())) append_string("("); sn->condition()->perform(this); - if (sn->needs_parens(&sn->condition())) append_string(")"); + if (sn->needs_parens(sn->condition())) append_string(")"); } void Inspect::operator()(Supports_Declaration_Ptr sd) @@ -766,7 +790,7 @@ namespace Sass { void Inspect::operator()(Media_Query_Ptr mq) { size_t i = 0; - if (&mq->media_type()) { + if (mq->media_type()) { if (mq->is_negated()) append_string("not "); else if (mq->is_restricted()) append_string("only "); mq->media_type()->perform(this); @@ -851,7 +875,7 @@ namespace Sass { return; } if (a->value()->concrete_type() == Expression::STRING) { - String_Constant_Ptr s = SASS_MEMORY_CAST(String_Constant, a->value()); + String_Constant_Ptr s = Cast(a->value()); if (s) s->perform(this); } else { a->value()->perform(this); @@ -919,7 +943,7 @@ namespace Sass { append_token(s->ns_name(), s); if (!s->matcher().empty()) { append_string(s->matcher()); - if (&s->value() && *s->value()) { + if (s->value() && *s->value()) { s->value()->perform(this); } } @@ -1040,8 +1064,8 @@ namespace Sass { bool was_comma_array = in_comma_array; // probably ruby sass eqivalent of element_needs_parens if (output_style() == TO_SASS && g->length() == 1 && - (!SASS_MEMORY_CAST(List, (*g)[0]) && - !SASS_MEMORY_CAST(Selector_List, (*g)[0]))) { + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { append_string("("); } else if (!in_declaration && in_comma_array) { @@ -1053,7 +1077,7 @@ namespace Sass { for (size_t i = 0, L = g->length(); i < L; ++i) { if (!in_wrapped && i == 0) append_indentation(); if ((*g)[i] == 0) continue; - schedule_mapping(&g->at(i)->last()); + schedule_mapping(g->at(i)->last()); // add_open_mapping((*g)[i]->last()); (*g)[i]->perform(this); // add_close_mapping((*g)[i]->last()); @@ -1066,8 +1090,8 @@ namespace Sass { in_comma_array = was_comma_array; // probably ruby sass eqivalent of element_needs_parens if (output_style() == TO_SASS && g->length() == 1 && - (!SASS_MEMORY_CAST(List, (*g)[0]) && - !SASS_MEMORY_CAST(Selector_List, (*g)[0]))) { + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { append_string(",)"); } else if (!in_declaration && in_comma_array) { diff --git a/src/libsass/src/inspect.hpp b/src/libsass/src/inspect.hpp index a5fc20570..59805a156 100755 --- a/src/libsass/src/inspect.hpp +++ b/src/libsass/src/inspect.hpp @@ -92,6 +92,9 @@ namespace Sass { virtual void operator()(Complex_Selector_Ptr); virtual void operator()(Selector_List_Ptr); + virtual std::string lbracket(List_Ptr); + virtual std::string rbracket(List_Ptr); + // template // void fallback(U x) { fallback_impl(reinterpret_cast(x)); } }; diff --git a/src/libsass/src/lexer.cpp b/src/libsass/src/lexer.cpp index 464ca444a..781257e2e 100755 --- a/src/libsass/src/lexer.cpp +++ b/src/libsass/src/lexer.cpp @@ -1,6 +1,5 @@ #include "sass.hpp" #include -#include #include #include #include "lexer.hpp" diff --git a/src/libsass/src/listize.cpp b/src/libsass/src/listize.cpp index 7edb2359f..88329ba1b 100755 --- a/src/libsass/src/listize.cpp +++ b/src/libsass/src/listize.cpp @@ -70,7 +70,7 @@ namespace Sass { if (tail) { Expression_Obj tt = tail->perform(this); - if (List_Ptr ls = SASS_MEMORY_CAST(List, tt)) + if (List_Ptr ls = Cast(tt)) { l->concat(ls); } } if (l->length() == 0) return 0; @@ -79,7 +79,7 @@ namespace Sass { Expression_Ptr Listize::fallback_impl(AST_Node_Ptr n) { - return dynamic_cast(n); + return Cast(n); } } diff --git a/src/libsass/src/listize.hpp b/src/libsass/src/listize.hpp index 208f43826..9716ebefc 100755 --- a/src/libsass/src/listize.hpp +++ b/src/libsass/src/listize.hpp @@ -11,7 +11,6 @@ namespace Sass { - typedef Environment Env; struct Backtrace; class Listize : public Operation_CRTP { diff --git a/src/libsass/src/memory/SharedPtr.cpp b/src/libsass/src/memory/SharedPtr.cpp index 53f368131..e6d44dab1 100755 --- a/src/libsass/src/memory/SharedPtr.cpp +++ b/src/libsass/src/memory/SharedPtr.cpp @@ -18,7 +18,7 @@ namespace Sass { std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; std::cerr << "###################################\n"; for (auto var : all) { - if (AST_Node_Ptr ast = SASS_MEMORY_CAST_PTR(AST_Node, var)) { + if (AST_Node_Ptr ast = Cast(var)) { debug_ast(ast); } else { std::cerr << "LEAKED " << var << "\n"; @@ -62,7 +62,7 @@ namespace Sass { #endif if (node->refcounter == 0) { #ifdef DEBUG_SHARED_PTR - AST_Node_Ptr ptr = SASS_MEMORY_CAST_PTR(AST_Node, node); + AST_Node_Ptr ptr = Cast(node); if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; #endif if (!node->detached) { diff --git a/src/libsass/src/memory/SharedPtr.hpp b/src/libsass/src/memory/SharedPtr.hpp index 3a85fb1a9..2df55bc24 100755 --- a/src/libsass/src/memory/SharedPtr.hpp +++ b/src/libsass/src/memory/SharedPtr.hpp @@ -17,7 +17,7 @@ namespace Sass { #ifdef DEBUG_SHARED_PTR #define SASS_MEMORY_NEW(Class, ...) \ - static_cast((new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + ((new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ #define SASS_MEMORY_COPY(obj) \ ((obj)->copy(__FILE__, __LINE__)) \ @@ -38,12 +38,6 @@ namespace Sass { #endif - #define SASS_MEMORY_CAST(Class, obj) \ - (dynamic_cast(&obj)) \ - - #define SASS_MEMORY_CAST_PTR(Class, ptr) \ - (dynamic_cast(ptr)) \ - class SharedObj { protected: friend class SharedPtr; @@ -114,13 +108,10 @@ namespace Sass { // destructor ~SharedPtr(); public: - SharedObj* obj () { - return node; - }; SharedObj* obj () const { return node; }; - SharedObj* operator-> () { + SharedObj* operator-> () const { return node; }; bool isNull () { @@ -129,68 +120,58 @@ namespace Sass { bool isNull () const { return node == NULL; }; - SharedObj* detach() { - node->detached = true; - return node; - }; SharedObj* detach() const { if (node) { node->detached = true; } return node; }; - operator bool() { - return node != NULL; - }; operator bool() const { return node != NULL; }; }; - template < typename T > + template < class T > class SharedImpl : private SharedPtr { public: SharedImpl() : SharedPtr(NULL) {}; SharedImpl(T* node) : SharedPtr(node) {}; + template < class U > + SharedImpl(SharedImpl obj) + : SharedPtr(static_cast(obj.ptr())) {} SharedImpl(T&& node) : SharedPtr(node) {}; SharedImpl(const T& node) : SharedPtr(node) {}; ~SharedImpl() {}; public: - T* operator& () { + operator T*() const { return static_cast(this->obj()); - }; - T* operator& () const { - return static_cast(this->obj()); - }; - T& operator* () { + } + operator T&() const { return *static_cast(this->obj()); - }; + } T& operator* () const { return *static_cast(this->obj()); }; - T* operator-> () { - return static_cast(this->obj()); - }; T* operator-> () const { return static_cast(this->obj()); }; - T* ptr () { + T* ptr () const { return static_cast(this->obj()); }; - T* detach() { + T* detach() const { if (this->obj() == NULL) return NULL; return static_cast(SharedPtr::detach()); } - bool isNull() { + bool isNull() const { return this->obj() == NULL; } - operator bool() { - return this->obj() != NULL; + bool operator<(const T& rhs) const { + return *this->ptr() < rhs; }; operator bool() const { return this->obj() != NULL; diff --git a/src/libsass/src/node.cpp b/src/libsass/src/node.cpp index f408521a2..1cea923a8 100755 --- a/src/libsass/src/node.cpp +++ b/src/libsass/src/node.cpp @@ -66,12 +66,12 @@ namespace Sass { bool Node::contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const { - bool found = false; + bool found = false; for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { Node& toTest = *iter; - if (nodesEqual(toTest, potentialChild, simpleSelectorOrderDependent)) { + if (toTest == potentialChild) { found = true; break; } @@ -82,37 +82,32 @@ namespace Sass { bool Node::operator==(const Node& rhs) const { - return nodesEqual(*this, rhs, true /*simpleSelectorOrderDependent*/); - } - - - bool nodesEqual(const Node& lhs, const Node& rhs, bool simpleSelectorOrderDependent) { - if (lhs.type() != rhs.type()) { + if (this->type() != rhs.type()) { return false; } - if (lhs.isCombinator()) { + if (this->isCombinator()) { - return lhs.combinator() == rhs.combinator(); + return this->combinator() == rhs.combinator(); - } else if (lhs.isNil()) { + } else if (this->isNil()) { return true; // no state to check - } else if (lhs.isSelector()){ + } else if (this->isSelector()){ - return selectors_equal(*&lhs.selector(), *&rhs.selector(), simpleSelectorOrderDependent); + return *this->selector() == *rhs.selector(); - } else if (lhs.isCollection()) { + } else if (this->isCollection()) { - if (lhs.collection()->size() != rhs.collection()->size()) { + if (this->collection()->size() != rhs.collection()->size()) { return false; } - for (NodeDeque::iterator lhsIter = lhs.collection()->begin(), lhsIterEnd = lhs.collection()->end(), + for (NodeDeque::iterator lhsIter = this->collection()->begin(), lhsIterEnd = this->collection()->end(), rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { - if (!nodesEqual(*lhsIter, *rhsIter, simpleSelectorOrderDependent)) { + if (*lhsIter != *rhsIter) { return false; } @@ -128,10 +123,10 @@ namespace Sass { void Node::plus(Node& rhs) { - if (!this->isCollection() || !rhs.isCollection()) { - throw "Both the current node and rhs must be collections."; + if (!this->isCollection() || !rhs.isCollection()) { + throw "Both the current node and rhs must be collections."; } - this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); + this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); } #ifdef DEBUG @@ -189,7 +184,7 @@ namespace Sass { if (pToConvert->head() && pToConvert->head()->has_parent_ref()) { Complex_Selector_Obj tail = pToConvert->tail(); if (tail) tail->has_line_feed(pToConvert->has_line_feed()); - pToConvert = &tail; + pToConvert = tail; } while (pToConvert) { @@ -216,7 +211,7 @@ namespace Sass { // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); } - pToConvert = &pToConvert->tail(); + pToConvert = pToConvert->tail(); } return node; @@ -254,7 +249,7 @@ namespace Sass { // collections, and can result in an infinite loop during the call to parentSuperselector() pCurrent->tail(SASS_MEMORY_COPY(child.selector())); // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - pCurrent = &pCurrent->tail(); + pCurrent = pCurrent->tail(); } else if (child.isCombinator()) { pCurrent->combinator(child.combinator()); if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); @@ -265,7 +260,7 @@ namespace Sass { if (nextNode.isCombinator()) { pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL)); if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed); - pCurrent = &pCurrent->tail(); + pCurrent = pCurrent->tail(); } } } else { diff --git a/src/libsass/src/node.hpp b/src/libsass/src/node.hpp index 21d10fb3a..969d5dfee 100755 --- a/src/libsass/src/node.hpp +++ b/src/libsass/src/node.hpp @@ -113,8 +113,6 @@ namespace Sass { Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx); Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx); - bool nodesEqual(const Node& one, const Node& two, bool simpleSelectorOrderDependent); - } #endif diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp index 456020001..0ba43332e 100755 --- a/src/libsass/src/output.cpp +++ b/src/libsass/src/output.cpp @@ -116,8 +116,8 @@ namespace Sass { if (!Util::isPrintable(r, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { const Statement_Obj& stm = b->at(i); - if (dynamic_cast(&stm)) { - if (!dynamic_cast(&stm)) { + if (Cast(stm)) { + if (!Cast(stm)) { stm->perform(this); } } @@ -135,27 +135,27 @@ namespace Sass { append_optional_linefeed(); } if (s) s->perform(this); - append_scope_opener(&b); + append_scope_opener(b); for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); bool bPrintExpression = true; // Check print conditions - if (Declaration_Ptr dec = SASS_MEMORY_CAST(Declaration, stm)) { - if (String_Constant_Ptr valConst = SASS_MEMORY_CAST(String_Constant, dec->value())) { + if (Declaration_Ptr dec = Cast(stm)) { + if (String_Constant_Ptr valConst = Cast(dec->value())) { std::string val(valConst->value()); - if (String_Quoted_Ptr qstr = SASS_MEMORY_CAST_PTR(String_Quoted, valConst)) { + if (String_Quoted_Ptr qstr = Cast(valConst)) { if (!qstr->quote_mark() && val.empty()) { bPrintExpression = false; } } } - else if (List_Ptr list = SASS_MEMORY_CAST(List, dec->value())) { + else if (List_Ptr list = Cast(dec->value())) { bool all_invisible = true; for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { - Expression_Ptr item = &list->at(list_i); + Expression_Ptr item = list->at(list_i); if (!item->is_invisible()) all_invisible = false; } - if (all_invisible) bPrintExpression = false; + if (all_invisible && !list->is_bracketed()) bPrintExpression = false; } } // Print if OK @@ -164,7 +164,7 @@ namespace Sass { } } if (output_style() == NESTED) indentation -= r->tabs(); - append_scope_closer(&b); + append_scope_closer(b); } void Output::operator()(Keyframe_Rule_Ptr r) @@ -172,7 +172,7 @@ namespace Sass { Block_Obj b = r->block(); Selector_Obj v = r->name(); - if (&v) { + if (!v.isNull()) { v->perform(this); } @@ -201,7 +201,7 @@ namespace Sass { if (!Util::isPrintable(f, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm)) { + if (Cast(stm)) { stm->perform(this); } } @@ -237,7 +237,7 @@ namespace Sass { if (!Util::isPrintable(m, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm)) { + if (Cast(stm)) { stm->perform(this); } } @@ -282,7 +282,7 @@ namespace Sass { if (v) { append_mandatory_space(); // ruby sass bug? should use options? - append_token(v->to_string(/* opt */), &v); + append_token(v->to_string(/* opt */), v); } if (!b) { append_delimiter(); diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp index 89cdb9fe5..d266484ce 100755 --- a/src/libsass/src/parser.cpp +++ b/src/libsass/src/parser.cpp @@ -1,7 +1,4 @@ #include "sass.hpp" -#include -#include -#include #include "parser.hpp" #include "file.hpp" #include "inspect.hpp" @@ -24,8 +21,10 @@ // Another case with delayed values are colors. In compressed mode // only processed values get compressed (other are left as written). +#include +#include +#include #include -#include namespace Sass { using namespace Constants; @@ -96,21 +95,25 @@ namespace Sass { /* main entry point to parse root block */ Block_Obj Parser::parse() { - bool is_root = false; - Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // consume unicode BOM read_bom(); - // custom headers + // create a block AST node to hold children + Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // check seems a bit esoteric but works if (ctx.resources.size() == 1) { - is_root = true; - ctx.apply_custom_headers(&root, path, pstate); + // apply headers only on very first include + ctx.apply_custom_headers(root, path, pstate); } + // parse children nodes block_stack.push_back(root); - /* bool rv = */ parse_block_nodes(is_root); + parse_block_nodes(true); block_stack.pop_back(); - // update for end position + // update final position root->update_pstate(pstate); if (position != end) { @@ -138,7 +141,7 @@ namespace Sass { Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); block_stack.push_back(block); - if (!parse_block_nodes()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); if (!lex_css < exactly<'}'> >()) { css_error("Invalid CSS", " after ", ": expected \"}\", was "); @@ -154,7 +157,7 @@ namespace Sass { block_stack.pop_back(); - return █ + return block; } // convenience function for block parsing @@ -162,7 +165,6 @@ namespace Sass { // also updates the `in_at_root` flag Block_Obj Parser::parse_block(bool is_root) { - LOCAL_FLAG(in_at_root, is_root); return parse_css_block(is_root); } @@ -214,15 +216,15 @@ namespace Sass { // also parse block comments // first parse everything that is allowed in functions - if (lex < variable >(true)) { block->append(&parse_assignment()); } - else if (lex < kwd_err >(true)) { block->append(&parse_error()); } - else if (lex < kwd_dbg >(true)) { block->append(&parse_debug()); } - else if (lex < kwd_warn >(true)) { block->append(&parse_warning()); } - else if (lex < kwd_if_directive >(true)) { block->append(&parse_if_directive()); } - else if (lex < kwd_for_directive >(true)) { block->append(&parse_for_directive()); } - else if (lex < kwd_each_directive >(true)) { block->append(&parse_each_directive()); } - else if (lex < kwd_while_directive >(true)) { block->append(&parse_while_directive()); } - else if (lex < kwd_return_directive >(true)) { block->append(&parse_return_directive()); } + if (lex < variable >(true)) { block->append(parse_assignment()); } + else if (lex < kwd_err >(true)) { block->append(parse_error()); } + else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } + else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } + else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } + else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } + else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } + else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } + else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } // parse imports to process later else if (lex < kwd_import >(true)) { @@ -232,9 +234,11 @@ namespace Sass { error("Import directives may not be used within control directives or mixins.", pstate); } } + // this puts the parsed doc into sheets + // import stub will fetch this in expand Import_Obj imp = parse_import(); // if it is a url, we only add the statement - if (!imp->urls().empty()) block->append(&imp); + if (!imp->urls().empty()) block->append(imp); // process all resources now (add Import_Stub nodes) for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); @@ -244,32 +248,38 @@ namespace Sass { else if (lex < kwd_extend >(true)) { Lookahead lookahead = lookahead_for_include(position); if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - Selector_Obj target; - if (lookahead.has_interpolants) target = &parse_selector_schema(lookahead.found); - else target = &parse_selector_list(true); - block->append(SASS_MEMORY_NEW(Extension, pstate, &target)); + Selector_List_Obj target; + if (!lookahead.has_interpolants) { + target = parse_selector_list(true); + } + else { + target = SASS_MEMORY_NEW(Selector_List, pstate); + target->schema(parse_selector_schema(lookahead.found, true)); + } + + block->append(SASS_MEMORY_NEW(Extension, pstate, target)); } // selector may contain interpolations which need delayed evaluation else if (!(lookahead_result = lookahead_for_selector(position)).error) - { block->append(&parse_ruleset(lookahead_result, is_root)); } + { block->append(parse_ruleset(lookahead_result)); } // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(&parse_media_block()); } - else if (lex < kwd_at_root >(true)) { block->append(&parse_at_root_block()); } - else if (lex < kwd_include_directive >(true)) { block->append(&parse_include_directive()); } - else if (lex < kwd_content_directive >(true)) { block->append(&parse_content_directive()); } - else if (lex < kwd_supports_directive >(true)) { block->append(&parse_supports_directive()); } - else if (lex < kwd_mixin >(true)) { block->append(&parse_definition(Definition::MIXIN)); } - else if (lex < kwd_function >(true)) { block->append(&parse_definition(Definition::FUNCTION)); } + else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } + else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } + else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } + else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } + else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } + else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } // ignore the @charset directive for now else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } // generic at keyword (keep last) - else if (lex< re_special_directive >(true)) { block->append(&parse_special_directive()); } - else if (lex< re_prefixed_directive >(true)) { block->append(&parse_prefixed_directive()); } - else if (lex< at_keyword >(true)) { block->append(&parse_directive()); } + else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } + else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } + else if (lex< at_keyword >(true)) { block->append(parse_directive()); } else if (is_root /* && block->is_root() */) { lex< css_whitespace >(); @@ -283,7 +293,7 @@ namespace Sass { // maybe we are expected to parse something? Declaration_Obj decl = parse_declaration(); decl->tabs(indentation); - block->append(&decl); + block->append(decl); // maybe we have a "sub-block" if (peek< exactly<'{'> >()) { if (decl->is_indented()) ++ indentation; @@ -312,24 +322,24 @@ namespace Sass { } else if (lex< uri_prefix >()) { Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", &args); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); if (lex< quoted_string >()) { - Expression_Obj the_url = &parse_string(); - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + Expression_Obj the_url = parse_string(); + args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), the_url)); } else if (String_Obj the_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), the_url)); } else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { Expression_Obj the_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), the_url)); } else { error("malformed URL", pstate); } if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate); - to_import.push_back(std::pair("", &result)); + to_import.push_back(std::pair("", result)); } else { if (first) error("@import directive requires a url or quoted path", pstate); @@ -345,9 +355,12 @@ namespace Sass { for(auto location : to_import) { if (location.second) { - imp->urls().push_back(&location.second); - } else if (!ctx.call_importers(unquote(location.first), path, pstate, &imp)) { - ctx.import_url(&imp, location.first, path); + imp->urls().push_back(location.second); + } + // check if custom importers want to take over the handling + else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { + // nobody wants it, so we do our import + ctx.import_url(imp, location.first, path); } } @@ -367,7 +380,7 @@ namespace Sass { else stack.push_back(Scope::Function); Block_Obj body = parse_block(); stack.pop_back(); - return SASS_MEMORY_NEW(Definition, source_position_of_def, name, ¶ms, &body, which_type); + return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); } Parameters_Obj Parser::parse_parameters() @@ -378,7 +391,7 @@ namespace Sass { if (lex_css< exactly<'('> >()) { // if there's anything there at all if (!peek_css< exactly<')'> >()) { - do params->append(&parse_parameter()); + do params->append(parse_parameter()); while (lex_css< exactly<','> >()); } if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); @@ -402,7 +415,7 @@ namespace Sass { else if (lex< exactly< ellipsis > >()) { is_rest = true; } - return SASS_MEMORY_NEW(Parameter, pos, name, &val, is_rest); + return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); } Arguments_Obj Parser::parse_arguments() @@ -413,7 +426,7 @@ namespace Sass { if (lex_css< exactly<'('> >()) { // if there's anything there at all if (!peek_css< exactly<')'> >()) { - do args->append(&parse_argument()); + do args->append(parse_argument()); while (lex_css< exactly<','> >()); } if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); @@ -441,7 +454,7 @@ namespace Sass { bool is_arglist = false; bool is_keyword = false; Expression_Obj val = parse_space_list(); - List_Ptr l = SASS_MEMORY_CAST(List, val); + List_Ptr l = Cast(val); if (lex_css< exactly< ellipsis > >()) { if (val->concrete_type() == Expression::MAP || ( (l != NULL && l->separator() == SASS_HASH) @@ -464,7 +477,7 @@ namespace Sass { Expression_Obj val; Lookahead lookahead = lookahead_for_value(position); if (lookahead.has_interpolants && lookahead.found) { - val = &parse_value_schema(lookahead.found); + val = parse_value_schema(lookahead.found); } else { val = parse_list(); } @@ -478,15 +491,22 @@ namespace Sass { } // a ruleset connects a selector and a block - Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead, bool is_root) + Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) { + // inherit is_root from parent block + Block_Obj parent = block_stack.back(); + bool is_root = parent && parent->is_root(); // make sure to move up the the last position lex < optional_css_whitespace >(false, true); // create the connector object (add parts later) Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); // parse selector static or as schema to be evaluated later - if (lookahead.parsable) ruleset->selector(&parse_selector_list(is_root)); - else ruleset->selector(&parse_selector_schema(lookahead.found)); + if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + else { + Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); + list->schema(parse_selector_schema(lookahead.found, false)); + ruleset->selector(list); + } // then parse the inner block stack.push_back(Scope::Rules); ruleset->block(parse_block()); @@ -494,7 +514,6 @@ namespace Sass { // update for end position ruleset->update_pstate(pstate); ruleset->block()->update_pstate(pstate); - // inherit is_root from parent block // need this info for sanity checks ruleset->is_root(is_root); // return AST Node @@ -504,7 +523,7 @@ namespace Sass { // parse a selector schema that will be evaluated in the eval stage // uses a string schema internally to do the actual schema handling // in the eval stage we will be re-parse it into an actual selector - Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector) + Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) { // move up to the start lex< optional_spaces >(); @@ -513,6 +532,7 @@ namespace Sass { String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); // the selector schema is pretty much just a wrapper for the string schema Selector_Schema_Ptr selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + selector_schema->connect_parent(chroot == false); selector_schema->media_block(last_media_block); // process until end @@ -525,7 +545,7 @@ namespace Sass { String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); pstate += Offset(parsed); str->update_pstate(pstate); - schema->append(&str); + schema->append(str); } // check if the interpolation only contains white-space (error out) @@ -541,7 +561,7 @@ namespace Sass { interpolant->is_interpolant(true); // schema->has_interpolants(true); // add to the string schema - schema->append(&interpolant); + schema->append(interpolant); // advance parser state pstate.add(p+2, j); // advance position @@ -557,7 +577,7 @@ namespace Sass { pstate += Offset(parsed); str->update_pstate(pstate); i = end_of_selector; - schema->append(&str); + schema->append(str); } // exit loop } @@ -611,24 +631,28 @@ namespace Sass { // parse a list of complex selectors // this is the main entry point for most - Selector_List_Obj Parser::parse_selector_list(bool in_root) + Selector_List_Obj Parser::parse_selector_list(bool chroot) { bool reloop = true; bool had_linefeed = false; Complex_Selector_Obj sel; Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); group->media_block(last_media_block); + + if (peek_css< alternatives < end_of_file, exactly <'{'> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + do { reloop = false; had_linefeed = had_linefeed || peek_newline(); - if (peek_css< class_char < selector_list_delims > >()) + if (peek_css< alternatives < class_char < selector_list_delims > > >()) break; // in case there are superfluous commas at the end - // now parse the complex selector - sel = parse_complex_selector(in_root); + sel = parse_complex_selector(chroot); if (!sel) return group.detach(); @@ -662,7 +686,7 @@ namespace Sass { // complex selector, with one of four combinator operations. // the compound selector (head) is optional, since the combinator // can come first in the whole selector sequence (like `> DIV'). - Complex_Selector_Obj Parser::parse_complex_selector(bool in_root) + Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) { String_Ptr reference = 0; @@ -670,6 +694,8 @@ namespace Sass { advanceToNextToken(); Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); + if (peek < end_of_file >()) return 0; + // parse the left hand side Compound_Selector_Obj lhs; // special case if it starts with combinator ([+~>]) @@ -678,11 +704,9 @@ namespace Sass { lhs = parse_compound_selector(); } - // check for end of file condition - if (peek < end_of_file >()) return 0; // parse combinator between lhs and rhs - Complex_Selector::Combinator combinator; + Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; @@ -693,7 +717,6 @@ namespace Sass { reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? } - else /* if (lex< zero >()) */ combinator = Complex_Selector::ANCESTOR_OF; if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; @@ -715,7 +738,7 @@ namespace Sass { // add a parent selector if we are not in a root // also skip adding parent ref if we only have refs - if (!sel->has_parent_ref() && !in_at_root && !in_root) { + if (!sel->has_parent_ref() && !chroot) { // create the objects to wrap parent selector reference Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); @@ -760,7 +783,7 @@ namespace Sass { // parse functional if (match < re_pseudo_selector >()) { - seq->append(&parse_simple_selector()); + seq->append(parse_simple_selector()); } // parse parent selector else if (lex< exactly<'&'> >(false)) @@ -795,11 +818,11 @@ namespace Sass { else { Simple_Selector_Obj sel = parse_simple_selector(); if (!sel) return 0; - seq->append(&sel); + seq->append(sel); } } - if (seq && !peek_css>()) { + if (seq && !peek_css>>()) { seq->has_line_break(peek_newline()); } @@ -825,16 +848,16 @@ namespace Sass { return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); } else if (peek< pseudo_not >()) { - return &parse_negated_selector(); + return parse_negated_selector(); } else if (peek< re_pseudo_selector >()) { - return &parse_pseudo_selector(); + return parse_pseudo_selector(); } else if (peek< exactly<':'> >()) { - return &parse_pseudo_selector(); + return parse_pseudo_selector(); } else if (lex < exactly<'['> >()) { - return &parse_attribute_selector(); + return parse_attribute_selector(); } else if (lex< placeholder >()) { Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); @@ -855,7 +878,7 @@ namespace Sass { error("negated selector is missing ')'", pstate); } name.erase(name.size() - 1); - return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, &negated); + return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); } // a pseudo selector often starts with one or two colons @@ -896,7 +919,7 @@ namespace Sass { } else if (Selector_List_Obj wrapped = parse_selector_list(true)) { if (wrapped && lex_css< exactly<')'> >()) { - return SASS_MEMORY_NEW(Wrapped_Selector, p, name, &wrapped); + return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); } } @@ -933,7 +956,7 @@ namespace Sass { value = SASS_MEMORY_NEW(String_Constant, p, lexed); } else if (lex_css< quoted_string >()) { - value = &parse_interpolated_chunk(lexed, true); // needed! + value = parse_interpolated_chunk(lexed, true); // needed! } else { error("expected a string constant or identifier in attribute selector for " + name, pstate); @@ -974,22 +997,22 @@ namespace Sass { if (peek_css< exactly<';'> >()) error("style declaration must contain a value", pstate); if (peek_css< exactly<'{'> >()) is_indented = false; // don't indent if value is empty if (peek_css< static_value >()) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, &parse_static_value()/*, lex()*/); + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); } else { Expression_Obj value; Lookahead lookahead = lookahead_for_value(position); if (lookahead.found) { if (lookahead.has_interpolants) { - value = &parse_value_schema(lookahead.found); + value = parse_value_schema(lookahead.found); } else { - value = &parse_list(DELAYED); + value = parse_list(DELAYED); } } else { - value = &parse_list(DELAYED); - if (List_Ptr list = SASS_MEMORY_CAST(List, value)) { - if (list->length() == 0 && !peek< exactly <'{'> >()) { + value = parse_list(DELAYED); + if (List_Ptr list = Cast(value)) { + if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } } @@ -1003,6 +1026,7 @@ namespace Sass { } // parse +/- and return false if negative + // this is never hit via spec tests bool Parser::parse_number_prefix() { bool positive = true; @@ -1053,7 +1077,50 @@ namespace Sass { ps.offset = pstate - ps + pstate.offset; map->pstate(ps); - return ↦ + return map; + } + + Expression_Obj Parser::parse_bracket_list() + { + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); + } + + bool has_paren = peek_css< exactly<'('> >() != NULL; + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + List_Obj l = Cast(list); + if (!l || l->is_bracketed() || has_paren) { + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); + bracketed_list->append(list); + return bracketed_list; + } + l->is_bracketed(true); + return l; + } + + // if we got so far, we actually do have a comma list + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); + // wrap the first expression + bracketed_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + bracketed_list->append(parse_space_list()); + } + // return the list + return bracketed_list; } // parse list returns either a space separated list, @@ -1069,18 +1136,7 @@ namespace Sass { { // check if we have an empty list // return the empty list as such - if (peek_css< alternatives < - // exactly<'!'>, - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - > >(position)) + if (peek_css< list_terminator >(position)) { // return an empty list (nothing to delay) return SASS_MEMORY_NEW(List, pstate, 0); @@ -1104,23 +1160,13 @@ namespace Sass { while (lex_css< exactly<','> >()) { // check for abort condition - if (peek_css< alternatives < - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - > >(position) + if (peek_css< list_terminator >(position) ) { break; } // otherwise add another expression comma_list->append(parse_space_list()); } // return the list - return &comma_list; + return comma_list; } // EO parse_comma_list @@ -1129,43 +1175,22 @@ namespace Sass { { Expression_Obj disj1 = parse_disjunction(); // if it's a singleton, return it (don't wrap it) - if (peek_css< alternatives < - // exactly<'!'>, - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<','>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - > >(position) - ) { return disj1; } + if (peek_css< space_list_terminator >(position) + ) { + return disj1; } List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); space_list->append(disj1); - while (!(peek_css< alternatives < - // exactly<'!'>, - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<','>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - > >(position)) && peek_css< optional_css_whitespace >() != end + while ( + !(peek_css< space_list_terminator >(position)) && + peek_css< optional_css_whitespace >() != end ) { // the space is parsed implicitly? space_list->append(parse_disjunction()); } // return the list - return &space_list; + return space_list; } // EO parse_space_list @@ -1200,7 +1225,7 @@ namespace Sass { // parse multiple right hand sides std::vector operands; while (lex_css< kwd_and >()) { - operands.push_back(&parse_relation()); + operands.push_back(parse_relation()); } // if it's a singleton, return it directly if (operands.size() == 0) return rel; @@ -1246,7 +1271,7 @@ namespace Sass { // is directly adjacent to expression? bool right_ws = peek < css_comments >() != NULL; operators.push_back({ op, left_ws, right_ws }); - operands.push_back(&parse_expression()); + operands.push_back(parse_expression()); left_ws = peek < css_comments >() != NULL; } // we are called recursively for list, so we first @@ -1297,7 +1322,7 @@ namespace Sass { bool right_ws = peek < css_comments >() != NULL; operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); - operands.push_back(&parse_operators()); + operands.push_back(parse_operators()); left_ws = peek < css_comments >() != NULL; } @@ -1350,58 +1375,66 @@ namespace Sass { // lex the expected closing parenthesis if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate); // expression can be evaluated - return &value; + return value; + } + else if (lex_css< exactly<'['> >()) { + // explicit bracketed + Expression_Obj value = parse_bracket_list(); + // lex the expected closing square bracket + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket", pstate); + return value; } // string may be interpolated // if (lex< quoted_string >()) { // return &parse_string(); // } else if (peek< ie_property >()) { - return &parse_ie_property(); + return parse_ie_property(); } else if (peek< ie_keyword_arg >()) { - return &parse_ie_keyword_arg(); + return parse_ie_keyword_arg(); } else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { - return &parse_calc_function(); + return parse_calc_function(); } else if (lex < functional_schema >()) { - return &parse_function_call_schema(); + return parse_function_call_schema(); } else if (lex< identifier_schema >()) { String_Obj string = parse_identifier_schema(); - if (String_Schema_Ptr schema = SASS_MEMORY_CAST(String_Schema, string)) { + if (String_Schema_Ptr schema = Cast(string)) { if (lex < exactly < '(' > >()) { - schema->append(&parse_list()); + schema->append(parse_list()); lex < exactly < ')' > >(); } } - return &string; + return string; } else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { - return &parse_url_function_string(); + return parse_url_function_string(); } else if (peek< re_functional >()) { - return &parse_function_call(); + return parse_function_call(); } else if (lex< exactly<'+'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, &parse_factor()); + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } else if (lex< exactly<'-'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_factor()); + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } else if (lex< sequence< kwd_not > >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, &parse_factor()); + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } + // this whole branch is never hit via spec tests else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { - if (parse_number_prefix()) return &parse_value(); // prefix is positive - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_value()); + if (parse_number_prefix()) return parse_value(); // prefix is positive + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } @@ -1430,14 +1463,14 @@ namespace Sass { // string may be interpolated if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) - { return &parse_string(); } + { return parse_string(); } if (const char* stop = peek< value_schema >()) - { return &parse_value_schema(stop); } + { return parse_value_schema(stop); } // string may be interpolated if (lex< quoted_string >()) - { return &parse_string(); } + { return parse_string(); } if (lex< kwd_true >()) { return SASS_MEMORY_NEW(Boolean, pstate, true); } @@ -1521,7 +1554,7 @@ namespace Sass { // parse the interpolant and accumulate it Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); interp_node->is_interpolant(true); - schema->append(&interp_node); + schema->append(interp_node); i = j; } else { @@ -1585,7 +1618,7 @@ namespace Sass { // parse the interpolant and accumulate it Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); interp_node->is_interpolant(true); - schema->append(&interp_node); + schema->append(interp_node); i = j; } else { @@ -1614,9 +1647,9 @@ namespace Sass { } lex< exactly<'='> >(); kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (peek< variable >()) kwd_arg->append(&parse_list()); + if (peek< variable >()) kwd_arg->append(parse_list()); else if (lex< number >()) kwd_arg->append(SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, Util::normalize_decimals(lexed))); - else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(&parse_list()); } + else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } return kwd_arg; } @@ -1644,7 +1677,7 @@ namespace Sass { // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); } if ((e = peek< re_functional >()) && e < stop) { - schema->append(&parse_function_call()); + schema->append(parse_function_call()); } // lex an interpolant /#{...}/ else if (lex< exactly < hash_lbrace > >()) { @@ -1674,7 +1707,7 @@ namespace Sass { // need_space = true; // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); // else need_space = true; - schema->append(&parse_string()); + schema->append(parse_string()); if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { // need_space = true; } @@ -1712,7 +1745,7 @@ namespace Sass { } // lex a value in parentheses else if (peek< parenthese_scope >()) { - schema->append(&parse_factor()); + schema->append(parse_factor()); } else { break; @@ -1746,7 +1779,7 @@ namespace Sass { if (i < p) { // accumulate the preceding segment if it's nonempty const char* o = position; position = i; - schema->append(&parse_value_schema(p)); + schema->append(parse_value_schema(p)); position = o; } // we need to skip anything inside strings @@ -1771,7 +1804,7 @@ namespace Sass { else { // no interpolants left; add the last segment if nonempty if (i < end) { const char* o = position; position = i; - schema->append(&parse_value_schema(id.end)); + schema->append(parse_value_schema(id.end)); position = o; } break; @@ -1796,9 +1829,9 @@ namespace Sass { exactly < ')' > > >(); - Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, &parse_interpolated_chunk(Token(arg_beg, arg_end))); + Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); - args->append(&arg); + args->append(arg); return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); } @@ -1818,16 +1851,16 @@ namespace Sass { } std::string uri(""); - if (&url_string) { + if (url_string) { uri = url_string->to_string({ NESTED, 5 }); } - if (String_Schema_Ptr schema = dynamic_cast(&url_string)) { + if (String_Schema_Ptr schema = Cast(url_string)) { String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); res->append(schema); res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); - return &res; + return res; } else { std::string res = prefix + uri + suffix; return SASS_MEMORY_NEW(String_Constant, pstate, res); @@ -1850,7 +1883,7 @@ namespace Sass { pp = sequence< interpolant, real_uri_value >(pp); } position = pp; - return &parse_interpolated_chunk(Token(p, position)); + return parse_interpolated_chunk(Token(p, position)); } else if (uri != "") { std::string res = Util::rtrim(uri); @@ -1897,10 +1930,10 @@ namespace Sass { // we want all other comments to be parsed if (lex_css< elseif_directive >()) { alternative = SASS_MEMORY_NEW(Block, pstate); - alternative->append(&parse_if_directive(true)); + alternative->append(parse_if_directive(true)); } else if (lex_css< kwd_else_directive >()) { - alternative = &parse_block(root); + alternative = parse_block(root); } stack.pop_back(); return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); @@ -1997,9 +2030,9 @@ namespace Sass { media_block->media_queries(parse_media_queries()); Media_Block_Obj prev_media_block = last_media_block; - last_media_block = &media_block; + last_media_block = media_block; media_block->block(parse_css_block()); - last_media_block = &prev_media_block; + last_media_block = prev_media_block; stack.pop_back(); return media_block.detach(); } @@ -2008,8 +2041,8 @@ namespace Sass { { advanceToNextToken(); List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); - if (!peek_css < exactly <'{'> >()) queries->append(&parse_media_query()); - while (lex_css < exactly <','> >()) queries->append(&parse_media_query()); + if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); + while (lex_css < exactly <','> >()) queries->append(parse_media_query()); queries->update_pstate(pstate); return queries.detach(); } @@ -2022,19 +2055,19 @@ namespace Sass { if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } - if (lex < identifier_schema >()) media_query->media_type(&parse_identifier_schema()); - else if (lex < identifier >()) media_query->media_type(&parse_interpolated_chunk(lexed)); - else media_query->append(&parse_media_expression()); + if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); + else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); + else media_query->append(parse_media_expression()); - while (lex_css < kwd_and >()) media_query->append(&parse_media_expression()); + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); if (lex < identifier_schema >()) { String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - schema->append(&media_query->media_type()); + schema->append(media_query->media_type()); schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - schema->append(&parse_identifier_schema()); + schema->append(parse_identifier_schema()); media_query->media_type(schema); } - while (lex_css < kwd_and >()) media_query->append(&parse_media_expression()); + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); media_query->update_pstate(pstate); @@ -2045,7 +2078,7 @@ namespace Sass { { if (lex < identifier_schema >()) { String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, &ss, 0, true); + return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); } if (!lex_css< exactly<'('> >()) { error("media query expression must begin with '('", pstate); @@ -2054,10 +2087,10 @@ namespace Sass { if (peek_css< exactly<')'> >()) { error("media feature required in media query expression", pstate); } - feature = &parse_expression(); + feature = parse_expression(); Expression_Obj expression = 0; if (lex_css< exactly<':'> >()) { - expression = &parse_list(DELAYED); + expression = parse_list(DELAYED); } if (!lex_css< exactly<')'> >()) { error("unclosed parenthesis in media query expression", pstate); @@ -2095,13 +2128,13 @@ namespace Sass { { if (!lex < kwd_not >()) return 0; Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - return SASS_MEMORY_NEW(Supports_Negation, pstate, &cond); + return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); } Supports_Condition_Obj Parser::parse_supports_operator() { Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - if (!&cond) return 0; + if (cond.isNull()) return 0; while (true) { Supports_Operator::Operand op = Supports_Operator::OR; @@ -2112,7 +2145,7 @@ namespace Sass { Supports_Condition_Obj right = parse_supports_condition_in_parens(); // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); - cond = SASS_MEMORY_NEW(Supports_Operator, pstate, &cond, &right, op); + cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); } return cond; } @@ -2124,7 +2157,7 @@ namespace Sass { String_Obj interp = parse_interpolated_chunk(lexed); if (!interp) return 0; - return SASS_MEMORY_NEW(Supports_Interpolation, pstate, &interp); + return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); } // TODO: This needs some major work. Although feature conditions @@ -2137,7 +2170,7 @@ namespace Sass { if (!declaration) error("@supports condition expected declaration", pstate); cond = SASS_MEMORY_NEW(Supports_Declaration, declaration->pstate(), - &declaration->property(), + declaration->property(), declaration->value()); // ToDo: maybe we need an additional error condition? return cond; @@ -2146,13 +2179,13 @@ namespace Sass { Supports_Condition_Obj Parser::parse_supports_condition_in_parens() { Supports_Condition_Obj interp = parse_supports_interpolation(); - if (&interp != 0) return interp; + if (interp != 0) return interp; if (!lex < exactly <'('> >()) return 0; lex < css_whitespace >(); Supports_Condition_Obj cond = parse_supports_condition(); - if (&cond != 0) { + if (cond != 0) { if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); } else { cond = parse_supports_declaration(); @@ -2168,21 +2201,20 @@ namespace Sass { Block_Obj body = 0; At_Root_Query_Obj expr; Lookahead lookahead_result; - LOCAL_FLAG(in_at_root, true); if (lex_css< exactly<'('> >()) { expr = parse_at_root_query(); } if (peek_css < exactly<'{'> >()) { lex (); - body = &parse_block(true); + body = parse_block(true); } else if ((lookahead_result = lookahead_for_selector(position)).found) { - Ruleset_Obj r = parse_ruleset(lookahead_result, false); + Ruleset_Obj r = parse_ruleset(lookahead_result); body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); - body->append(&r); + body->append(r); } At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); - if (&expr) at_root->expression(&expr); + if (!expr.isNull()) at_root->expression(expr); return at_root; } @@ -2200,14 +2232,14 @@ namespace Sass { List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); if (expression->concrete_type() == Expression::LIST) { - value = SASS_MEMORY_CAST(List, expression); + value = Cast(expression); } else value->append(expression); At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, value->pstate(), feature, - &value); + value); if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression", pstate); return cond; } @@ -2218,16 +2250,18 @@ namespace Sass { if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + // this whole branch is never hit via spec tests + Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); Lookahead lookahead = lookahead_for_include(position); if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(&parse_selector_list(true)); + at_rule->selector(parse_selector_list(false)); } lex < css_comments >(false); if (lex < static_property >()) { - at_rule->value(&parse_interpolated_chunk(Token(lexed))); + at_rule->value(parse_interpolated_chunk(Token(lexed))); } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { at_rule->value(parse_list()); } @@ -2241,6 +2275,7 @@ namespace Sass { return at_rule; } + // this whole branch is never hit via spec tests Directive_Obj Parser::parse_prefixed_directive() { std::string kwd(lexed); @@ -2250,15 +2285,15 @@ namespace Sass { Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); Lookahead lookahead = lookahead_for_include(position); if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(&parse_selector_list(true)); + at_rule->selector(parse_selector_list(false)); } lex < css_comments >(false); if (lex < static_property >()) { - at_rule->value(&parse_interpolated_chunk(Token(lexed))); + at_rule->value(parse_interpolated_chunk(Token(lexed))); } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(&parse_list()); + at_rule->value(parse_list()); } lex < css_comments >(false); @@ -2276,7 +2311,7 @@ namespace Sass { Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); String_Schema_Obj val = parse_almost_any_value(); // strip left and right if they are of type string - directive->value(&val); + directive->value(val); if (peek< exactly<'{'> >()) { directive->block(parse_block()); } @@ -2286,7 +2321,7 @@ namespace Sass { Expression_Obj Parser::lex_interpolation() { if (lex < interpolant >(true) != NULL) { - return &parse_interpolated_chunk(lexed, true); + return parse_interpolated_chunk(lexed, true); } return 0; } @@ -2361,12 +2396,12 @@ namespace Sass { { Expression_Obj rv = 0; if (*position == 0) return 0; - if ((rv = &lex_almost_any_value_chars())) return rv; + if ((rv = lex_almost_any_value_chars())) return rv; // if ((rv = lex_block_comment())) return rv; // if ((rv = lex_single_line_comment())) return rv; - if ((rv = &lex_interp_string())) return rv; - if ((rv = &lex_interp_uri())) return rv; - if ((rv = &lex_interpolation())) return rv; + if ((rv = lex_interp_string())) return rv; + if ((rv = lex_interp_uri())) return rv; + if ((rv = lex_interpolation())) return rv; return rv; } @@ -2384,7 +2419,7 @@ namespace Sass { return schema.detach(); } - while ((token = &lex_almost_any_value_token())) { + while ((token = lex_almost_any_value_token())) { schema->append(token); } @@ -2436,7 +2471,7 @@ namespace Sass { // check that we do not have an empty list (ToDo: check if we got all cases) if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - return SASS_MEMORY_NEW(Return, pstate, &parse_list()); + return SASS_MEMORY_NEW(Return, pstate, parse_list()); } Lookahead Parser::lookahead_for_selector(const char* start) @@ -2469,6 +2504,7 @@ namespace Sass { // check expected opening bracket // only after successfull matching if (peek < exactly<'{'> >(q)) rv.found = q; + // else if (peek < end_of_file >(q)) rv.found = q; else if (peek < exactly<'('> >(q)) rv.found = q; // else if (peek < exactly<';'> >(q)) rv.found = q; // else if (peek < exactly<'}'> >(q)) rv.found = q; @@ -2538,6 +2574,7 @@ namespace Sass { sequence < // optional_spaces, alternatives < + // end_of_file, exactly<'{'>, exactly<'}'>, exactly<';'> @@ -2641,14 +2678,14 @@ namespace Sass { Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) { for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); } return base; } Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) { - if (String_Schema_Ptr schema = dynamic_cast(&base)) { + if (String_Schema_Ptr schema = Cast(base)) { // return schema; if (schema->has_interpolants()) { if (i + 1 < operands.size() && ( @@ -2663,7 +2700,7 @@ namespace Sass { || (ops[0].operand == Sass_OP::GTE) )) { Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, &rhs); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); return rhs; } // return schema; @@ -2671,31 +2708,32 @@ namespace Sass { } for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema_Ptr schema = dynamic_cast(&operands[i])) { + if (String_Schema_Ptr schema = Cast(operands[i])) { if (schema->has_interpolants()) { if (i + 1 < S) { + // this whole branch is never hit via spec tests Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, &rhs); - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, &rhs); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); return base; } - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); return base; } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); } } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); } - Binary_Expression_Ptr b = static_cast(&base); + Binary_Expression_Ptr b = Cast(base.ptr()); if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { base->is_delayed(true); } } // nested binary expression are never to be delayed - if (Binary_Expression_Ptr b = dynamic_cast(&base)) { - if (SASS_MEMORY_CAST(Binary_Expression, b->left())) base->set_delayed(false); - if (SASS_MEMORY_CAST(Binary_Expression, b->right())) base->set_delayed(false); + if (Binary_Expression_Ptr b = Cast(base)) { + if (Cast(b->left())) base->set_delayed(false); + if (Cast(b->right())) base->set_delayed(false); } return base; } @@ -2727,8 +2765,8 @@ namespace Sass { const char* pos_left(last_pos); const char* end_left(last_pos); - utf8::next(pos_left, end); - utf8::next(end_left, end); + if (*pos_left) utf8::next(pos_left, end); + if (*end_left) utf8::next(end_left, end); while (pos_left > source) { if (utf8::distance(pos_left, end_left) >= max_len) { utf8::prior(pos_left, source); diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp index f4be97826..263b4e1e2 100755 --- a/src/libsass/src/parser.hpp +++ b/src/libsass/src/parser.hpp @@ -39,12 +39,11 @@ namespace Sass { Token lexed; - bool in_at_root; Parser(Context& ctx, const ParserState& pstate) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0) - { in_at_root = false; stack.push_back(Scope::Root); } + { stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); @@ -240,10 +239,10 @@ namespace Sass { Arguments_Obj parse_arguments(); Argument_Obj parse_argument(); Assignment_Obj parse_assignment(); - Ruleset_Obj parse_ruleset(Lookahead lookahead, bool is_root = false); - Selector_Schema_Obj parse_selector_schema(const char* end_of_selector); - Selector_List_Obj parse_selector_list(bool at_root = false); - Complex_Selector_Obj parse_complex_selector(bool in_root = true); + Ruleset_Obj parse_ruleset(Lookahead lookahead); + Selector_List_Obj parse_selector_list(bool chroot); + Complex_Selector_Obj parse_complex_selector(bool chroot); + Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); Compound_Selector_Obj parse_compound_selector(); Simple_Selector_Obj parse_simple_selector(); Wrapped_Selector_Obj parse_negated_selector(); @@ -257,6 +256,7 @@ namespace Sass { bool parse_number_prefix(); Declaration_Obj parse_declaration(); Expression_Obj parse_map(); + Expression_Obj parse_bracket_list(); Expression_Obj parse_list(bool delayed = false); Expression_Obj parse_comma_list(bool delayed = false); Expression_Obj parse_space_list(); @@ -340,15 +340,15 @@ namespace Sass { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (position[0] == '#' && position[1] == '{') { Expression_Obj itpl = lex_interpolation(); - if (&itpl) schema->append(&itpl); + if (!itpl.isNull()) schema->append(itpl); while (lex < close >(false)) { // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (position[0] == '#' && position[1] == '{') { Expression_Obj itpl = lex_interpolation(); - if (&itpl) schema->append(&itpl); + if (!itpl.isNull()) schema->append(itpl); } else { - return &schema; + return schema; } } } else { diff --git a/src/libsass/src/plugins.cpp b/src/libsass/src/plugins.cpp index bdd61a905..eecba7880 100755 --- a/src/libsass/src/plugins.cpp +++ b/src/libsass/src/plugins.cpp @@ -1,3 +1,8 @@ +#include "sass.hpp" +#include +#include "output.hpp" +#include "plugins.hpp" + #ifdef _WIN32 #include #else @@ -7,15 +12,21 @@ #include #endif -#include "sass.hpp" -#include -#include "output.hpp" -#include "plugins.hpp" - namespace Sass { Plugins::Plugins(void) { } - Plugins::~Plugins(void) { } + Plugins::~Plugins(void) + { + for (auto function : functions) { + sass_delete_function(function); + } + for (auto importer : importers) { + sass_delete_importer(importer); + } + for (auto header : headers) { + sass_delete_importer(header); + } + } // check if plugin is compatible with this version // plugins may be linked static against libsass @@ -57,20 +68,23 @@ namespace Sass { // try to get import address for "libsass_load_functions" if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) { - Sass_Function_List fns = plugin_load_functions(); + Sass_Function_List fns = plugin_load_functions(), _p = fns; while (fns && *fns) { functions.push_back(*fns); ++ fns; } + sass_free_memory(_p); // only delete the container, items not yet } // try to get import address for "libsass_load_importers" if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers")) { - Sass_Importer_List imps = plugin_load_importers(); + Sass_Importer_List imps = plugin_load_importers(), _p = imps; while (imps && *imps) { importers.push_back(*imps); ++ imps; } + sass_free_memory(_p); // only delete the container, items not yet } // try to get import address for "libsass_load_headers" if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers")) { - Sass_Importer_List imps = plugin_load_headers(); + Sass_Importer_List imps = plugin_load_headers(), _p = imps; while (imps && *imps) { headers.push_back(*imps); ++ imps; } + sass_free_memory(_p); // only delete the container, items not yet } // success return true; @@ -153,7 +167,11 @@ namespace Sass { struct dirent *dirp; if((dp = opendir(path.c_str())) == NULL) return -1; while ((dirp = readdir(dp)) != NULL) { - if (!ends_with(dirp->d_name, ".so")) continue; + #if __APPLE__ + if (!ends_with(dirp->d_name, ".dylib")) continue; + #else + if (!ends_with(dirp->d_name, ".so")) continue; + #endif if (load_plugin(path + dirp->d_name)) ++ loaded; } closedir(dp); diff --git a/src/libsass/src/position.hpp b/src/libsass/src/position.hpp index e4306d5a8..1aeb5d15a 100755 --- a/src/libsass/src/position.hpp +++ b/src/libsass/src/position.hpp @@ -85,7 +85,7 @@ namespace Sass { size_t length() const { return end - begin; } std::string ws_before() const { return std::string(prefix, begin); } - std::string to_string() const { return std::string(begin, end); } + const std::string to_string() const { return std::string(begin, end); } std::string time_wspace() const { std::string str(to_string()); std::string whitespaces(" \t\f\v\n\r"); diff --git a/src/libsass/src/prelexer.cpp b/src/libsass/src/prelexer.cpp index cab812bba..f702513aa 100755 --- a/src/libsass/src/prelexer.cpp +++ b/src/libsass/src/prelexer.cpp @@ -1,6 +1,5 @@ #include "sass.hpp" #include -#include #include #include #include "util.hpp" @@ -1420,6 +1419,28 @@ namespace Sass { >(src); } + const char* list_terminator(const char* src) { + return alternatives < + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<']'>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + >(src); + }; + + const char* space_list_terminator(const char* src) { + return alternatives < + exactly<','>, + list_terminator + >(src); + }; + // const char* real_uri_prefix(const char* src) { // return alternatives< diff --git a/src/libsass/src/prelexer.hpp b/src/libsass/src/prelexer.hpp index 69c404323..7cac454a1 100755 --- a/src/libsass/src/prelexer.hpp +++ b/src/libsass/src/prelexer.hpp @@ -355,6 +355,10 @@ namespace Sass { const char* ie_keyword_arg_value(const char* src); const char* ie_keyword_arg_property(const char* src); + // characters that terminate parsing of a list + const char* list_terminator(const char* src); + const char* space_list_terminator(const char* src); + // match url() const char* H(const char* src); const char* W(const char* src); diff --git a/src/libsass/src/remove_placeholders.cpp b/src/libsass/src/remove_placeholders.cpp index 0fce4ed7e..7402a9251 100755 --- a/src/libsass/src/remove_placeholders.cpp +++ b/src/libsass/src/remove_placeholders.cpp @@ -6,13 +6,12 @@ namespace Sass { - Remove_Placeholders::Remove_Placeholders(Context& ctx) - : ctx(ctx) + Remove_Placeholders::Remove_Placeholders() { } void Remove_Placeholders::operator()(Block_Ptr b) { for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr st = &b->at(i); + Statement_Ptr st = b->at(i); st->perform(this); } } @@ -34,18 +33,18 @@ namespace Sass { void Remove_Placeholders::operator()(Ruleset_Ptr r) { // Create a new selector group without placeholders - Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, r->selector()); + Selector_List_Obj sl = Cast(r->selector()); if (sl) { // Set the new placeholder selector list - r->selector(remove_placeholders(&sl)); + r->selector(remove_placeholders(sl)); // Remove placeholders in wrapped selectors for (Complex_Selector_Obj cs : sl->elements()) { while (cs) { if (cs->head()) { for (Simple_Selector_Obj& ss : cs->head()->elements()) { - if (Wrapped_Selector_Ptr ws = SASS_MEMORY_CAST(Wrapped_Selector, ss)) { - if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) { + if (Wrapped_Selector_Ptr ws = Cast(ss)) { + if (Selector_List_Ptr sl = Cast(ws->selector())) { Selector_List_Ptr clean = remove_placeholders(sl); // also clean superflous parent selectors // probably not really the correct place @@ -72,10 +71,10 @@ namespace Sass { } void Remove_Placeholders::operator()(Media_Block_Ptr m) { - operator()(&m->block()); + operator()(m->block()); } void Remove_Placeholders::operator()(Supports_Block_Ptr m) { - operator()(&m->block()); + operator()(m->block()); } void Remove_Placeholders::operator()(Directive_Ptr a) { diff --git a/src/libsass/src/remove_placeholders.hpp b/src/libsass/src/remove_placeholders.hpp index a3f81dffc..c13b63134 100755 --- a/src/libsass/src/remove_placeholders.hpp +++ b/src/libsass/src/remove_placeholders.hpp @@ -9,19 +9,15 @@ namespace Sass { - class Context; - class Remove_Placeholders : public Operation_CRTP { - Context& ctx; - void fallback_impl(AST_Node_Ptr n) {} public: Selector_List_Ptr remove_placeholders(Selector_List_Ptr); public: - Remove_Placeholders(Context&); + Remove_Placeholders(); ~Remove_Placeholders() { } void operator()(Block_Ptr); diff --git a/src/libsass/src/sass.cpp b/src/libsass/src/sass.cpp index 3f6af2703..98e349f48 100755 --- a/src/libsass/src/sass.cpp +++ b/src/libsass/src/sass.cpp @@ -7,6 +7,23 @@ #include "sass.h" #include "file.hpp" #include "util.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // helper to convert string list to vector + std::vector list2vec(struct string_list* cur) + { + std::vector list; + while (cur) { + list.push_back(cur->string); + cur = cur->next; + } + return list; + } + +} extern "C" { using namespace Sass; @@ -49,11 +66,50 @@ extern "C" { return sass_copy_c_string(unquoted.c_str()); } + char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_include(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_file(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + // Make sure to free the returned value! // Incs array has to be null terminated! - char* ADDCALL sass_resolve_file (const char* file, const char* paths[]) + // this has the original resolve logic for sass include + char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) { - std::string resolved(File::find_file(file, paths)); + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_include(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_file(file, vec)); return sass_copy_c_string(resolved.c_str()); } diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp index 8cf7e2cf7..82dc2152f 100755 --- a/src/libsass/src/sass_context.cpp +++ b/src/libsass/src/sass_context.cpp @@ -272,10 +272,14 @@ extern "C" { #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } - #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } \ + #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } + #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } + #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } @@ -468,7 +472,7 @@ extern "C" { if (compiler->c_ctx->error_status) return compiler->c_ctx->error_status; // parse the context we have set up (file or data) - compiler->root = &sass_parse_block(compiler); + compiler->root = sass_parse_block(compiler); // success return 0; } @@ -674,6 +678,10 @@ extern "C" { size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } + // Getters for Sass_Compiler options (query function stack) + size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } // Calculate the size of the stored null terminated array size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) @@ -693,10 +701,10 @@ extern "C" { IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, plugin_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, include_path, 0); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); @@ -740,6 +748,23 @@ extern "C" { } + // Push function for include paths (no manipulation support for now) + size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) + { + size_t len = 0; + struct string_list* cur = options->include_paths; + while (cur) { len ++; cur = cur->next; } + return len; + } + + // Push function for include paths (no manipulation support for now) + const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) + { + struct string_list* cur = options->include_paths; + while (i) { i--; cur = cur->next; } + return cur->string; + } + // Push function for plugin paths (no manipulation support for now) void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) { diff --git a/src/libsass/src/sass_context.hpp b/src/libsass/src/sass_context.hpp index 3a78d3ad8..8ae1fb12c 100755 --- a/src/libsass/src/sass_context.hpp +++ b/src/libsass/src/sass_context.hpp @@ -1,9 +1,8 @@ #ifndef SASS_SASS_CONTEXT_H #define SASS_SASS_CONTEXT_H -#include "sass.h" -#include "sass.hpp" -#include "context.hpp" +#include "sass/base.h" +#include "sass/context.h" #include "ast_fwd_decl.hpp" // sass config options structure @@ -33,7 +32,7 @@ struct Sass_Options : Sass_Output_Options { char* input_path; // The output path is used for source map - // generation. Libsass will not write to + // generation. LibSass will not write to // this file, it is just used to create // information in source-maps etc. char* output_path; diff --git a/src/libsass/src/sass_functions.cpp b/src/libsass/src/sass_functions.cpp index db153d603..f7beda35f 100755 --- a/src/libsass/src/sass_functions.cpp +++ b/src/libsass/src/sass_functions.cpp @@ -2,6 +2,7 @@ #include #include "util.hpp" #include "context.hpp" +#include "values.hpp" #include "sass/functions.h" #include "sass_functions.hpp" @@ -17,12 +18,30 @@ extern "C" { { Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); if (cb == 0) return 0; - cb->signature = signature; + cb->signature = strdup(signature); cb->function = function; cb->cookie = cookie; return cb; } + void ADDCALL sass_delete_function(Sass_Function_Entry entry) + { + free(entry->signature); + free(entry); + } + + // Deallocator for the allocated memory + void ADDCALL sass_delete_function_list(Sass_Function_List list) + { + Sass_Function_List it = list; + if (list == 0) return; + while(*list) { + sass_delete_function(*list); + ++list; + } + free(it); + } + // Setters and getters for callbacks on function lists Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; } void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; } @@ -57,6 +76,18 @@ extern "C" { return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry)); } + // Deallocator for the allocated memory + void ADDCALL sass_delete_importer_list(Sass_Importer_List list) + { + Sass_Importer_List it = list; + if (list == 0) return; + while(*list) { + sass_delete_importer(*list); + ++list; + } + free(it); + } + Sass_Importer_Entry ADDCALL sass_importer_get_list_entry(Sass_Importer_List list, size_t idx) { return list[idx]; } void ADDCALL sass_importer_set_list_entry(Sass_Importer_List list, size_t idx, Sass_Importer_Entry cb) { list[idx] = cb; } @@ -126,6 +157,37 @@ extern "C" { free(import); } + // Getter for callee entry + const char* ADDCALL sass_callee_get_name(Sass_Callee_Entry entry) { return entry->name; } + const char* ADDCALL sass_callee_get_path(Sass_Callee_Entry entry) { return entry->path; } + size_t ADDCALL sass_callee_get_line(Sass_Callee_Entry entry) { return entry->line; } + size_t ADDCALL sass_callee_get_column(Sass_Callee_Entry entry) { return entry->column; } + enum Sass_Callee_Type ADDCALL sass_callee_get_type(Sass_Callee_Entry entry) { return entry->type; } + Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry entry) { return &entry->env; } + + // Getters and Setters for environments (lexical, local and global) + union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame env, const char* name) { + Expression_Ptr ex = Cast((*env->frame)[name]); + return ex != NULL ? ast_node_to_sass_value(ex) : NULL; + } + void ADDCALL sass_env_set_lexical (Sass_Env_Frame env, const char* name, union Sass_Value* val) { + (*env->frame)[name] = sass_value_to_ast_node(val); + } + union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame env, const char* name) { + Expression_Ptr ex = Cast(env->frame->get_local(name)); + return ex != NULL ? ast_node_to_sass_value(ex) : NULL; + } + void ADDCALL sass_env_set_local (Sass_Env_Frame env, const char* name, union Sass_Value* val) { + env->frame->set_local(name, sass_value_to_ast_node(val)); + } + union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame env, const char* name) { + Expression_Ptr ex = Cast(env->frame->get_global(name)); + return ex != NULL ? ast_node_to_sass_value(ex) : NULL; + } + void ADDCALL sass_env_set_global (Sass_Env_Frame env, const char* name, union Sass_Value* val) { + env->frame->set_global(name, sass_value_to_ast_node(val)); + } + // Getter for import entry const char* ADDCALL sass_import_get_imp_path(Sass_Import_Entry entry) { return entry->imp_path; } const char* ADDCALL sass_import_get_abs_path(Sass_Import_Entry entry) { return entry->abs_path; } diff --git a/src/libsass/src/sass_functions.hpp b/src/libsass/src/sass_functions.hpp index 5a7865d77..3b646d67e 100755 --- a/src/libsass/src/sass_functions.hpp +++ b/src/libsass/src/sass_functions.hpp @@ -2,10 +2,12 @@ #define SASS_SASS_FUNCTIONS_H #include "sass.h" +#include "environment.hpp" +#include "functions.hpp" // Struct to hold custom function callback struct Sass_Function { - const char* signature; + char* signature; Sass_Function_Fn function; void* cookie; }; @@ -22,6 +24,22 @@ struct Sass_Import { size_t column; }; +// External environments +struct Sass_Env { + // links to parent frames + Sass::Env* frame; +}; + +// External call entry +struct Sass_Callee { + const char* name; + const char* path; + size_t line; + size_t column; + enum Sass_Callee_Type type; + struct Sass_Env env; +}; + // Struct to hold importer callback struct Sass_Importer { Sass_Importer_Fn importer; diff --git a/src/libsass/src/sass_util.hpp b/src/libsass/src/sass_util.hpp index ca2819aaa..ef72dff27 100755 --- a/src/libsass/src/sass_util.hpp +++ b/src/libsass/src/sass_util.hpp @@ -40,7 +40,7 @@ namespace Sass { bool operator()(const Node& one, const Node& two, Node& out) const { // TODO: Is this the correct C++ interpretation? // block ||= proc {|a, b| a == b && a} - if (nodesEqual(one, two, true)) { + if (one == two) { out = one; return true; } diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp index efdd8dd62..adf41d573 100755 --- a/src/libsass/src/sass_values.cpp +++ b/src/libsass/src/sass_values.cpp @@ -54,6 +54,8 @@ extern "C" { size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; } enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; } void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; } + bool ADDCALL sass_list_get_is_bracketed(const union Sass_Value* v) { return v->list.is_bracketed; } + void ADDCALL sass_list_set_is_bracketed(union Sass_Value* v, bool is_bracketed) { v->list.is_bracketed = is_bracketed; } // Getters and setters for Sass_List values union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; } void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; } @@ -130,13 +132,14 @@ extern "C" { return v; } - union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep) + union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep, bool is_bracketed) { union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); if (v == 0) return 0; v->list.tag = SASS_LIST; v->list.length = len; v->list.separator = sep; + v->list.is_bracketed = is_bracketed; v->list.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value*)); if (v->list.values == 0) { free(v); return 0; } return v; @@ -247,7 +250,7 @@ extern "C" { return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); } break; case SASS_LIST: { - union Sass_Value* list = sass_make_list(val->list.length, val->list.separator); + union Sass_Value* list = sass_make_list(val->list.length, val->list.separator, val->list.is_bracketed); for (i = 0; i < list->list.length; i++) { list->list.values[i] = sass_clone_value(val->list.values[i]); } @@ -294,40 +297,40 @@ extern "C" { // see if it's a relational expression switch(op) { - case Sass_OP::EQ: return sass_make_boolean(Eval::eq(&lhs, &rhs)); - case Sass_OP::NEQ: return sass_make_boolean(!Eval::eq(&lhs, &rhs)); - case Sass_OP::GT: return sass_make_boolean(!Eval::lt(&lhs, &rhs, "gt") && !Eval::eq(&lhs, &rhs)); - case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(&lhs, &rhs, "gte")); - case Sass_OP::LT: return sass_make_boolean(Eval::lt(&lhs, &rhs, "lt")); - case Sass_OP::LTE: return sass_make_boolean(Eval::lt(&lhs, &rhs, "lte") || Eval::eq(&lhs, &rhs)); - case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? &lhs : &rhs); - case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? &rhs : &lhs); + case Sass_OP::EQ: return sass_make_boolean(Eval::eq(lhs, rhs)); + case Sass_OP::NEQ: return sass_make_boolean(!Eval::eq(lhs, rhs)); + case Sass_OP::GT: return sass_make_boolean(!Eval::lt(lhs, rhs, "gt") && !Eval::eq(lhs, rhs)); + case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(lhs, rhs, "gte")); + case Sass_OP::LT: return sass_make_boolean(Eval::lt(lhs, rhs, "lt")); + case Sass_OP::LTE: return sass_make_boolean(Eval::lt(lhs, rhs, "lte") || Eval::eq(lhs, rhs)); + case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); + case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); default: break; } if (sass_value_is_number(a) && sass_value_is_number(b)) { - Number_Ptr_Const l_n = SASS_MEMORY_CAST(Number, lhs); - Number_Ptr_Const r_n = SASS_MEMORY_CAST(Number, rhs); + Number_Ptr_Const l_n = Cast(lhs); + Number_Ptr_Const r_n = Cast(rhs); rv = Eval::op_numbers(op, *l_n, *r_n, options); } else if (sass_value_is_number(a) && sass_value_is_color(a)) { - Number_Ptr_Const l_n = SASS_MEMORY_CAST(Number, lhs); - Color_Ptr_Const r_c = SASS_MEMORY_CAST(Color, rhs); + Number_Ptr_Const l_n = Cast(lhs); + Color_Ptr_Const r_c = Cast(rhs); rv = Eval::op_number_color(op, *l_n, *r_c, options); } else if (sass_value_is_color(a) && sass_value_is_number(b)) { - Color_Ptr_Const l_c = SASS_MEMORY_CAST(Color, lhs); - Number_Ptr_Const r_n = SASS_MEMORY_CAST(Number, rhs); + Color_Ptr_Const l_c = Cast(lhs); + Number_Ptr_Const r_n = Cast(rhs); rv = Eval::op_color_number(op, *l_c, *r_n, options); } else if (sass_value_is_color(a) && sass_value_is_color(b)) { - Color_Ptr_Const l_c = SASS_MEMORY_CAST(Color, lhs); - Color_Ptr_Const r_c = SASS_MEMORY_CAST(Color, rhs); + Color_Ptr_Const l_c = Cast(lhs); + Color_Ptr_Const r_c = Cast(rhs); rv = Eval::op_colors(op, *l_c, *r_c, options); } else /* convert other stuff to string and apply operation */ { - Value_Ptr l_v = SASS_MEMORY_CAST(Value, lhs); - Value_Ptr r_v = SASS_MEMORY_CAST(Value, rhs); + Value_Ptr l_v = Cast(lhs); + Value_Ptr r_v = Cast(rhs); rv = Eval::op_strings(op, *l_v, *r_v, options); } diff --git a/src/libsass/src/sass_values.hpp b/src/libsass/src/sass_values.hpp index b9e9ebfcc..9aa5cdb33 100755 --- a/src/libsass/src/sass_values.hpp +++ b/src/libsass/src/sass_values.hpp @@ -35,6 +35,7 @@ struct Sass_String { struct Sass_List { enum Sass_Tag tag; enum Sass_Separator separator; + bool is_bracketed; size_t length; // null terminated "array" union Sass_Value** values; @@ -78,4 +79,4 @@ struct Sass_MapPair { union Sass_Value* value; }; -#endif \ No newline at end of file +#endif diff --git a/src/libsass/src/source_map.cpp b/src/libsass/src/source_map.cpp index 0f496f6fb..0e408b65d 100755 --- a/src/libsass/src/source_map.cpp +++ b/src/libsass/src/source_map.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "ast.hpp" diff --git a/src/libsass/src/subset_map.cpp b/src/libsass/src/subset_map.cpp index 766073df8..24513e498 100755 --- a/src/libsass/src/subset_map.cpp +++ b/src/libsass/src/subset_map.cpp @@ -4,22 +4,20 @@ namespace Sass { - void Subset_Map::put(const Compound_Selector_Obj& sel, const Subset_Map_Val& value) + void Subset_Map::put(const Compound_Selector_Obj& sel, const SubSetMapPair& value) { if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty"); size_t index = values_.size(); values_.push_back(value); for (size_t i = 0, S = sel->length(); i < S; ++i) { - hash_[(*sel)[i]].push_back(std::make_pair(&sel, index)); + hash_[(*sel)[i]].push_back(std::make_pair(sel, index)); } } - std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) + std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) { - // std::vector s = sel->to_str_vec(); - // std::set dict(s.begin(), s.end()); - std::unordered_set dict(sel->begin(), sel->end()); + SimpleSelectorDict dict(sel->begin(), sel->end()); // XXX Set std::vector indices; for (size_t i = 0, S = sel->length(); i < S; ++i) { if (!hash_.count((*sel)[i])) { @@ -42,14 +40,14 @@ namespace Sass { std::vector::iterator indices_end = unique(indices.begin(), indices.end()); indices.resize(distance(indices.begin(), indices_end)); - std::vector results; + std::vector results; for (size_t i = 0, S = indices.size(); i < S; ++i) { results.push_back(values_[indices[i]]); } return results; } - std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) + std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) { return get_kv(sel); } diff --git a/src/libsass/src/subset_map.hpp b/src/libsass/src/subset_map.hpp index 58916bccf..5c091e685 100755 --- a/src/libsass/src/subset_map.hpp +++ b/src/libsass/src/subset_map.hpp @@ -60,15 +60,15 @@ namespace Sass { class Subset_Map { private: - std::vector values_; - std::map > > hash_; + std::vector values_; + std::map >, OrderNodes > hash_; public: - void put(const Compound_Selector_Obj& sel, const Subset_Map_Val& value); - std::vector get_kv(const Compound_Selector_Obj& s); - std::vector get_v(const Compound_Selector_Obj& s); + void put(const Compound_Selector_Obj& sel, const SubSetMapPair& value); + std::vector get_kv(const Compound_Selector_Obj& s); + std::vector get_v(const Compound_Selector_Obj& s); bool empty() { return values_.empty(); } void clear() { values_.clear(); hash_.clear(); } - const std::vector values(void) { return values_; } + const std::vector values(void) { return values_; } }; } diff --git a/src/libsass/src/to_c.cpp b/src/libsass/src/to_c.cpp index a39e3e76a..8a6ea8d51 100755 --- a/src/libsass/src/to_c.cpp +++ b/src/libsass/src/to_c.cpp @@ -36,7 +36,7 @@ namespace Sass { union Sass_Value* To_C::operator()(List_Ptr l) { - union Sass_Value* v = sass_make_list(l->length(), l->separator()); + union Sass_Value* v = sass_make_list(l->length(), l->separator(), l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { sass_list_set_value(v, i, (*l)[i]->perform(this)); } @@ -57,7 +57,7 @@ namespace Sass { union Sass_Value* To_C::operator()(Arguments_Ptr a) { - union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA); + union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA, false); for (size_t i = 0, L = a->length(); i < L; ++i) { sass_list_set_value(v, i, (*a)[i]->perform(this)); } diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp index 5e598131e..4b1636e1c 100755 --- a/src/libsass/src/util.cpp +++ b/src/libsass/src/util.cpp @@ -445,7 +445,7 @@ namespace Sass { Block_Obj b = r->block(); - Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, r->selector()); + Selector_List_Ptr sl = Cast(r->selector()); bool hasSelectors = sl ? sl->length() > 0 : false; if (!hasSelectors) { @@ -456,16 +456,16 @@ namespace Sass { bool hasPrintableChildBlocks = false; for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm)) { + if (Cast(stm)) { return true; - } else if (Declaration_Ptr d = dynamic_cast(&stm)) { + } else if (Declaration_Ptr d = Cast(stm)) { return isPrintable(d, style); - } else if (dynamic_cast(&stm)) { - Block_Obj pChildBlock = ((Has_Block_Ptr)&stm)->block(); - if (isPrintable(&pChildBlock, style)) { + } else if (Has_Block_Ptr p = Cast(stm)) { + Block_Obj pChildBlock = p->block(); + if (isPrintable(pChildBlock, style)) { hasPrintableChildBlocks = true; } - } else if (Comment_Ptr c = dynamic_cast(&stm)) { + } else if (Comment_Ptr c = Cast(stm)) { // keep for uncompressed if (style != COMPRESSED) { hasDeclarations = true; @@ -499,8 +499,8 @@ namespace Sass { bool isPrintable(Declaration_Ptr d, Sass_Output_Style style) { Expression_Obj val = d->value(); - if (String_Quoted_Obj sq = SASS_MEMORY_CAST(String_Quoted, val)) return isPrintable(&sq, style); - if (String_Constant_Obj sc = SASS_MEMORY_CAST(String_Constant, val)) return isPrintable(&sc, style); + if (String_Quoted_Obj sq = Cast(val)) return isPrintable(sq.ptr(), style); + if (String_Constant_Obj sc = Cast(val)) return isPrintable(sc.ptr(), style); return true; } @@ -511,18 +511,16 @@ namespace Sass { Block_Obj b = f->block(); -// bool hasSelectors = f->selector() && static_cast(f->selector())->length() > 0; - bool hasDeclarations = false; bool hasPrintableChildBlocks = false; for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm) || dynamic_cast(&stm)) { + if (Cast(stm) || Cast(stm)) { hasDeclarations = true; } - else if (dynamic_cast(&stm)) { - Block_Obj pChildBlock = ((Has_Block_Ptr)&stm)->block(); - if (isPrintable(&pChildBlock, style)) { + else if (Has_Block_Ptr b = Cast(stm)) { + Block_Obj pChildBlock = b->block(); + if (isPrintable(pChildBlock, style)) { hasPrintableChildBlocks = true; } } @@ -542,34 +540,32 @@ namespace Sass { if (b == 0) return false; for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm)) return true; - else if (dynamic_cast(&stm)) return true; - else if (dynamic_cast(&stm)) { - Comment_Ptr c = (Comment_Ptr) &stm; + if (Cast(stm)) return true; + else if (Cast(stm)) return true; + else if (Comment_Ptr c = Cast(stm)) { if (isPrintable(c, style)) { return true; } } - else if (dynamic_cast(&stm)) { - Ruleset_Ptr r = (Ruleset_Ptr) &stm; + else if (Ruleset_Ptr r = Cast(stm)) { if (isPrintable(r, style)) { return true; } } - else if (dynamic_cast(&stm)) { - Supports_Block_Ptr f = (Supports_Block_Ptr) &stm; + else if (Supports_Block_Ptr f = Cast(stm)) { if (isPrintable(f, style)) { return true; } } - else if (dynamic_cast(&stm)) { - Media_Block_Ptr m = (Media_Block_Ptr) &stm; + else if (Media_Block_Ptr m = Cast(stm)) { if (isPrintable(m, style)) { return true; } } - else if (dynamic_cast(&stm) && isPrintable(((Has_Block_Ptr)&stm)->block(), style)) { - return true; + else if (Has_Block_Ptr b = Cast(stm)) { + if (isPrintable(b->block(), style)) { + return true; + } } } return false; @@ -596,35 +592,33 @@ namespace Sass { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); - if (dynamic_cast(&stm) || dynamic_cast(&stm)) { + if (Cast(stm) || Cast(stm)) { return true; } - else if (dynamic_cast(&stm)) { - Comment_Ptr c = (Comment_Ptr) &stm; + else if (Comment_Ptr c = Cast(stm)) { if (isPrintable(c, style)) { return true; } } - else if (dynamic_cast(&stm)) { - Ruleset_Ptr r = (Ruleset_Ptr) &stm; + else if (Ruleset_Ptr r = Cast(stm)) { if (isPrintable(r, style)) { return true; } } - else if (dynamic_cast(&stm)) { - Supports_Block_Ptr f = (Supports_Block_Ptr) &stm; + else if (Supports_Block_Ptr f = Cast(stm)) { if (isPrintable(f, style)) { return true; } } - else if (dynamic_cast(&stm)) { - Media_Block_Ptr m = (Media_Block_Ptr) &stm; + else if (Media_Block_Ptr m = Cast(stm)) { if (isPrintable(m, style)) { return true; } } - else if (dynamic_cast(&stm) && isPrintable(((Has_Block_Ptr)&stm)->block(), style)) { - return true; + else if (Has_Block_Ptr b = Cast(stm)) { + if (isPrintable(b->block(), style)) { + return true; + } } } diff --git a/src/libsass/src/values.cpp b/src/libsass/src/values.cpp index 4d16b614f..a8b165f79 100755 --- a/src/libsass/src/values.cpp +++ b/src/libsass/src/values.cpp @@ -11,32 +11,32 @@ namespace Sass { { if (val->concrete_type() == Expression::NUMBER) { - Number_Ptr_Const res = dynamic_cast(val); + Number_Ptr_Const res = Cast(val); return sass_make_number(res->value(), res->unit().c_str()); } else if (val->concrete_type() == Expression::COLOR) { - Color_Ptr_Const col = dynamic_cast(val); + Color_Ptr_Const col = Cast(val); return sass_make_color(col->r(), col->g(), col->b(), col->a()); } else if (val->concrete_type() == Expression::LIST) { - List_Ptr_Const l = dynamic_cast(val); - union Sass_Value* list = sass_make_list(l->size(), l->separator()); + List_Ptr_Const l = Cast(val); + union Sass_Value* list = sass_make_list(l->size(), l->separator(), l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { Expression_Obj obj = l->at(i); - auto val = ast_node_to_sass_value(&obj); + auto val = ast_node_to_sass_value(obj); sass_list_set_value(list, i, val); } return list; } else if (val->concrete_type() == Expression::MAP) { - Map_Ptr_Const m = dynamic_cast(val); + Map_Ptr_Const m = Cast(val); union Sass_Value* map = sass_make_map(m->length()); size_t i = 0; for (Expression_Obj key : m->keys()) { - sass_map_set_key(map, i, ast_node_to_sass_value(&key)); - sass_map_set_value(map, i, ast_node_to_sass_value(&m->at(key))); + sass_map_set_key(map, i, ast_node_to_sass_value(key)); + sass_map_set_value(map, i, ast_node_to_sass_value(m->at(key))); ++ i; } return map; @@ -47,16 +47,16 @@ namespace Sass { } else if (val->concrete_type() == Expression::BOOLEAN) { - Boolean_Ptr_Const res = dynamic_cast(val); + Boolean_Ptr_Const res = Cast(val); return sass_make_boolean(res->value()); } else if (val->concrete_type() == Expression::STRING) { - if (String_Quoted_Ptr_Const qstr = dynamic_cast(val)) + if (String_Quoted_Ptr_Const qstr = Cast(val)) { return sass_make_qstring(qstr->value().c_str()); } - else if (String_Constant_Ptr_Const cstr = dynamic_cast(val)) + else if (String_Constant_Ptr_Const cstr = Cast(val)) { return sass_make_string(cstr->value().c_str()); } @@ -106,6 +106,7 @@ namespace Sass { for (size_t i = 0, L = sass_list_get_length(val); i < L; ++i) { l->append(sass_value_to_ast_node(sass_list_get_value(val, i))); } + l->is_bracketed(sass_list_get_is_bracketed(val)); return l; } break; diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets index 222c4381a..2f079b9d5 100755 --- a/src/libsass/win/libsass.targets +++ b/src/libsass/win/libsass.targets @@ -69,6 +69,7 @@ + diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters index 47ea1c147..36d85b389 100755 --- a/src/libsass/win/libsass.vcxproj.filters +++ b/src/libsass/win/libsass.vcxproj.filters @@ -218,6 +218,9 @@ Sources + + Sources + Sources diff --git a/src/sass_types/list.cpp b/src/sass_types/list.cpp index c17dbd59e..cc94729a3 100644 --- a/src/sass_types/list.cpp +++ b/src/sass_types/list.cpp @@ -8,6 +8,7 @@ namespace SassTypes Sass_Value* List::construct(const std::vector> raw_val, Sass_Value **out) { size_t length = 0; bool comma = true; + bool is_bracketed = false; if (raw_val.size() >= 1) { if (!raw_val[0]->IsNumber()) { @@ -25,7 +26,7 @@ namespace SassTypes } } - return *out = sass_make_list(length, comma ? SASS_COMMA : SASS_SPACE); + return *out = sass_make_list(length, comma ? SASS_COMMA : SASS_SPACE, is_bracketed); } void List::initPrototype(v8::Local proto) { From 323b193833d044c54e09121bb9a2f6c75e67574f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 26 Jan 2017 14:07:22 +1100 Subject: [PATCH 027/286] Bump sass-spec for 3.5 features --- package.json | 2 +- test/spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f64a62fa9..425b59c0c 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,6 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.4.3" + "sass-spec": "3.5.0-1" } } diff --git a/test/spec.js b/test/spec.js index f672c6185..0c3de2f40 100644 --- a/test/spec.js +++ b/test/spec.js @@ -12,7 +12,7 @@ var assert = require('assert'), glob = require('glob'), specPath = require('sass-spec').dirname.replace(/\\/g, '/'), impl = 'libsass', - version = 3.4; + version = 3.5; var normalize = function(str) { // This should be /\r\n/g, '\n', but there seems to be some empty line issues From d628b103f722f484bb60376a15cb441d48c60c40 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 26 Jan 2017 14:07:55 +1100 Subject: [PATCH 028/286] 4.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 425b59c0c..ba29f7fca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.3.0", + "version": "4.4.0", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From 36de131209c5f87bfdb302e3addac796337350bc Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 27 Jan 2017 12:32:33 +1100 Subject: [PATCH 029/286] Use Visual Studio 2015 for Node 6+ Node change to Visual Studio 2015 when Node 6 went to LTS. See nodejs/node#7989 Fixes #1854 --- appveyor.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b9c2c4358..0e57a65b3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,13 +43,17 @@ - nodejs_version: 4 - nodejs_version: 5 - nodejs_version: 6 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - nodejs_version: 7 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform - node --version - npm --version - - npm install --msvs_version=2013 + - npm install test: off @@ -111,16 +115,26 @@ SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: - nodejs_version: 0.10 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 0.12 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 4 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 6 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - nodejs_version: 7 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform - node --version - npm --version - - npm install --msvs_version=2013 + - npm install test_script: - ps: set-location -path c:\projects\node_modules\node-sass From 38fe5c698700344fe76a4597fb1e484a18a3383c Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 28 Jan 2017 12:51:09 +1100 Subject: [PATCH 030/286] Make the watcher more responsive to child changes Watching for changes on files with lots of `@import`s had a significant regression in responsiveness in #1745. The regression was caused by calling `gaze.add` unnecessarily. We only need to call `gaze.add` on files that aren't currently being watched. At the time I confirmed that calling `gaze.add` in files that were being watched wouldn't result in a leak or multiple events being fired. I however assumed calling `gaze.add` for already watched files would be very cheap. Fixes #1869 --- bin/node-sass | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/node-sass b/bin/node-sass index 066a5b8d9..e94c12c77 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -268,7 +268,10 @@ function watch(options, emitter) { // Add children to watcher graph.visitDescendents(file, function(child) { - gaze.add(child); + if (watch.indexOf(child) === -1) { + watch.push(child); + gaze.add(child); + } }); files.forEach(function(file) { if (path.basename(file)[0] !== '_') { From 722e617e3006f26aac3664237fc27a56c4688caf Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 28 Jan 2017 15:14:47 +1100 Subject: [PATCH 031/286] Update AppVeyor release config The AppVeyor config was updated in #1870 but I missed some changes for the release config. --- appveyor.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 0e57a65b3..a3224b8a0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,13 +35,29 @@ SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: - nodejs_version: 0.10 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 0.12 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 1.0 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 1 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 2 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 3 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 4 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 5 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 6 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 From 43106236cce7c78061fbd6a198269fc9d7beb782 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 31 Jan 2017 20:06:40 +1100 Subject: [PATCH 032/286] Restore behaviour of cwd in include path Traditionally Sass has added the cwd as the first include path. This behaviour was remove in Sass 3.4, and recently in LibSass 3.5.0.beta.2. People depend on this behaviour and as a result a lot of compilations are now failing. This PR restores the expected behaviour and adds a test to boot. Fixes #1876 --- lib/index.js | 5 +++++ test/api.js | 17 +++++++++++++++++ test/fixtures/cwd-include-path/expected.css | 2 ++ test/fixtures/cwd-include-path/outside.scss | 3 +++ test/fixtures/cwd-include-path/root/index.scss | 1 + 5 files changed, 28 insertions(+) create mode 100644 test/fixtures/cwd-include-path/expected.css create mode 100644 test/fixtures/cwd-include-path/outside.scss create mode 100644 test/fixtures/cwd-include-path/root/index.scss diff --git a/lib/index.js b/lib/index.js index 4d880a8f5..fafbe55f2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -171,6 +171,11 @@ function buildIncludePaths(options) { ); } + // Preserve the behaviour people have come to expect. + // This behaviour was removed from Sass in 3.4 and + // LibSass in 3.5. + options.includePaths.unshift(process.cwd()); + return options.includePaths.join(path.delimiter); } diff --git a/test/api.js b/test/api.js index 8ff5a972d..67a53ccb3 100644 --- a/test/api.js +++ b/test/api.js @@ -142,6 +142,23 @@ describe('api', function() { }); }); + it('should add cwd to the front on include paths', function(done) { + var src = fixture('cwd-include-path/root/index.scss'); + var expected = read(fixture('cwd-include-path/expected.css'), 'utf8').trim(); + var cwd = process.cwd(); + + process.chdir(fixture('cwd-include-path')); + sass.render({ + file: src, + includePaths: [] + }, function(error, result) { + assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n')); + + process.chdir(cwd); + done(); + }); + }); + it('should check SASS_PATH in the specified order', function(done) { var src = read(fixture('sass-path/index.scss'), 'utf8'); var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim(); diff --git a/test/fixtures/cwd-include-path/expected.css b/test/fixtures/cwd-include-path/expected.css new file mode 100644 index 000000000..1cfd35a4a --- /dev/null +++ b/test/fixtures/cwd-include-path/expected.css @@ -0,0 +1,2 @@ +.outside { + color: red; } diff --git a/test/fixtures/cwd-include-path/outside.scss b/test/fixtures/cwd-include-path/outside.scss new file mode 100644 index 000000000..956862381 --- /dev/null +++ b/test/fixtures/cwd-include-path/outside.scss @@ -0,0 +1,3 @@ +.outside { + color: red; +} diff --git a/test/fixtures/cwd-include-path/root/index.scss b/test/fixtures/cwd-include-path/root/index.scss new file mode 100644 index 000000000..0279f783b --- /dev/null +++ b/test/fixtures/cwd-include-path/root/index.scss @@ -0,0 +1 @@ +@import 'outside'; From 203f8d6627067b1004bad0599989c5849f72f6a0 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 31 Jan 2017 20:42:38 +1100 Subject: [PATCH 033/286] Mirror render and renderSync tests --- test/api.js | 167 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 152 insertions(+), 15 deletions(-) diff --git a/test/api.js b/test/api.js index 67a53ccb3..e082c9714 100644 --- a/test/api.js +++ b/test/api.js @@ -87,6 +87,19 @@ describe('api', function() { }); }); + it('should compile sass to css using indented syntax', function(done) { + var src = read(fixture('indent/index.sass'), 'utf8'); + var expected = read(fixture('indent/expected.css'), 'utf8').trim(); + + sass.render({ + data: src, + indentedSyntax: true + }, function(error, result) { + assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n')); + done(); + }); + }); + it('should NOT compile empty data string', function(done) { sass.render({ data: '' @@ -96,27 +109,14 @@ describe('api', function() { }); }); - it('should NOT compile without parameters', function(done) { + it('should NOT compile without any input', function(done) { sass.render({ }, function(error) { assert.equal(error.message, 'No input specified: provide a file name or a source string to process'); done(); }); }); - it('should compile sass to css using indented syntax', function(done) { - var src = read(fixture('indent/index.sass'), 'utf8'); - var expected = read(fixture('indent/expected.css'), 'utf8').trim(); - - sass.render({ - data: src, - indentedSyntax: true - }, function(error, result) { - assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n')); - done(); - }); - }); - - it('should throw error status 1 for bad input', function(done) { + it('should returnn error status 1 for bad input', function(done) { sass.render({ data: '#navbar width 80%;' }, function(error) { @@ -1406,6 +1406,143 @@ describe('api', function() { done(); }); + + it('should compile with include paths', function(done) { + var src = read(fixture('include-path/index.scss'), 'utf8'); + var expected = read(fixture('include-path/expected.css'), 'utf8').trim(); + var result = sass.renderSync({ + data: src, + includePaths: [ + fixture('include-path/functions'), + fixture('include-path/lib') + ] + }); + + assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n')); + done(); + }); + + it('should add cwd to the front on include paths', function(done) { + var src = fixture('cwd-include-path/root/index.scss'); + var expected = read(fixture('cwd-include-path/expected.css'), 'utf8').trim(); + var cwd = process.cwd(); + + process.chdir(fixture('cwd-include-path')); + var result = sass.renderSync({ + file: src, + includePaths: [ + fixture('include-path/functions'), + fixture('include-path/lib') + ] + }); + process.chdir(cwd); + + assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n')); + done(); + }); + + it('should check SASS_PATH in the specified order', function(done) { + var src = read(fixture('sass-path/index.scss'), 'utf8'); + var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim(); + var expectedOrange = read(fixture('sass-path/expected-orange.css'), 'utf8').trim(); + + var envIncludes = [ + fixture('sass-path/red'), + fixture('sass-path/orange') + ]; + + process.env.SASS_PATH = envIncludes.join(path.delimiter); + var result = sass.renderSync({ + data: src, + includePaths: [] + }); + + assert.equal(result.css.toString().trim(), expectedRed.replace(/\r\n/g, '\n')); + + process.env.SASS_PATH = envIncludes.reverse().join(path.delimiter); + result = sass.renderSync({ + data: src, + includePaths: [] + }); + + assert.equal(result.css.toString().trim(), expectedOrange.replace(/\r\n/g, '\n')); + done(); + }); + + it('should prefer include path over SASS_PATH', function(done) { + var src = read(fixture('sass-path/index.scss'), 'utf8'); + var expectedRed = read(fixture('sass-path/expected-red.css'), 'utf8').trim(); + var expectedOrange = read(fixture('sass-path/expected-orange.css'), 'utf8').trim(); + + var envIncludes = [ + fixture('sass-path/red') + ]; + process.env.SASS_PATH = envIncludes.join(path.delimiter); + + var result = sass.renderSync({ + data: src, + includePaths: [] + }); + + assert.equal(result.css.toString().trim(), expectedRed.replace(/\r\n/g, '\n')); + + result = sass.renderSync({ + data: src, + includePaths: [fixture('sass-path/orange')] + }); + + assert.equal(result.css.toString().trim(), expectedOrange.replace(/\r\n/g, '\n')); + done(); + }); + + it('should render with precision option', function(done) { + var src = read(fixture('precision/index.scss'), 'utf8'); + var expected = read(fixture('precision/expected.css'), 'utf8').trim(); + var result = sass.renderSync({ + data: src, + precision: 10 + }); + + assert.equal(result.css.toString().trim(), expected.replace(/\r\n/g, '\n')); + done(); + }); + + it('should contain all included files in stats when data is passed', function(done) { + var src = read(fixture('include-files/index.scss'), 'utf8'); + var expected = [ + fixture('include-files/bar.scss').replace(/\\/g, '/'), + fixture('include-files/foo.scss').replace(/\\/g, '/') + ]; + + var result = sass.renderSync({ + data: src, + includePaths: [fixture('include-files')] + }); + + assert.deepEqual(result.stats.includedFiles, expected); + done(); + }); + + it('should render with indentWidth and indentType options', function(done) { + var result = sass.renderSync({ + data: 'div { color: transparent; }', + indentWidth: 7, + indentType: 'tab' + }); + + assert.equal(result.css.toString().trim(), 'div {\n\t\t\t\t\t\t\tcolor: transparent; }'); + done(); + }); + + it('should render with linefeed option', function(done) { + var result = sass.renderSync({ + data: 'div { color: transparent; }', + linefeed: 'lfcr' + }); + + assert.equal(result.css.toString().trim(), 'div {\n\r color: transparent; }'); + done(); + }); }); describe('.renderSync(importer)', function() { From cc7c3b37ba65fe5c7ebd0965c16e086abec57f3b Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 31 Jan 2017 22:35:24 +1100 Subject: [PATCH 034/286] 4.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba29f7fca..0e7e8ff22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.4.0", + "version": "4.5.0", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From f0419a7adce8ab289a2583d36edb7bc30bd3a970 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Mon, 6 Feb 2017 20:18:05 +1100 Subject: [PATCH 035/286] Fix npm rebuild Running `npm rebuild node-sass` as we instruct people, has been noop for a while. I'm not entirely sure what has changed. Debugging as reveal that running `rebuild` now just triggers the `install` and `postinstall` scripts. The `build` script is no longer run. This suggests something has changed in newer versions of npm. Since the `--force` flag is required to rebuild the binary I've update the `build` script to take into account if `--force` was passed to npm. --- lib/errors.js | 2 +- scripts/build.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/errors.js b/lib/errors.js index 1dbcf02a7..6bfb77f29 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -25,7 +25,7 @@ function foundBinariesList() { function missingBinaryFooter() { return [ 'This usually happens because your environment has changed since running `npm install`.', - 'Run `npm rebuild node-sass` to build the binding for your current environment.', + 'Run `npm rebuild node-sass --force` to build the binding for your current environment.', ].join('\n'); } diff --git a/scripts/build.js b/scripts/build.js index ef919eed5..7bbba5ee4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -93,7 +93,8 @@ function build(options) { function parseArgs(args) { var options = { arch: process.arch, - platform: process.platform + platform: process.platform, + force: process.env.npm_config_force === 'true', }; options.args = args.filter(function(arg) { From eb7f0138368bdc985253312f43c93f2971aea295 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 16 Feb 2017 02:14:39 -0500 Subject: [PATCH 036/286] Add binding.js to Coveralls coverage (#1902) --- scripts/coverage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/coverage.js b/scripts/coverage.js index bc7e90246..33836e9cf 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -8,7 +8,7 @@ var Mocha = require('mocha'), mkdirp = require('mkdirp'), coveralls = require('coveralls'), istanbul = require('istanbul'), - sourcefiles = ['index.js', 'extensions.js', 'render.js', 'errors.js'], + sourcefiles = ['index.js', 'binding.js', 'extensions.js', 'render.js', 'errors.js'], summary= istanbul.Report.create('text-summary'), lcov = istanbul.Report.create('lcovonly', { dir: path.join('coverage') }), html = istanbul.Report.create('html', { dir: path.join('coverage', 'html') }); From 7201e9870c394790c9b9d2ecbadc96cdfc0a6050 Mon Sep 17 00:00:00 2001 From: Franco Arza Date: Mon, 27 Feb 2017 14:55:42 +0200 Subject: [PATCH 037/286] Update request version to avoid npm warn (#1915) * Update request version to avoid npm warn --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e7e8ff22..85aef6e0c 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "nan": "^2.3.2", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", - "request": "^2.61.0", + "request": "^2.79.0", "sass-graph": "^2.1.1", "stdout-stream": "^1.4.0" }, From f2f4b96eca1e6762f5ca30e36618c5426e4f34bc Mon Sep 17 00:00:00 2001 From: Harris Weeks Date: Sun, 12 Mar 2017 12:43:14 -0700 Subject: [PATCH 038/286] Update link for VS 2013 download Closes #1838 [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc3e6aafe..4a939c2f6 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ npm install node-sass Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager) to install NodeJS so that `#!/usr/bin/env node` correctly resolved. -Compiling versions 0.9.4 and above on Windows machines requires [Visual Studio 2013 WD](https://www.visualstudio.com/downloads/download-visual-studio-vs#d-express-windows-desktop). If you have multiple VS versions, use ```npm install``` with the ```--msvs_version=2013``` flag also use this flag when rebuilding the module with node-gyp or nw-gyp. +Compiling versions 0.9.4 and above on Windows machines requires [Visual Studio 2013 WD](https://www.microsoft.com/en-us/download/details.aspx?id=44914). If you have multiple VS versions, use ```npm install``` with the ```--msvs_version=2013``` flag also use this flag when rebuilding the module with node-gyp or nw-gyp. **Having installation troubles? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md).** From 10f9a58ce5f499fb898a9ae77e093cc4e9dc8928 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 21 Mar 2017 21:24:51 +1100 Subject: [PATCH 039/286] Add Node 8 to the support versions lists This is a workaround for nodejs/citgm#389. The install/build flow needs to be updated so that unsupported environments result in an informative warning instead of a failure. --- lib/extensions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/extensions.js b/lib/extensions.js index 7d5fd9e88..e8c2ffaf5 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -69,6 +69,7 @@ function getHumanNodeVersion(abi) { case 47: return 'Node.js 5.x'; case 48: return 'Node.js 6.x'; case 51: return 'Node.js 7.x'; + case 53: return 'Node.js 8.x'; default: return false; } } From 7488f558731d38e498935b296fcb5112dc794848 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 21 Mar 2017 21:36:42 +1100 Subject: [PATCH 040/286] Make binding tests less fragile Use hardcoded process values rather than inspecting the current environment. --- test/binding.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/test/binding.js b/test/binding.js index 329f0d524..200c7802b 100644 --- a/test/binding.js +++ b/test/binding.js @@ -10,7 +10,8 @@ var assert = require('assert'), describe('binding', function() { describe('missing error', function() { it('should be useful', function() { - process.env.SASS_BINARY_NAME = 'Linux-x64-48'; + process.env.SASS_BINARY_NAME = 'unknown-x64-48'; + assert.throws( function() { binding(etx); }, function(err) { @@ -42,11 +43,7 @@ describe('binding', function() { describe('on unsupported environment', function() { describe('with an unsupported architecture', function() { - var prevValue; - beforeEach(function() { - prevValue = process.arch; - Object.defineProperty(process, 'arch', { value: 'foo', }); @@ -54,7 +51,7 @@ describe('binding', function() { afterEach(function() { Object.defineProperty(process, 'arch', { - value: prevValue, + value: 'x64', }); }); @@ -74,11 +71,7 @@ describe('binding', function() { }); describe('with an unsupported platform', function() { - var prevValue; - beforeEach(function() { - prevValue = process.platform; - Object.defineProperty(process, 'platform', { value: 'bar', }); @@ -86,7 +79,7 @@ describe('binding', function() { afterEach(function() { Object.defineProperty(process, 'platform', { - value: prevValue, + value: 'darwin', }); }); @@ -106,11 +99,7 @@ describe('binding', function() { }); describe('with an unsupported runtime', function() { - var prevValue; - beforeEach(function() { - prevValue = process.versions.modules; - Object.defineProperty(process.versions, 'modules', { value: 'baz', }); @@ -118,7 +107,7 @@ describe('binding', function() { afterEach(function() { Object.defineProperty(process.versions, 'modules', { - value: prevValue, + value: 51, }); }); From 86c600ae8ba77e8c451a8387fa96310c2fd2214e Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 21 Mar 2017 22:07:45 +1100 Subject: [PATCH 041/286] 4.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 85aef6e0c..becf517c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.5.0", + "version": "4.5.1", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From f2b410b7ffecb8b48b11e59b2ce2aa9601367112 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 21 Mar 2017 22:13:36 +1100 Subject: [PATCH 042/286] Update changelog --- CHANGELOG.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be2471f25..dd1ebee79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,79 @@ +## v4.5.0 + +https://github.com/sass/node-sass/releases/tag/v4.5.0 + +## v4.4.0 + +https://github.com/sass/node-sass/releases/tag/v4.4.0 + +## v4.3.0 + +https://github.com/sass/node-sass/releases/tag/v4.3.0 + +## v4.2.0 + +https://github.com/sass/node-sass/releases/tag/v4.2.0 + +## v4.1.1 + +https://github.com/sass/node-sass/releases/tag/v4.1.1 + +## v4.1.0 + +https://github.com/sass/node-sass/releases/tag/v4.1.0 + +## v4.0.0 + +https://github.com/sass/node-sass/releases/tag/v4.0.0 + +## v3.13.1 + +https://github.com/sass/node-sass/releases/tag/v3.13.1 + +## v3.13.0 + +https://github.com/sass/node-sass/releases/tag/v3.13.0 + +## v3.12.5 + +https://github.com/sass/node-sass/releases/tag/v3.12.5 + +## v3.12.4 + +https://github.com/sass/node-sass/releases/tag/v3.12.4 + +## v3.12.3 + +https://github.com/sass/node-sass/releases/tag/v3.12.3 + +## v3.12.2 + +https://github.com/sass/node-sass/releases/tag/v3.12.2 + +## v3.12.1 + +https://github.com/sass/node-sass/releases/tag/v3.12.1 + +## v3.12.0 + +https://github.com/sass/node-sass/releases/tag/v3.12.0 + +## v3.11.3 + +https://github.com/sass/node-sass/releases/tag/v3.11.3 + +## v3.11.2 + +https://github.com/sass/node-sass/releases/tag/v3.11.2 + +## v3.11.1 + +https://github.com/sass/node-sass/releases/tag/v3.11.1 + +## v3.11.0 + +https://github.com/sass/node-sass/releases/tag/v3.11.0 + ## v3.10.1 https://github.com/sass/node-sass/releases/tag/v3.10.1 From 29941a136b0d3b8849f7405e83dfeca65acfc06a Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 29 Mar 2017 02:03:35 -0400 Subject: [PATCH 043/286] Update module version for node v8.x V8 5.7 landed recently in node core, causing the module version to get bumped. --- lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index e8c2ffaf5..43fde1b55 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -69,7 +69,7 @@ function getHumanNodeVersion(abi) { case 47: return 'Node.js 5.x'; case 48: return 'Node.js 6.x'; case 51: return 'Node.js 7.x'; - case 53: return 'Node.js 8.x'; + case 54: return 'Node.js 8.x'; default: return false; } } From ae4f935f04ec1d3a5db2b5692f97171ec7ddb9a5 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 29 Mar 2017 17:27:32 +1100 Subject: [PATCH 044/286] 4.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index becf517c3..7115a33d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.5.1", + "version": "4.5.2", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From 348a3b18d32395c9c1a4ab8cb974ed85ae56ef48 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 16 May 2017 16:20:56 +0200 Subject: [PATCH 045/286] Update module version for Node 8 --- lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index 43fde1b55..047d2216f 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -69,7 +69,7 @@ function getHumanNodeVersion(abi) { case 47: return 'Node.js 5.x'; case 48: return 'Node.js 6.x'; case 51: return 'Node.js 7.x'; - case 54: return 'Node.js 8.x'; + case 55: return 'Node.js 8.x'; default: return false; } } From f783a9c57e537e45aab17ae211007ffd4ab121f9 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 16 May 2017 16:25:38 +0200 Subject: [PATCH 046/286] Premptively account for module version 57 TODO(xzyfer): fix this post Node 8 --- lib/extensions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/extensions.js b/lib/extensions.js index 047d2216f..0a523a837 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -70,6 +70,7 @@ function getHumanNodeVersion(abi) { case 48: return 'Node.js 6.x'; case 51: return 'Node.js 7.x'; case 55: return 'Node.js 8.x'; + case 57: return 'Node.js 8.x'; default: return false; } } From 48bbed1b923ab3f6338f866e8dc3fe906909803b Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 16 May 2017 16:53:05 +0200 Subject: [PATCH 047/286] 4.5.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7115a33d6..956d9bc51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.5.2", + "version": "4.5.3", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From fcd29e92b4a5351c827c7cac61281a2bee05ecab Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 30 May 2017 16:42:03 -0400 Subject: [PATCH 048/286] Build: Use Travis Stages (#1977) Run the initial listing before running the full matrix of builds --- .travis.yml | 55 ++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab225d22d..a9d7bcbf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,40 @@ language: node_js -node_js: - - "7" - - "6" - - "4" - - "0.12" - - "0.10" - - "iojs" compiler: gcc sudo: false -os: - - linux - - osx - env: global: - SKIP_SASS_BINARY_DOWNLOAD_FOR_CI=true -matrix: - fast_finish: true - exclude: - - node_js: iojs - - node_js: "0.10" +jobs: + include: + - stage: test + node_js: "node" + os: linux + before_script: npm run lint || exit 1; + after_success: npm run-script coverage; + - stage: platform-test + node_js: "node" + os: osx + - stage: platform-test + node_js: "lts/boron" + os: linux + - stage: platform-test + node_js: "lts/boron" os: osx - - node_js: "0.12" + - stage: platform-test + node_js: "lts/argon" + os: linux + - stage: platform-test + node_js: "lts/argon" os: osx + - stage: platform-test + node_js: "0.12" + os: linux + - stage: platform-test + node_js: "0.10" + os: linux addons: apt: @@ -49,17 +58,11 @@ before_install: - gcc --version - g++ --version -script: +install: - npm install - - if [ $TRAVIS_OS_NAME == "linux" ] && [ $TRAVIS_NODE_VERSION == "4" ]; then - npm run lint || exit 1; - fi - - npm test -after_success: - - if [ $TRAVIS_OS_NAME == "linux" ] && [ $TRAVIS_NODE_VERSION == "4" ]; then - npm run-script coverage; - fi +script: + - npm test cache: directories: From 6331ad82eba76379e1646c60590d8b5dc327faf9 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 30 May 2017 19:24:34 -0400 Subject: [PATCH 049/286] Chore: Add back Node 7 to Travis (#1986) Closes #1983 --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index a9d7bcbf6..28693b22f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,12 @@ jobs: after_success: npm run-script coverage; - stage: platform-test node_js: "node" + os: osx + - stage: platform-test + node_js: "7" + os: linux + - stage: platform-test + node_js: "7" os: osx - stage: platform-test node_js: "lts/boron" From 593d1fa9e5a830b7136e2d6a0814227da5e965b1 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 30 May 2017 23:49:33 -0400 Subject: [PATCH 050/286] Chore: Add Node 8 to Appveyor (#1987) --- appveyor.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index a3224b8a0..87dc0ed8c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -64,6 +64,9 @@ - nodejs_version: 7 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 8 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform @@ -145,6 +148,9 @@ - nodejs_version: 7 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 8 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform From 1dcc78acce4243a8014b0f23806bccd2aa328cbd Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 2 Jun 2017 00:27:53 -0400 Subject: [PATCH 051/286] Move GitHub files under .github folder (#1997) * Move Contributing.md under .github * Move ISSUE_TEMPLATE.md under .github * Chore: Move ISSUE_TEMPLATE under .github --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE.md | 0 README.md | 6 +----- 3 files changed, 1 insertion(+), 5 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) rename ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE.md (100%) diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md diff --git a/README.md b/README.md index 4a939c2f6..c46b0dddf 100644 --- a/README.md +++ b/README.md @@ -563,11 +563,7 @@ We <3 our contributors! A special thanks to all those who have clocked in some d ### Note on Patches/Pull Requests - * Fork the project. - * Make your feature addition or bug fix. - * Add documentation if necessary. - * Add tests for it. This is important so I don't break it in a future version unintentionally. - * Send a pull request. Bonus points for topic branches. +Check out our [Contributing guide](/.github/CONTRIBUTING.md) ## Copyright From a1488f1ecc3db2acc208c17f296773e958cffc16 Mon Sep 17 00:00:00 2001 From: Jonas Hermsmeier Date: Fri, 2 Jun 2017 20:25:30 +0200 Subject: [PATCH 052/286] Add Electron ABI versions This adds the respective Electron ABI versions to `getHumanNodeVersion()` to enable running `node-sass` under Electron where the ABI versions differ from Node's. --- lib/extensions.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/extensions.js b/lib/extensions.js index 0a523a837..8681be11a 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -68,7 +68,10 @@ function getHumanNodeVersion(abi) { case 46: return 'Node.js 4.x'; case 47: return 'Node.js 5.x'; case 48: return 'Node.js 6.x'; + case 49: return 'Electron 1.3.x'; + case 50: return 'Electron 1.4.x'; case 51: return 'Node.js 7.x'; + case 53: return 'Electron 1.6.x'; case 55: return 'Node.js 8.x'; case 57: return 'Node.js 8.x'; default: return false; From 528ac44ec88479c9a75d87712f75ce21d70cc792 Mon Sep 17 00:00:00 2001 From: Jan Piotrowski Date: Fri, 9 Jun 2017 17:28:15 +0200 Subject: [PATCH 053/286] Readme: Fix typo in alt text of Slack Badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c46b0dddf..bc947e457 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # node-sass -#### Supported Node.js versions 0.10, 0.12, 1, 2, 3, 4, 5, 6 and 7. +#### Supported Node.js versions 0.10, 0.12, 1, 2, 3, 4, 5, 6, 7 and 8. @@ -22,7 +22,7 @@ [![devDependency Status](https://david-dm.org/sass/node-sass/dev-status.svg?theme=shields.io)](https://david-dm.org/sass/node-sass#info=devDependencies) [![Coverage Status](https://coveralls.io/repos/sass/node-sass/badge.svg?branch=master)](https://coveralls.io/r/sass/node-sass?branch=master) [![Inline docs](http://inch-ci.org/github/sass/node-sass.svg?branch=master)](http://inch-ci.org/github/sass/node-sass) -[![Join us in Slakc](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/) +[![Join us in Slack](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/) Node-sass is a library that provides binding for Node.js to [LibSass], the C version of the popular stylesheet preprocessor, Sass. From 750573a6e607e9adbe5e04a171bfb08391193ec7 Mon Sep 17 00:00:00 2001 From: Jan Piotrowski Date: Fri, 9 Jun 2017 18:49:14 +0200 Subject: [PATCH 054/286] Readme: Add Node v8 as supported (#2008) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c46b0dddf..65d7a2313 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # node-sass -#### Supported Node.js versions 0.10, 0.12, 1, 2, 3, 4, 5, 6 and 7. +#### Supported Node.js versions 0.10, 0.12, 1, 2, 3, 4, 5, 6, 7 and 8.
From 61d7e1c1abf762aad22bb2a6b3fdac884ca369bd Mon Sep 17 00:00:00 2001 From: Jason Pettett Date: Tue, 13 Jun 2017 18:23:26 +1000 Subject: [PATCH 055/286] Fix spelling of "synchronously" in README.md (#2015) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc947e457..229356260 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Starting from v3.0.0: ```javascript done(new Error('doesn\'t exist!')); - // or return synchornously + // or return synchronously return new Error('nothing to do here'); ``` From aadf8d47560a8a44f6d6180e5d7f43b7fbe6f792 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Sat, 17 Jun 2017 19:06:39 +1000 Subject: [PATCH 056/286] Decouple the graph and render logic from the fs watcher (#2020) This logic is all tightly coupled in the bin. This logic has been notoriously impossible to test due to weird fs timing issues. By extracting the actual logic we're now able to test it in isolation. With this in place replacing Gaze (#636) becomes a viable option. This PR not only massively increases our test coverage for the watcher but also address a bunch of known edge cases i.e. orphaned imports when a files is deleted. Closes #1896 Fixes #1891 --- bin/node-sass | 70 +++---- lib/watcher.js | 83 ++++++++ package.json | 4 +- test/fixtures/watcher/main/one.scss | 5 + test/fixtures/watcher/main/partials/_one.scss | 3 + .../watcher/main/partials/_three.scss | 3 + test/fixtures/watcher/main/partials/_two.scss | 3 + test/fixtures/watcher/main/two.scss | 5 + .../watcher/sibling/partials/_three.scss | 3 + test/fixtures/watcher/sibling/three.scss | 5 + test/watcher.js | 190 ++++++++++++++++++ 11 files changed, 327 insertions(+), 47 deletions(-) create mode 100644 lib/watcher.js create mode 100644 test/fixtures/watcher/main/one.scss create mode 100644 test/fixtures/watcher/main/partials/_one.scss create mode 100644 test/fixtures/watcher/main/partials/_three.scss create mode 100644 test/fixtures/watcher/main/partials/_two.scss create mode 100644 test/fixtures/watcher/main/two.scss create mode 100644 test/fixtures/watcher/sibling/partials/_three.scss create mode 100644 test/fixtures/watcher/sibling/three.scss create mode 100644 test/watcher.js diff --git a/bin/node-sass b/bin/node-sass index e94c12c77..4139f71d1 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -3,13 +3,13 @@ var Emitter = require('events').EventEmitter, forEach = require('async-foreach').forEach, Gaze = require('gaze'), - grapher = require('sass-graph'), meow = require('meow'), util = require('util'), path = require('path'), glob = require('glob'), sass = require('../lib'), render = require('../lib/render'), + watcher = require('../lib/watcher'), stdout = require('stdout-stream'), stdin = require('get-stdin'), fs = require('fs'); @@ -229,63 +229,41 @@ function getOptions(args, options) { */ function watch(options, emitter) { - var buildGraph = function(options) { - var graph; - var graphOptions = { - loadPaths: options.includePath, - extensions: ['scss', 'sass', 'css'] - }; - - if (options.directory) { - graph = grapher.parseDir(options.directory, graphOptions); - } else { - graph = grapher.parseFile(options.src, graphOptions); - } + var handler = function(files) { + files.added.forEach(function(file) { + var watch = gaze.watched(); + if (watch.indexOf(file) === -1) { + gaze.add(file); + } + }); - return graph; + files.changed.forEach(function(file) { + if (path.basename(file)[0] !== '_') { + renderFile(file, options, emitter); + } + }); + + files.removed.forEach(function(file) { + gaze.remove(file); + }); }; - var watch = []; - var graph = buildGraph(options); - - // Add all files to watch list - for (var i in graph.index) { - watch.push(i); - } + watcher.reset(options); var gaze = new Gaze(); - gaze.add(watch); + gaze.add(watcher.reset(options)); gaze.on('error', emitter.emit.bind(emitter, 'error')); gaze.on('changed', function(file) { - var files = [file]; - - // descendents may be added, so we need a new graph - graph = buildGraph(options); - graph.visitAncestors(file, function(parent) { - files.push(parent); - }); - - // Add children to watcher - graph.visitDescendents(file, function(child) { - if (watch.indexOf(child) === -1) { - watch.push(child); - gaze.add(child); - } - }); - files.forEach(function(file) { - if (path.basename(file)[0] !== '_') { - renderFile(file, options, emitter); - } - }); + handler(watcher.changed(file)); }); - gaze.on('added', function() { - graph = buildGraph(options); + gaze.on('added', function(file) { + handler(watcher.added(file)); }); - gaze.on('deleted', function() { - graph = buildGraph(options); + gaze.on('deleted', function(file) { + handler(watcher.deleted(file)); }); } diff --git a/lib/watcher.js b/lib/watcher.js new file mode 100644 index 000000000..2d317b670 --- /dev/null +++ b/lib/watcher.js @@ -0,0 +1,83 @@ +var grapher = require('sass-graph'), + clonedeep = require('lodash.clonedeep'), + config = {}, + watcher = {}, + graph = null; + +watcher.reset = function(opts) { + config = clonedeep(opts || config || {}); + var options = { + loadPaths: config.includePath, + extensions: ['scss', 'sass', 'css'] + }; + + if (config.directory) { + graph = grapher.parseDir(config.directory, options); + } else { + graph = grapher.parseFile(config.src, options); + } + + return Object.keys(graph.index); +}; + +watcher.changed = function(absolutePath) { + var files = { + added: [], + changed: [], + removed: [], + }; + + this.reset(); + + graph.visitAncestors(absolutePath, function(parent) { + files.changed.push(parent); + }); + + graph.visitDescendents(absolutePath, function(child) { + files.added.push(child); + }); + + return files; +}; + +watcher.added = function(absolutePath) { + var files = { + added: [], + changed: [], + removed: [], + }; + + this.reset(); + + if (Object.keys(graph.index).indexOf(absolutePath) !== -1) { + files.added.push(absolutePath); + } + + graph.visitDescendents(absolutePath, function(child) { + files.added.push(child); + }); + + return files; +}; + +watcher.removed = function(absolutePath) { + var files = { + added: [], + changed: [], + removed: [], + }; + + graph.visitAncestors(absolutePath, function(parent) { + files.changed.push(parent); + }); + + if (Object.keys(graph.index).indexOf(absolutePath) !== -1) { + files.removed.push(absolutePath); + } + + this.reset(); + + return files; +}; + +module.exports = watcher; diff --git a/package.json b/package.json index 956d9bc51..d3ac26619 100644 --- a/package.json +++ b/package.json @@ -75,12 +75,14 @@ "devDependencies": { "coveralls": "^2.11.8", "eslint": "^3.4.0", + "fs-extra": "^0.30.0", "istanbul": "^0.4.2", "mocha": "^3.1.2", "mocha-lcov-reporter": "^1.2.0", "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.0-1" + "sass-spec": "3.5.0-1", + "unique-temp-dir": "^1.0.0" } } diff --git a/test/fixtures/watcher/main/one.scss b/test/fixtures/watcher/main/one.scss new file mode 100644 index 000000000..414af5e3f --- /dev/null +++ b/test/fixtures/watcher/main/one.scss @@ -0,0 +1,5 @@ +@import "partials/one"; + +.one { + color: red; +} diff --git a/test/fixtures/watcher/main/partials/_one.scss b/test/fixtures/watcher/main/partials/_one.scss new file mode 100644 index 000000000..b2a72d637 --- /dev/null +++ b/test/fixtures/watcher/main/partials/_one.scss @@ -0,0 +1,3 @@ +.one { + color: darkred; +} diff --git a/test/fixtures/watcher/main/partials/_three.scss b/test/fixtures/watcher/main/partials/_three.scss new file mode 100644 index 000000000..1846e9ae2 --- /dev/null +++ b/test/fixtures/watcher/main/partials/_three.scss @@ -0,0 +1,3 @@ +.three { + color: darkgreen; +} diff --git a/test/fixtures/watcher/main/partials/_two.scss b/test/fixtures/watcher/main/partials/_two.scss new file mode 100644 index 000000000..6a3b4ed90 --- /dev/null +++ b/test/fixtures/watcher/main/partials/_two.scss @@ -0,0 +1,3 @@ +.two { + color: darkblue; +} diff --git a/test/fixtures/watcher/main/two.scss b/test/fixtures/watcher/main/two.scss new file mode 100644 index 000000000..3e9c0fbf9 --- /dev/null +++ b/test/fixtures/watcher/main/two.scss @@ -0,0 +1,5 @@ +@import "partials/two"; + +.two { + color: blue; +} diff --git a/test/fixtures/watcher/sibling/partials/_three.scss b/test/fixtures/watcher/sibling/partials/_three.scss new file mode 100644 index 000000000..1846e9ae2 --- /dev/null +++ b/test/fixtures/watcher/sibling/partials/_three.scss @@ -0,0 +1,3 @@ +.three { + color: darkgreen; +} diff --git a/test/fixtures/watcher/sibling/three.scss b/test/fixtures/watcher/sibling/three.scss new file mode 100644 index 000000000..4e9d1a7e4 --- /dev/null +++ b/test/fixtures/watcher/sibling/three.scss @@ -0,0 +1,5 @@ +@import "partials/three"; + +.three { + color: green; +} diff --git a/test/watcher.js b/test/watcher.js new file mode 100644 index 000000000..7c9135e72 --- /dev/null +++ b/test/watcher.js @@ -0,0 +1,190 @@ +var assert = require('assert'), + fs = require('fs-extra'), + path = require('path'), + temp = require('unique-temp-dir'), + watcher = require('../lib/watcher'); + +describe.only('watcher', function() { + var main, sibling; + var origin = path.join(__dirname, 'fixtures', 'watcher'); + + beforeEach(function() { + var fixture = temp(); + fs.ensureDirSync(fixture); + fs.copySync(origin, fixture); + main = fs.realpathSync(path.join(fixture, 'main')); + sibling = fs.realpathSync(path.join(fixture, 'sibling')); + }); + + describe('with directory', function() { + beforeEach(function() { + watcher.reset({ + directory: main, + loadPaths: [main] + }); + }); + + describe('when a file is changed in the graph', function() { + it('should return the files to compile', function() { + var files = watcher.changed(path.join(main, 'partials', '_one.scss')); + assert.deepEqual(files, { + added: [], + changed: [path.join(main, 'one.scss')], + removed: [], + }); + }); + + describe('and @imports previously not imported files', function() { + it('should also return the new imported files', function() { + var file = path.join(main, 'partials', '_one.scss'); + fs.writeFileSync(file, '@import "partials/three.scss";', { flag: 'a' }); + var files = watcher.changed(file); + assert.deepEqual(files, { + added: [path.join(main, 'partials', '_three.scss')], + changed: [path.join(main, 'one.scss')], + removed: [], + }); + }); + }); + }); + + describe('when a file is changed outside the graph', function() { + it('should return an empty array', function() { + var files = watcher.changed(path.join(sibling, 'partials', '_three.scss')); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('when a file is added in the graph', function() { + it('should return only the added file', function() { + var file = path.join(main, 'three.scss'); + fs.writeFileSync(file, '@import "paritals/two.scss";'); + var files = watcher.added(file); + assert.deepEqual(files, { + added: [file], + changed: [], + removed: [], + }); + }); + + describe('and @imports previously not imported files', function() { + it('should also return the new imported files', function() { + var file = path.join(main, 'three.scss'); + fs.writeFileSync(file, '@import "partials/three.scss";'); + var files = watcher.added(file); + assert.deepEqual(files, { + added: [ + file, + path.join(main, 'partials', '_three.scss') + ], + changed: [], + removed: [], + }); + }); + }); + }); + + describe('when a file is added outside the graph', function() { + it('should return an empty array', function() { + var files = watcher.added(path.join(sibling, 'three.scss')); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('when a file is removed from the graph', function() { + it('should return the files to compile', function() { + var files = watcher.removed(path.join(main, 'partials', '_one.scss')); + assert.deepEqual(files, { + added: [], + changed: [path.join(main, 'one.scss')], + removed: [path.join(main, 'partials', '_one.scss')], + }); + }); + }); + + describe('when a file is removed from outside the graph', function() { + it('should return an empty array', function() { + var files = watcher.removed(path.join(sibling, 'partials', '_three.scss')); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + }); + + describe('with file', function() { + beforeEach(function() { + watcher.reset({ + src: path.join(main, 'one.scss'), + loadPaths: [main] + }); + }); + + describe('when a file is changed in the graph', function() { + it('should return the files to compile', function() { + var files = watcher.changed(path.join(main, 'partials', '_one.scss')); + assert.deepEqual(files, { + added: [], + changed: [path.join(main, 'one.scss')], + removed: [], + }); + }); + }); + + describe('when a file is changed outside the graph', function() { + it('should return an empty array', function() { + var files = watcher.changed(path.join(sibling, 'partials', '_three.scss')); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('when a file is added', function() { + it('should return an empty array', function() { + var file = path.join(main, 'three.scss'); + fs.writeFileSync(file, '@import "paritals/one.scss";'); + var files = watcher.added(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('when a file is removed from the graph', function() { + it('should return the files to compile', function() { + var files = watcher.removed(path.join(main, 'partials', '_one.scss')); + assert.deepEqual(files, { + added: [], + changed: [path.join(main, 'one.scss')], + removed: [path.join(main, 'partials', '_one.scss')], + }); + }); + }); + + describe('when a file is removed from outside the graph', function() { + it('should return an empty array', function() { + var files = watcher.removed(path.join(sibling, 'partials', '_three.scss')); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + }); +}); From 72ab73b7988ae306d36c15043a1e034d226ecc0f Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Sat, 17 Jun 2017 19:45:16 +1000 Subject: [PATCH 057/286] Bump sass-graph@2.2.4 (#1958) - fixes issues with transitive dependency scss-tokenizer - allows graph to follow symlinks --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d3ac26619..a3bcbbc82 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "node-gyp": "^3.3.1", "npmlog": "^4.0.0", "request": "^2.79.0", - "sass-graph": "^2.1.1", + "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0" }, "devDependencies": { From 59fb078766d67d78a118159fba6cb3bd93fadf65 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 18 Jun 2017 18:07:28 +1000 Subject: [PATCH 058/286] Pass "follow" option to sass-graph Fixes #1952 Closes #1956 --- lib/watcher.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/watcher.js b/lib/watcher.js index 2d317b670..ab79cc16f 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -8,7 +8,8 @@ watcher.reset = function(opts) { config = clonedeep(opts || config || {}); var options = { loadPaths: config.includePath, - extensions: ['scss', 'sass', 'css'] + extensions: ['scss', 'sass', 'css'], + follow: config.follow, }; if (config.directory) { From 74179fcf07073d4494fdb65fcc09bccc547eef3c Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 18 Jun 2017 18:12:39 +1000 Subject: [PATCH 059/286] Fix test exclusion --- test/watcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/watcher.js b/test/watcher.js index 7c9135e72..2b3c95057 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -4,7 +4,7 @@ var assert = require('assert'), temp = require('unique-temp-dir'), watcher = require('../lib/watcher'); -describe.only('watcher', function() { +describe('watcher', function() { var main, sibling; var origin = path.join(__dirname, 'fixtures', 'watcher'); From 4f9b667b462cbd1133e7e8802dacb45d0fe5c640 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 27 Jun 2017 16:42:39 -0400 Subject: [PATCH 060/286] docs: Replace VS 2013 requirement with node-gyp (#2028) Closes #2027 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 229356260..74b76de4e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ npm install node-sass Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager) to install NodeJS so that `#!/usr/bin/env node` correctly resolved. -Compiling versions 0.9.4 and above on Windows machines requires [Visual Studio 2013 WD](https://www.microsoft.com/en-us/download/details.aspx?id=44914). If you have multiple VS versions, use ```npm install``` with the ```--msvs_version=2013``` flag also use this flag when rebuilding the module with node-gyp or nw-gyp. +Compiling on Windows machines requires the [node-gyp prerequisits](https://github.com/nodejs/node-gyp#on-windows). **Having installation troubles? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md).** From a219c8cf031be5b2d73f3a70355a038056409204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Cie=C5=9Blak?= Date: Fri, 30 Jun 2017 23:19:47 +0200 Subject: [PATCH 061/286] Fix broken link to CONTRIBUTING.md Thanks to @drook for reporting it via #2033 --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 69c235e15..ba06a04af 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,7 +3,7 @@ Before opening an issue: - [Search for duplicate or closed issues](https://github.com/sass/node-sass/issues?utf8=%E2%9C%93&q=is%3Aissue) - [Validate](http://sassmeister.com/) that it runs with both Ruby Sass and LibSass - Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs -- Read the [contributing guidelines](https://github.com/sass/node-sass/blob/master/CONTRIBUTING.md) +- Read the [contributing guidelines](https://github.com/sass/node-sass/blob/master/.github/CONTRIBUTING.md) When reporting an bug, **you must provide this information**: From 5392533e17363d1c00affd2e5c756c8e83003c69 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 4 Jul 2017 15:51:35 -0400 Subject: [PATCH 062/286] chore: add NPM 5 troubleshooting (#2036) --- TROUBLESHOOTING.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 3b6bcfd38..dcab5a1d3 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -8,6 +8,7 @@ This document covers some common node-sass issues and how to resolve them. You s - [Assertion failed: (handle->flags & UV_CLOSING), function uv__finish_close](#assertion-failed-handle-flags-&-uv_closing-function-uv__finish_close) - [Cannot find module '/root/<...>/install.js'](#cannot-find-module-rootinstalljs) - [Linux](#linux) + - [npm 5](#npm-5) - [Glossary](#glossary) - [Which node runtime am I using?](#which-node-runtime-am-i-using) - [Which version of node am I using?](#which-version-of-node-am-i-using) @@ -42,6 +43,25 @@ $ sudo npm install --unsafe-perm -g node-sass If this didn't solve your problem please open an issue with the output from [our debugging script](#debugging-installation-issues). +### npm 5 + +Some users upgrading from previous versions of npm have found conflicts with old lock file formats. This may be show up as a URL instead of the actual version number when downloading the binary. EX: + +```console +Downloading binary from https://github.com/sass/node-sass/releases/download/vhttps://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz/win32-x64-57_binding.node +Cannot download "https://github.com/sass/node-sass/releases/download/vhttps://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz/win32-x64-57_binding.node": + +HTTP error 404 Not Found +``` + +The easiest way to get around this is just to cleanup the npm files and reinstall. + +```console +rm -rf node_modules +rm package-lock.json +npm cache clean +npm install +``` ## Glossary From 4a0f3d028c81ab355b9d2b75b76da14e7bf13725 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 8 Aug 2017 22:16:55 +0300 Subject: [PATCH 063/286] docs: README formatting and spelling fixes (#2063) Fix a couple of typos and minor Markdown tweaks. --- README.md | 113 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 74b76de4e..4cedd9a0e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ It allows you to natively compile .scss files to css at incredible speed and aut Find it on npm: -Follow @nodesass on twitter for release updates: https://twitter.com/nodesass +Follow @nodesass on twitter for release updates: ## Install @@ -40,7 +40,7 @@ npm install node-sass Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager) to install NodeJS so that `#!/usr/bin/env node` correctly resolved. -Compiling on Windows machines requires the [node-gyp prerequisits](https://github.com/nodejs/node-gyp#on-windows). +Compiling on Windows machines requires the [node-gyp prerequisites](https://github.com/nodejs/node-gyp#on-windows). **Having installation troubles? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md).** @@ -60,16 +60,21 @@ var result = sass.renderSync({ ``` ## Options + ### file -Type: `String` -Default: `null` + +* Type: `String` +* Default: `null` + **Special**: `file` or `data` must be specified Path to a file for [LibSass] to render. ### data -Type: `String` -Default: `null` + +* Type: `String` +* Default: `null` + **Special**: `file` or `data` must be specified A string to pass to [LibSass] to render. It is recommended that you use `includePaths` in conjunction with this so that [LibSass] can find files when using the `@import` directive. @@ -78,10 +83,11 @@ A string to pass to [LibSass] to render. It is recommended that you use `include **This is an experimental LibSass feature. Use with caution.** -Type: `Function | Function[]` signature `function(url, prev, done)` -Default: `undefined` +* Type: `Function | Function[]` signature `function(url, prev, done)` +* Default: `undefined` Function Parameters and Information: + * `url (String)` - the path in import **as-is**, which [LibSass] encountered * `prev (String)` - the previously resolved path * `done (Function)` - a callback function to invoke on async completion, takes an object literal containing @@ -115,13 +121,16 @@ Starting from v3.0.0: `functions` is an `Object` that holds a collection of custom functions that may be invoked by the sass files being compiled. They may take zero or more input parameters and must return a value either synchronously (`return ...;`) or asynchronously (`done();`). Those parameters will be instances of one of the constructors contained in the `require('node-sass').types` hash. The return value must be of one of these types as well. See the list of available types below: #### types.Number(value [, unit = ""]) + * `getValue()`/ `setValue(value)` : gets / sets the numerical portion of the number * `getUnit()` / `setUnit(unit)` : gets / sets the unit portion of the number #### types.String(value) + * `getValue()` / `setValue(value)` : gets / sets the enclosed string #### types.Color(r, g, b [, a = 1.0]) or types.Color(argb) + * `getR()` / `setR(value)` : red component (integer from `0` to `255`) * `getG()` / `setG(value)` : green component (integer from `0` to `255`) * `getB()` / `setB(value)` : blue component (integer from `0` to `255`) @@ -136,21 +145,25 @@ var Color = require('node-sass').types.Color, ``` #### types.Boolean(value) + * `getValue()` : gets the enclosed boolean * `types.Boolean.TRUE` : Singleton instance of `types.Boolean` that holds "true" * `types.Boolean.FALSE` : Singleton instance of `types.Boolean` that holds "false" #### types.List(length [, commaSeparator = true]) + * `getValue(index)` / `setValue(index, value)` : `value` must itself be an instance of one of the constructors in `sass.types`. * `getSeparator()` / `setSeparator(isComma)` : whether to use commas as a separator * `getLength()` #### types.Map(length) + * `getKey(index)` / `setKey(index, value)` * `getValue(index)` / `setValue(index, value)` * `getLength()` #### types.Null() + * `types.Null.NULL` : Singleton instance of `types.Null`. #### Example @@ -174,48 +187,57 @@ sass.renderSync({ ``` ### includePaths -Type: `Array` -Default: `[]` + +* Type: `Array` +* Default: `[]` An array of paths that [LibSass] can look in to attempt to resolve your `@import` declarations. When using `data`, it is recommended that you use this. ### indentedSyntax -Type: `Boolean` -Default: `false` + +* Type: `Boolean` +* Default: `false` `true` values enable [Sass Indented Syntax](http://sass-lang.com/documentation/file.INDENTED_SYNTAX.html) for parsing the data string or file. __Note:__ node-sass/libsass will compile a mixed library of scss and indented syntax (.sass) files with the Default setting (false) as long as .sass and .scss extensions are used in filenames. ### indentType (>= v3.0.0) -Type: `String` -Default: `space` + +* Type: `String` +* Default: `space` Used to determine whether to use space or tab character for indentation. ### indentWidth (>= v3.0.0) -Type: `Number` -Default: `2` -Maximum: `10` + +* Type: `Number` +* Default: `2` +* Maximum: `10` Used to determine the number of spaces or tabs to be used for indentation. ### linefeed (>= v3.0.0) -Type: `String` -Default: `lf` + +* Type: `String` +* Default: `lf` Used to determine whether to use `cr`, `crlf`, `lf` or `lfcr` sequence for line break. ### omitSourceMapUrl -Type: `Boolean` -Default: `false` + +* Type: `Boolean` +* Default: `false` + **Special:** When using this, you should also specify `outFile` to avoid unexpected behavior. `true` values disable the inclusion of source map information in the output file. ### outFile -Type: `String | null` -Default: `null` + +* Type: `String | null` +* Default: `null` + **Special:** Required when `sourceMap` is a truthy value Specify the intended location of the output file. Strongly recommended when outputting source maps so that they can properly refer back to their intended files. @@ -223,6 +245,7 @@ Specify the intended location of the output file. Strongly recommended when outp **Attention** enabling this option will **not** write the file on disk for you, it's for internal reference purpose only (to generate the map for example). Example on how to write it on the disk + ```javascript sass.render({ ... @@ -241,53 +264,63 @@ sass.render({ ``` ### outputStyle -Type: `String` -Default: `nested` -Values: `nested`, `expanded`, `compact`, `compressed` + +* Type: `String` +* Default: `nested` +* Values: `nested`, `expanded`, `compact`, `compressed` Determines the output format of the final CSS style. ### precision -Type: `Integer` -Default: `5` + +* Type: `Integer` +* Default: `5` Used to determine how many digits after the decimal will be allowed. For instance, if you had a decimal number of `1.23456789` and a precision of `5`, the result will be `1.23457` in the final CSS. ### sourceComments -Type: `Boolean` -Default: `false` + +* Type: `Boolean` +* Default: `false` `true` Enables the line number and file where a selector is defined to be emitted into the compiled CSS as a comment. Useful for debugging, especially when using imports and mixins. ### sourceMap -Type: `Boolean | String | undefined` -Default: `undefined` + +* Type: `Boolean | String | undefined` +* Default: `undefined` + **Special:** Setting the `sourceMap` option requires also setting the `outFile` option Enables the outputting of a source map during `render` and `renderSync`. When `sourceMap === true`, the value of `outFile` is used as the target output location for the source map. When `typeof sourceMap === "string"`, the value of `sourceMap` will be used as the writing location for the file. ### sourceMapContents -Type: `Boolean` -Default: `false` + +* Type: `Boolean` +* Default: `false` `true` includes the `contents` in the source map information ### sourceMapEmbed -Type: `Boolean` -Default: `false` + +* Type: `Boolean` +* Default: `false` `true` embeds the source map as a data URI ### sourceMapRoot -Type: `String` -Default: `undefined` + +* Type: `String` +* Default: `undefined` the value will be emitted as `sourceRoot` in the source map information ## `render` Callback (>= v3.0.0) + node-sass supports standard node style asynchronous callbacks with the signature of `function(err, result)`. In error conditions, the `error` argument is populated with the error object. In success conditions, the `result` object is populated with an object describing the result of the render call. ### Error Object + * `message` (String) - The error message. * `line` (Number) - The line number of error. * `column` (Number) - The column number of error. @@ -295,6 +328,7 @@ node-sass supports standard node style asynchronous callbacks with the signature * `file` (String) - The filename of error. In case `file` option was not set (in favour of `data`), this will reflect the value `stdin`. ### Result Object + * `css` (Buffer) - The compiled CSS. Write this to a file, or serve it out as needed. * `map` (Buffer) - The source map * `stats` (Object) - An object containing information about the compile. It contains the following keys: @@ -359,7 +393,7 @@ var result = sass.renderSync({ // this.options contains this options hash someAsyncFunction(url, prev, function(result){ done({ - file: result.path, // only one of them is required, see section Sepcial Behaviours. + file: result.path, // only one of them is required, see section Special Behaviours. contents: result.data }); }); @@ -474,6 +508,7 @@ The interface for command-line usage is fairly simplistic at this stage, as seen Output will be sent to stdout if the `--output` flag is omitted. ### Usage + `node-sass [options] [output]` Or: `cat | node-sass > output` From 9e18803a3dfefd2a8941bb40bbb094ade03ceeca Mon Sep 17 00:00:00 2001 From: AJ Hsu Date: Thu, 5 Oct 2017 14:28:39 -0500 Subject: [PATCH 064/286] docs: Fix broken anchor for Functions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cedd9a0e..0bdfe0730 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Starting from v3.0.0: return new Error('nothing to do here'); ``` -* importer can be an array of functions, which will be called by LibSass in the order of their occurrence in array. This helps user specify special importer for particular kind of path (filesystem, http). If an importer does not want to handle a particular path, it should return `null`. See [functions section](#functions--v300) for more details on Sass types. +* importer can be an array of functions, which will be called by LibSass in the order of their occurrence in array. This helps user specify special importer for particular kind of path (filesystem, http). If an importer does not want to handle a particular path, it should return `null`. See [functions section](#functions--v300---experimental) for more details on Sass types. ### functions (>= v3.0.0) - _experimental_ From 6ed334be3f0590e7dbdf4de7597b7d64e708acf2 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Fri, 6 Oct 2017 14:56:46 +1100 Subject: [PATCH 065/286] Add workaround for Node < 4 support with v4.x --- TROUBLESHOOTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index dcab5a1d3..5f7b0d9cf 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -16,6 +16,7 @@ This document covers some common node-sass issues and how to resolve them. You s - [Windows](#windows) - [Linux/OSX](#linuxosx) - [Using node-sass with Visual Studio 2015 Task Runner.](#using-node-sass-with-visual-studio-2015-task-runner) +- [Installing node-sass 4.x with Node < 4](#installing-node-sass-4x-with-node--4) ## Installation problems @@ -254,3 +255,7 @@ If this still produces an error please open an issue with the output from these If you are using node-sass with VS2015 Task Runner Explorer, you need to make sure that the version of node.js (or io.js) is same as the one you installed node-sass with. This is because for each node.js runtime modules version (`node -p process.versions.modules`), we have a separate build of native binary. See [#532](https://github.com/sass/node-sass/issues/532). Alternatively, if you prefer using system-installed node.js (supposedly higher version than one bundles with VS2015), you may want to point Visual Studio 2015 to use it for task runner jobs by following the guidelines available at: http://blogs.msdn.com/b/webdev/archive/2015/03/19/customize-external-web-tools-in-visual-studio-2015.aspx. + +### Installing node-sass 4.x with Node < 4 + +See the discussion in [this comment](https://github.com/sass/node-sass/issues/2100#issuecomment-334651235) for a workaround. As of node-sass@v5 only Node 6 and above will be offically supported. From e934a55d5a0433e8e1d483a485c4717c9a416b6c Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Fri, 6 Oct 2017 16:00:29 +1100 Subject: [PATCH 066/286] Remove Node 0.10 and 0.12 from CI config (#2112) * Remove Node 0.10 and 0.12 from travis config These will always fails now due to https://github.com/sass/node-sass/issues/2100. We manually verify support when building release binaries so there's no value in failing CI. It's scary to contributors. * Remove Node 0.10 and 0.12 from appveyor config These will always fails now due to https://github.com/sass/node-sass/issues/2100. We manually verify support when building release binaries so there's no value in failing CI. It's scary to contributors. * Link to the troubleshoot guide for Node < 4 installation issues Help people search for keywords because noone reads the help guides anyway. --- .travis.yml | 6 ------ README.md | 6 ++++++ appveyor.yml | 24 ------------------------ 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 28693b22f..5fa910f3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,12 +35,6 @@ jobs: - stage: platform-test node_js: "lts/argon" os: osx - - stage: platform-test - node_js: "0.12" - os: linux - - stage: platform-test - node_js: "0.10" - os: linux addons: apt: diff --git a/README.md b/README.md index 0bdfe0730..80ffd2c17 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ Some users have reported issues installing on Ubuntu due to `node` being registe Compiling on Windows machines requires the [node-gyp prerequisites](https://github.com/nodejs/node-gyp#on-windows). +If you're seeing the following error? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md#installing-node-sass-4x-with-node--4).** + +``` +SyntaxError: Use of const in strict mode. +``` + **Having installation troubles? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md).** ## Usage diff --git a/appveyor.yml b/appveyor.yml index 87dc0ed8c..0a41bf3d6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,24 +34,6 @@ environment: SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: - - nodejs_version: 0.10 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 0.12 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 1.0 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 1 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 2 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 3 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 4 GYP_MSVS_VERSION: 2013 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 @@ -133,12 +115,6 @@ environment: SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: - - nodejs_version: 0.10 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 0.12 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 4 GYP_MSVS_VERSION: 2013 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 From aa5bbbf20917d145bc697fcaf81b11fb459fa13e Mon Sep 17 00:00:00 2001 From: Folusho Oladipo Date: Mon, 30 Oct 2017 16:00:03 +0100 Subject: [PATCH 067/286] improve grammar/clarity of instructions --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 80ffd2c17..3ae258f94 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,11 @@ Follow @nodesass on twitter for release updates: npm install node-sass ``` -Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager) to install NodeJS so that `#!/usr/bin/env node` correctly resolved. +Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager) to install NodeJS so that `#!/usr/bin/env node` correctly resolves. Compiling on Windows machines requires the [node-gyp prerequisites](https://github.com/nodejs/node-gyp#on-windows). -If you're seeing the following error? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md#installing-node-sass-4x-with-node--4).** +Are you seeing the following error? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md#installing-node-sass-4x-with-node--4).** ``` SyntaxError: Use of const in strict mode. @@ -74,7 +74,7 @@ var result = sass.renderSync({ **Special**: `file` or `data` must be specified -Path to a file for [LibSass] to render. +Path to a file for [LibSass] to compile. ### data @@ -83,7 +83,7 @@ Path to a file for [LibSass] to render. **Special**: `file` or `data` must be specified -A string to pass to [LibSass] to render. It is recommended that you use `includePaths` in conjunction with this so that [LibSass] can find files when using the `@import` directive. +A string to pass to [LibSass] to compile. It is recommended that you use `includePaths` in conjunction with this so that [LibSass] can find files when using the `@import` directive. ### importer (>= v2.0.0) - _experimental_ From f2a09eb02c44312ad556041799fe80f40e4d6d5f Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Fri, 22 Sep 2017 15:39:58 -0700 Subject: [PATCH 068/286] Add Isolated Memory Leak Scenarios * boolean * function-bridge * map * string --- memory-tests/_measure.js | 12 ++++++++++++ memory-tests/boolean.js | 6 ++++++ memory-tests/function-bridge.js | 15 +++++++++++++++ memory-tests/map.js | 17 +++++++++++++++++ memory-tests/string.js | 6 ++++++ 5 files changed, 56 insertions(+) create mode 100644 memory-tests/_measure.js create mode 100644 memory-tests/boolean.js create mode 100644 memory-tests/function-bridge.js create mode 100644 memory-tests/map.js create mode 100644 memory-tests/string.js diff --git a/memory-tests/_measure.js b/memory-tests/_measure.js new file mode 100644 index 000000000..c220872dd --- /dev/null +++ b/memory-tests/_measure.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = function iterateAndMeasure(fn, mod = 1000000) { + let count = 0; + while (true) { + count++; + fn(); + if (count % mod === 0) { + console.log(process.memoryUsage().rss / 1000000); + } + } +} diff --git a/memory-tests/boolean.js b/memory-tests/boolean.js new file mode 100644 index 000000000..0da6cb958 --- /dev/null +++ b/memory-tests/boolean.js @@ -0,0 +1,6 @@ +'use strict'; + +var types = require('../').types; +var iterateAndMeasure = require('./_measure'); + +iterateAndMeasure(function() { return types.Boolean(true).getValue(); }); diff --git a/memory-tests/function-bridge.js b/memory-tests/function-bridge.js new file mode 100644 index 000000000..72ef96620 --- /dev/null +++ b/memory-tests/function-bridge.js @@ -0,0 +1,15 @@ +"use strict"; + +var sass = require("../"); +var iterateAndMeasure = require('./_measure'); + +iterateAndMeasure(function() { + sass.renderSync({ + data: '#{headings()} { color: #08c; }', + functions: { + 'headings()': function() { + return new sass.types.String('hi'); + } + } + }); +}, 10000); diff --git a/memory-tests/map.js b/memory-tests/map.js new file mode 100644 index 000000000..4cd047144 --- /dev/null +++ b/memory-tests/map.js @@ -0,0 +1,17 @@ +'use strict'; + +var types = require('../').types; +var iterateAndMeasure = require('./_measure'); + +iterateAndMeasure(function() { + var key = new types.String('the-key'); + var value = new types.String('the-value'); + + var map = new types.Map(1); + + map.setKey(0, key); + map.setValue(0, value); + + map.getKey(0); +}, 100000); + diff --git a/memory-tests/string.js b/memory-tests/string.js new file mode 100644 index 000000000..ee645043c --- /dev/null +++ b/memory-tests/string.js @@ -0,0 +1,6 @@ +'use strict'; + +var types = require('../').types; +var iterateAndMeasure = require('./_measure'); + +iterateAndMeasure(function() { return new types.String('hi'); }); From 930c2d9fa7657578a68b0472cb1186665832d32c Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Tue, 31 Oct 2017 10:46:27 -0700 Subject: [PATCH 069/286] Backfill some test for sass.types.* --- test/types.js | 643 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 643 insertions(+) create mode 100644 test/types.js diff --git a/test/types.js b/test/types.js new file mode 100644 index 000000000..b1863af3b --- /dev/null +++ b/test/types.js @@ -0,0 +1,643 @@ +/*eslint new-cap: ["error", { "capIsNew": false }]*/ +'use strict'; + +var assert = require('assert'); +var sass = require('../'); + +describe('sass.types', function() { + describe('Boolean', function() { + it('exists', function() { + assert(sass.types.Boolean); + }); + + it('names the constructor correctly', function() { + assert.equal(sass.types.Boolean.name, 'SassBoolean'); + }); + + it('supports call constructor', function() { + var t = sass.types.Boolean(true); + assert.equal(t.toString(), '[object SassBoolean]'); + + var f = sass.types.Boolean(false); + assert.equal(f.toString(), '[object SassBoolean]'); + }); + + it('has true and false singletons', function() { + assert.equal(sass.types.Boolean(true), sass.types.Boolean(true)); + assert.equal(sass.types.Boolean(false), sass.types.Boolean(false)); + assert.notEqual(sass.types.Boolean(false), sass.types.Boolean(true)); + assert.equal(sass.types.Boolean(true), sass.types.Boolean.TRUE); + assert.equal(sass.types.Boolean(false), sass.types.Boolean.FALSE); + }); + + it('supports DOES NOT support new constructor', function() { + assert.throws(function() { + new sass.types.Boolean(true); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Cannot instantiate SassBoolean'); + return true; + }); + }); + + it('throws with incorrect constructor args', function() { + assert.throws(function() { + sass.types.Boolean(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected one boolean argument'); + return true; + }); + + [1, 2, '', 'hi', {}, []].forEach(function(arg) { + assert.throws(function() { + sass.types.Boolean(arg); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected one boolean argument'); + return true; + }); + }); + + assert.throws(function() { + sass.types.Boolean(true, false); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected one boolean argument'); + return true; + }); + }); + + it('implements getValue', function() { + var t = sass.types.Boolean(true); + assert.equal(typeof t.getValue, 'function'); + assert.equal(t.getValue(), true); + + var f = sass.types.Boolean(false); + assert.equal(typeof f.getValue, 'function'); + assert.equal(f.getValue(), false); + }); + }); + + describe('Color', function() { + it('exists', function() { + assert(sass.types.Color); + }); + + it('names the constructor correctly', function() { + assert.equal(sass.types.Color.name, 'SassColor'); + }); + + it('supports call constructor', function() { + var t = sass.types.Color(); + assert.equal(t.toString(), '[object SassColor]'); + }); + + it('supports new constructor', function() { + var t = new sass.types.Color(1); + assert.equal(t.toString(), '[object SassColor]'); + }); + + it('supports variadic constructor args', function() { + var a = new sass.types.Color(); + + assert.equal(a.getR(), 0); + assert.equal(a.getG(), 0); + assert.equal(a.getB(), 0); + assert.equal(a.getA(), 1); + + var b = new sass.types.Color(1); + + assert.equal(b.getR(), 0); + assert.equal(b.getG(), 0); + assert.equal(b.getB(), 1); + assert.equal(b.getA(), 0); // why ? + + assert.throws(function() { + new sass.types.Color(1, 2); + }, function(error) { + // assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Constructor should be invoked with either 0, 1, 3 or 4 arguments.'); + return true; + }); + + var c = new sass.types.Color(1, 2, 3); + + assert.equal(c.getR(), 1); + assert.equal(c.getG(), 2); + assert.equal(c.getB(), 3); + assert.equal(c.getA(), 1); + + var d = new sass.types.Color(1, 2, 3, 4); + + assert.equal(d.getR(), 1); + assert.equal(d.getG(), 2); + assert.equal(d.getB(), 3); + assert.equal(d.getA(), 4); + + assert.throws(function() { + new sass.types.Color(1, 2, 3, 4, 5); + }, function(error) { + // assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Constructor should be invoked with either 0, 1, 3 or 4 arguments.'); + return true; + }); + }); + + it('supports get{R,G,B,A} and set{R,G,B,A}', function() { + var c = new sass.types.Color(); + + assert.equal(c.getR(), 0); + assert.equal(c.getG(), 0); + assert.equal(c.getB(), 0); + assert.equal(c.getA(), 1); + + assert.equal(c.setR(1), undefined); + + assert.equal(c.getR(), 1); + assert.equal(c.getG(), 0); + assert.equal(c.getB(), 0); + assert.equal(c.getA(), 1); + + assert.equal(c.setG(1), undefined); + + assert.equal(c.getR(), 1); + assert.equal(c.getG(), 1); + assert.equal(c.getB(), 0); + assert.equal(c.getA(), 1); + + assert.equal(c.setB(1), undefined); + + assert.equal(c.getR(), 1); + assert.equal(c.getG(), 1); + assert.equal(c.getB(), 1); + assert.equal(c.getA(), 1); + + assert.equal(c.setA(0), undefined); + + assert.equal(c.getR(), 1); + assert.equal(c.getG(), 1); + assert.equal(c.getB(), 1); + assert.equal(c.getA(), 0); + }); + + it('throws with incorrect set{R,G,B,A} arguments', function() { + var c = new sass.types.Color(); + + function assertJustOneArgument(cb) { + assert.throws(function() { + cb(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + + return true; + }); + } + + function assertNumberArgument(arg, cb) { + assert.throws(function() { + cb(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Supplied value should be a number'); + + return true; + }, 'argument was: ' + arg); + } + + assertJustOneArgument(function() { c.setR(); }); + assertJustOneArgument(function() { c.setG(); }); + assertJustOneArgument(function() { c.setB(); }); + assertJustOneArgument(function() { c.setA(); }); + + assertJustOneArgument(function() { c.setR(1, 2); }); + assertJustOneArgument(function() { c.setG(1, 2); }); + assertJustOneArgument(function() { c.setB(1, 2); }); + assertJustOneArgument(function() { c.setA(1, 2); }); + + [true, false, '0', '1', '', 'omg', {}, []].forEach(function(arg) { + assertNumberArgument(arg, function() { c.setR(arg); }); + assertNumberArgument(arg, function() { c.setG(arg); }); + assertNumberArgument(arg, function() { c.setB(arg); }); + assertNumberArgument(arg, function() { c.setA(arg); }); + }); + }); + }); + + describe('Error', function() { + it('exists', function() { + assert(sass.types.Error); + }); + + it('has a correctly named constructor', function() { + assert.equal(sass.types.Error.name, 'SassError'); + }); + + it('supports call constructor', function() { + var e = sass.types.Error('Such Error'); + assert.ok(e instanceof sass.types.Error); + assert.equal(e.toString(), '[object SassError]'); + + // TODO: I'm not sure this object works well, it likely needs to be fleshed out more... + }); + + it('supports new constructor', function() { + var e = new sass.types.Error('Such Error'); + assert.ok(e instanceof sass.types.Error); + assert.equal(e.toString(), '[object SassError]'); + // TODO: I'm not sure this object works well, it likely needs to be fleshed out more... + }); + }); + + describe('List', function() { + it('exists', function() { + assert(sass.types.List); + }); + + it('has a corectly named constructor', function() { + assert.equal(sass.types.List.name, 'SassList'); + }); + + it('support call constructor', function() { + var list = sass.types.List(); + assert.ok(list instanceof sass.types.List); + assert.equal(list.toString(), '[object SassList]'); + }); + + it('support new constructor', function() { + var list = new sass.types.List(); + assert.ok(list instanceof sass.types.List); + assert.equal(list.toString(), '[object SassList]'); + }); + + it('support variadic constructor', function() { + var a = new sass.types.List(); + assert.equal(a.getLength(), 0); + assert.equal(a.getSeparator(), true); + var b = new sass.types.List(1); + assert.equal(b.getSeparator(), true); + assert.equal(b.getLength(), 1); + var c = new sass.types.List(1, true); + assert.equal(b.getLength(), 1); + assert.equal(c.getSeparator(), true); + var d = new sass.types.List(1, false); + assert.equal(b.getLength(), 1); + assert.equal(d.getSeparator(), false); + var e = new sass.types.List(1, true, 2); + assert.equal(b.getLength(), 1); + assert.equal(e.getSeparator(), true); + + assert.throws(function() { + new sass.types.List('not-a-number'); + }, function(error) { + // TODO: TypeError + assert.equal(error.message, 'First argument should be an integer.'); + return true; + }); + + assert.throws(function() { + new sass.types.List(1, 'not-a-boolean'); + }, function(error) { + // TODO: TypeError + assert.equal(error.message, 'Second argument should be a boolean.'); + return true; + }); + }); + + it('supports {get,set}Separator', function() { + var a = new sass.types.List(); + assert.equal(a.getSeparator(), true); + assert.equal(a.setSeparator(true), undefined); + assert.equal(a.getSeparator(), true); + assert.equal(a.setSeparator(false), undefined); + assert.equal(a.getSeparator(), false); + + assert.throws(function() { + a.setSeparator(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + return true; + }); + + [1, '', [], {}].forEach(function(arg) { + assert.throws(function() { + a.setSeparator(arg); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Supplied value should be a boolean'); + return true; + }, 'setSeparator(' + arg + ')'); + }); + }); + + it('supports setValue and getValue', function() { + var a = new sass.types.List(); + + assert.throws(function() { + a.getValue(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + + return true; + }); + + ['hi', [], {}].forEach(function(arg) { + assert.throws(function() { + a.getValue(arg); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Supplied index should be an integer'); + + return true; + }, 'getValue(' + arg + ')'); + }); + + assert.throws(function() { + a.getValue(0); + }, function(error) { + assert.ok(error instanceof RangeError); + assert.equal(error.message, 'Out of bound index'); + + return true; + }); + + assert.throws(function() { + a.getValue(-1); + }, function(error) { + assert.ok(error instanceof RangeError); + assert.equal(error.message, 'Out of bound index'); + + return true; + }); + + assert.throws(function() { + a.setValue(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected two arguments'); + return true; + }); + + assert.throws(function() { + a.setValue(1); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected two arguments'); + return true; + }); + + assert.throws(function() { + a.setValue(0, 'no-a-sass-value'); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Supplied value should be a SassValue object'); + return true; + }); + }); + + // TODO: more complex set/get value scenarios + }); + + describe('Map', function() { + it('exists', function() { + assert(sass.types.Map); + }); + + it('has a correctly named constructor', function() { + assert.equal(sass.types.Map.name, 'SassMap'); + }); + + it('supports call constructor', function() { + var x = sass.types.Map(); + assert.equal(x.toString(), '[object SassMap]'); + }); + + it('supports new constructor', function() { + var x = new sass.types.Map(); + assert.equal(x.toString(), '[object SassMap]'); + }); + + it('supports an optional constructor argument', function() { + var x = new sass.types.Map(); + var y = new sass.types.Map(1); + var z = new sass.types.Map(2, 3); + + assert.throws(function() { + new sass.types.Map('OMG'); + }, function(error) { + assert.equal(error.message, 'First argument should be an integer.'); + // TODO: TypeError + + return true; + }); + + assert.equal(x.getLength(), 0); + assert.equal(y.getLength(), 1); + assert.equal(z.getLength(), 2); + }); + + it('supports length', function() { + var y = new sass.types.Map(1); + var z = new sass.types.Map(2); + + assert.equal(y.getLength(), 1); + assert.equal(z.getLength(), 2); + }); + + it('supports {get,set}Value {get,set}Key', function() { + var y = new sass.types.Map(1); + var omg = new sass.types.String('OMG'); + y.setValue(0, omg); + console.log(y.getValue(0)); + }); + }); + + describe('Null', function() { + it('exists', function() { + assert(sass.types.Null); + }); + + it('has a correctly named constructor', function() { + assert.equal(sass.types.Null.name, 'SassNull'); + }); + + it('does not support new constructor', function() { + assert.throws(function() { + new sass.types.Null(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Cannot instantiate SassNull'); + return true; + }); + }); + + it('supports call constructor (and is a singleton)', function() { + assert.equal(sass.types.Null(), sass.types.Null()); + assert.equal(sass.types.Null(), sass.types.Null.NULL); + }); + }); + + describe('Number', function() { + it('exists', function() { + assert(sass.types.Number); + }); + + it('has a correctly named constructor', function() { + assert.equal(sass.types.Number.name, 'SassNumber'); + }); + + it('supports new constructor', function() { + var number = new sass.types.Number(); + assert.equal(number.toString(), '[object SassNumber]'); + }); + + it('supports call constructor', function() { + var number = sass.types.Number(); + assert.equal(number.toString(), '[object SassNumber]'); + }); + + it('supports multiple constructor arguments', function() { + var a = new sass.types.Number(); + var b = new sass.types.Number(1); + var c = new sass.types.Number(2, 'px'); + + assert.throws(function() { + new sass.types.Number('OMG'); + }, function(error) { + // TODO: TypeError + assert.equal(error.message, 'First argument should be a number.'); + return true; + }); + + assert.throws(function() { + new sass.types.Number(1, 2); + }, function(error) { + // TODO: TypeError + assert.equal(error.message, 'Second argument should be a string.'); + return true; + }); + + assert.equal(a.getValue(), 0); + assert.equal(a.getUnit(), ''); + assert.equal(b.getValue(), 1); + assert.equal(b.getUnit(), ''); + assert.equal(c.getValue(), 2); + assert.equal(c.getUnit(), 'px'); + }); + + it('supports get{Unit,Value}, set{Unit,Value}', function() { + var number = new sass.types.Number(1, 'px'); + assert.equal(number.getValue(), 1); + assert.equal(number.getUnit(), 'px'); + + number.setValue(2); + assert.equal(number.getValue(), 2); + assert.equal(number.getUnit(), 'px'); + + number.setUnit('em'); + assert.equal(number.getValue(), 2); + assert.equal(number.getUnit(), 'em'); + + assert.throws(function() { + number.setValue('OMG'); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Supplied value should be a number'); + return true; + }); + + assert.throws(function() { + number.setValue(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + return true; + }); + + assert.throws(function() { + number.setUnit(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + return true; + }); + + assert.throws(function() { + number.setUnit(1); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Supplied value should be a string'); + return true; + }); + }); + }); + + describe('String', function() { + it('exists', function() { + assert(sass.types.String); + }); + + it('has a properly named constructor', function() { + assert.equal(sass.types.String.name, 'SassString'); + }); + + it('supports call constructor', function() { + var x = sass.types.String('OMG'); + + assert.equal(x.toString(), '[object SassString]'); + assert.equal(x.getValue(), 'OMG'); + }); + + it('supports new constructor', function() { + var x = new sass.types.String('OMG'); + + assert.equal(x.toString(), '[object SassString]'); + assert.equal(x.getValue(), 'OMG'); + }); + + it('supports multiple constructor arg combinations', function() { + new sass.types.String(); + new sass.types.String('OMG'); + new sass.types.String('OMG', 'NOPE'); + + [null, undefined, [], {}, function() { }].forEach(function(arg) { + assert.throws(function() { + new sass.types.String(arg); + }, function(error) { + // TODO: TypeError + assert.equal(error.message, 'Argument should be a string.'); + return true; + }); + }); + }); + + it('supports {get,set}Value', function() { + var x = new sass.types.String(); + + assert.equal(x.getValue(), ''); + assert.equal(x.setValue('hi'), undefined); + assert.equal(x.getValue(), 'hi'); + assert.equal(x.setValue('bye'), undefined); + assert.equal(x.getValue(), 'bye'); + + assert.throws(function() { + x.setValue(); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + return true; + }); + + assert.throws(function() { + x.setValue('hi', 'hi'); + }, function(error) { + assert.ok(error instanceof TypeError); + assert.equal(error.message, 'Expected just one argument'); + return true; + }); + }); + }); +}); From 4cf43ac99a8113a14d1c6fe0a999fb53606d5a51 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Tue, 31 Oct 2017 10:46:09 -0700 Subject: [PATCH 070/286] Remove dangling console.log from tests --- test/api.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/api.js b/test/api.js index e082c9714..a26254ad3 100644 --- a/test/api.js +++ b/test/api.js @@ -310,7 +310,6 @@ describe('api', function() { contents: '@import "b"' }); } else { - console.log(prev); assert.equal(prev, '/Users/me/sass/lib/a.scss'); done({ file: '/Users/me/sass/lib/b.scss', From 3052263f1d1cd7529fad68547f8d2bafc77d7c84 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Tue, 31 Oct 2017 10:39:30 -0700 Subject: [PATCH 071/286] [LEAK FIX] create_string must be paired with a free --- src/binding.cpp | 7 +++++-- src/callback_bridge.h | 10 +++++----- src/custom_importer_bridge.cpp | 6 ++++-- src/sass_types/number.cpp | 4 ++++ src/sass_types/string.cpp | 7 ++++++- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/binding.cpp b/src/binding.cpp index 422a832b5..4f2cfdaac 100644 --- a/src/binding.cpp +++ b/src/binding.cpp @@ -158,7 +158,9 @@ int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapp CustomFunctionBridge *bridge = new CustomFunctionBridge(callback, ctx_w->is_sync); ctx_w->function_bridges.push_back(bridge); - Sass_Function_Entry fn = sass_make_function(create_string(signature), sass_custom_function, bridge); + char* sig = create_string(signature); + Sass_Function_Entry fn = sass_make_function(sig, sass_custom_function, bridge); + free(sig); sass_function_set_list_entry(fn_list, i, fn); } @@ -267,7 +269,7 @@ NAN_METHOD(render) { struct Sass_Data_Context* dctx = sass_make_data_context(source_string); sass_context_wrapper* ctx_w = sass_make_context_wrapper(); - if (ExtractOptions(options, dctx, ctx_w, false, false) >= 0) { + if (ExtractOptions(options, dctx, ctx_w, false, false) >= 0) { int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback); @@ -290,6 +292,7 @@ NAN_METHOD(render_sync) { } sass_free_context_wrapper(ctx_w); + info.GetReturnValue().Set(result == 0); } diff --git a/src/callback_bridge.h b/src/callback_bridge.h index 2d6cb92f0..918cf8930 100644 --- a/src/callback_bridge.h +++ b/src/callback_bridge.h @@ -55,7 +55,7 @@ Nan::Persistent CallbackBridge::wrapper_constructor; template CallbackBridge::CallbackBridge(v8::Local callback, bool is_sync) : callback(new Nan::Callback(callback)), is_sync(is_sync) { - /* + /* * This is invoked from the main JavaScript thread. * V8 context is available. */ @@ -89,7 +89,7 @@ template T CallbackBridge::operator()(std::vector argv) { // argv.push_back(wrapper); if (this->is_sync) { - /* + /* * This is invoked from the main JavaScript thread. * V8 context is available. * @@ -110,7 +110,7 @@ T CallbackBridge::operator()(std::vector argv) { this->callback->Call(argv_v8.size(), &argv_v8[0]) ); } else { - /* + /* * This is invoked from the worker thread. * No V8 context and functions available. * Just wait for response from asynchronously @@ -141,7 +141,7 @@ template void CallbackBridge::dispatched_async_uv_callback(uv_async_t *req) { CallbackBridge* bridge = static_cast(req->data); - /* + /* * Function scheduled via uv_async mechanism, therefore * it is invoked from the main JavaScript thread. * V8 context is available. @@ -169,7 +169,7 @@ void CallbackBridge::dispatched_async_uv_callback(uv_async_t *req) { template NAN_METHOD(CallbackBridge::ReturnCallback) { - /* + /* * Callback function invoked by the user code. * It is invoked from the main JavaScript thread. * V8 context is available. diff --git a/src/custom_importer_bridge.cpp b/src/custom_importer_bridge.cpp index d4e1442ea..38d737c26 100644 --- a/src/custom_importer_bridge.cpp +++ b/src/custom_importer_bridge.cpp @@ -29,6 +29,7 @@ SassImportList CustomImporterBridge::post_process_return_value(v8::LocalIsObject()) { imports = sass_make_import_list(1); @@ -55,12 +57,12 @@ SassImportList CustomImporterBridge::post_process_return_value(v8::Local value, const char *msg) const { v8::Local checked; - if (value.ToLocal(&checked)) { + if (value.ToLocal(&checked)) { if (!checked->IsUndefined() && !checked->IsString()) { goto err; } else { return nullptr; - } + } } err: auto entry = sass_make_import_entry(0, 0, 0); diff --git a/src/sass_types/number.cpp b/src/sass_types/number.cpp index e98d1e302..d74a0d14d 100644 --- a/src/sass_types/number.cpp +++ b/src/sass_types/number.cpp @@ -23,6 +23,10 @@ namespace SassTypes } unit = create_string(raw_val[1]); + *out = sass_make_number(value, unit); + delete unit; + return *out; + } } diff --git a/src/sass_types/string.cpp b/src/sass_types/string.cpp index c72a93a95..9235f9e02 100644 --- a/src/sass_types/string.cpp +++ b/src/sass_types/string.cpp @@ -15,9 +15,14 @@ namespace SassTypes } value = create_string(raw_val[0]); + *out = sass_make_string(value); + delete value; + return *out; + + } else { + return *out = sass_make_string(value); } - return *out = sass_make_string(value); } void String::initPrototype(v8::Local proto) { From f6b0c7226460abbe8fc430b86c2af73cb7130664 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Tue, 31 Oct 2017 10:45:52 -0700 Subject: [PATCH 072/286] [LEAK FIX] Use Nan::ObjectWrap to handle memory management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nan::ObjectWrap Docs: https://github.com/nodejs/nan/blob/master/doc/object_wrappers.md#api_nan_object_wrap Prior to this, all wrapper Objects would never be deleted, allowing memory to grow unbounded. Example: The following, would leak WrapperObjects, and never ``` while (true) { new sass.types.String(‘I LEAK’); } ``` --- src/custom_function_bridge.cpp | 13 ++- src/sass_types/boolean.cpp | 20 ++-- src/sass_types/boolean.h | 3 +- src/sass_types/color.cpp | 16 +-- src/sass_types/factory.cpp | 11 +- src/sass_types/list.cpp | 10 +- src/sass_types/map.cpp | 15 +-- src/sass_types/null.cpp | 8 +- src/sass_types/number.cpp | 8 +- src/sass_types/sass_value_wrapper.h | 152 +++++++++++----------------- src/sass_types/string.cpp | 4 +- src/sass_types/value.h | 29 +++++- 12 files changed, 141 insertions(+), 148 deletions(-) diff --git a/src/custom_function_bridge.cpp b/src/custom_function_bridge.cpp index f0e49b6c8..f27c69545 100644 --- a/src/custom_function_bridge.cpp +++ b/src/custom_function_bridge.cpp @@ -4,10 +4,10 @@ #include "sass_types/factory.h" #include "sass_types/value.h" -Sass_Value* CustomFunctionBridge::post_process_return_value(v8::Local val) const { - SassTypes::Value *v_; - if ((v_ = SassTypes::Factory::unwrap(val))) { - return v_->get_sass_value(); +Sass_Value* CustomFunctionBridge::post_process_return_value(v8::Local _val) const { + SassTypes::Value *value = SassTypes::Factory::unwrap(_val); + if (value) { + return value->get_sass_value(); } else { return sass_make_error("A SassValue object was expected."); } @@ -17,7 +17,10 @@ std::vector> CustomFunctionBridge::pre_process_args(std::ve std::vector> argv = std::vector>(); for (void* value : in) { - argv.push_back(SassTypes::Factory::create(static_cast(value))->get_js_object()); + Sass_Value* x = static_cast(value); + SassTypes::Value* y = SassTypes::Factory::create(x); + + argv.push_back(y->get_js_object()); } return argv; diff --git a/src/sass_types/boolean.cpp b/src/sass_types/boolean.cpp index 401ed14fb..2d4793238 100644 --- a/src/sass_types/boolean.cpp +++ b/src/sass_types/boolean.cpp @@ -6,7 +6,9 @@ namespace SassTypes Nan::Persistent Boolean::constructor; bool Boolean::constructor_locked = false; - Boolean::Boolean(bool v) : value(v) {} + Boolean::Boolean(bool _value) { + value = sass_make_boolean(_value); + } Boolean& Boolean::get_singleton(bool v) { static Boolean instance_false(false), instance_true(true); @@ -15,7 +17,7 @@ namespace SassTypes v8::Local Boolean::get_constructor() { Nan::EscapableHandleScope scope; - v8::Local conslocal; + v8::Local conslocal; if (constructor.IsEmpty()) { v8::Local tpl = Nan::New(New); @@ -42,16 +44,15 @@ namespace SassTypes return scope.Escape(conslocal); } - Sass_Value* Boolean::get_sass_value() { - return sass_make_boolean(value); - } - v8::Local Boolean::get_js_object() { return Nan::New(this->js_object); } - NAN_METHOD(Boolean::New) { + v8::Local Boolean::get_js_boolean() { + return sass_boolean_get_value(this->value) ? Nan::True() : Nan::False(); + } + NAN_METHOD(Boolean::New) { if (info.IsConstructCall()) { if (constructor_locked) { return Nan::ThrowTypeError("Cannot instantiate SassBoolean"); @@ -67,9 +68,6 @@ namespace SassTypes } NAN_METHOD(Boolean::GetValue) { - Boolean *out; - if ((out = static_cast(Factory::unwrap(info.This())))) { - info.GetReturnValue().Set(Nan::New(out->value)); - } + info.GetReturnValue().Set(Boolean::Unwrap(info.This())->get_js_boolean()); } } diff --git a/src/sass_types/boolean.h b/src/sass_types/boolean.h index ac2a254ff..721a41c02 100644 --- a/src/sass_types/boolean.h +++ b/src/sass_types/boolean.h @@ -12,7 +12,6 @@ namespace SassTypes static Boolean& get_singleton(bool); static v8::Local get_constructor(); - Sass_Value* get_sass_value(); v8::Local get_js_object(); static NAN_METHOD(New); @@ -21,11 +20,11 @@ namespace SassTypes private: Boolean(bool); - bool value; Nan::Persistent js_object; static Nan::Persistent constructor; static bool constructor_locked; + v8::Local get_js_boolean(); }; } diff --git a/src/sass_types/color.cpp b/src/sass_types/color.cpp index f484b196e..57efab3da 100644 --- a/src/sass_types/color.cpp +++ b/src/sass_types/color.cpp @@ -62,19 +62,19 @@ namespace SassTypes } NAN_METHOD(Color::GetR) { - info.GetReturnValue().Set(sass_color_get_r(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_r(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::GetG) { - info.GetReturnValue().Set(sass_color_get_g(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_g(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::GetB) { - info.GetReturnValue().Set(sass_color_get_b(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_b(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::GetA) { - info.GetReturnValue().Set(sass_color_get_a(unwrap(info.This())->value)); + info.GetReturnValue().Set(sass_color_get_a(Color::Unwrap(info.This())->value)); } NAN_METHOD(Color::SetR) { @@ -86,7 +86,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_r(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_r(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Color::SetG) { @@ -98,7 +98,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_g(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_g(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Color::SetB) { @@ -110,7 +110,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_b(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_b(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Color::SetA) { @@ -122,6 +122,6 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_color_set_a(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_color_set_a(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } } diff --git a/src/sass_types/factory.cpp b/src/sass_types/factory.cpp index 98bda5d58..c650710c3 100644 --- a/src/sass_types/factory.cpp +++ b/src/sass_types/factory.cpp @@ -61,11 +61,12 @@ namespace SassTypes } Value* Factory::unwrap(v8::Local obj) { - // Todo: non-SassValue objects could easily fall under that condition, need to be more specific. - if (!obj->IsObject() || obj.As()->InternalFieldCount() != 1) { + if (obj->IsObject()) { + v8::Local v8_obj = obj.As(); + if (v8_obj->InternalFieldCount() == 1) { + return SassTypes::Value::Unwrap(v8_obj); + } + } return NULL; - } - - return static_cast(Nan::GetInternalFieldPointer(obj.As(), 0)); } } diff --git a/src/sass_types/list.cpp b/src/sass_types/list.cpp index cc94729a3..4c946ec90 100644 --- a/src/sass_types/list.cpp +++ b/src/sass_types/list.cpp @@ -47,7 +47,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied index should be an integer"); } - Sass_Value* list = unwrap(info.This())->value; + Sass_Value* list = List::Unwrap(info.This())->value; size_t index = Nan::To(info[0]).FromJust(); @@ -73,14 +73,14 @@ namespace SassTypes Value* sass_value = Factory::unwrap(info[1]); if (sass_value) { - sass_list_set_value(unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); + sass_list_set_value(List::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); } else { Nan::ThrowTypeError("A SassValue is expected as the list item"); } } NAN_METHOD(List::GetSeparator) { - info.GetReturnValue().Set(sass_list_get_separator(unwrap(info.This())->value) == SASS_COMMA); + info.GetReturnValue().Set(sass_list_get_separator(List::Unwrap(info.This())->value) == SASS_COMMA); } NAN_METHOD(List::SetSeparator) { @@ -92,10 +92,10 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a boolean"); } - sass_list_set_separator(unwrap(info.This())->value, Nan::To(info[0]).FromJust() ? SASS_COMMA : SASS_SPACE); + sass_list_set_separator(List::Unwrap(info.This())->value, Nan::To(info[0]).FromJust() ? SASS_COMMA : SASS_SPACE); } NAN_METHOD(List::GetLength) { - info.GetReturnValue().Set(Nan::New(sass_list_get_length(unwrap(info.This())->value))); + info.GetReturnValue().Set(Nan::New(sass_list_get_length(List::Unwrap(info.This())->value))); } } diff --git a/src/sass_types/map.cpp b/src/sass_types/map.cpp index ad147e6f9..ae4a260bf 100644 --- a/src/sass_types/map.cpp +++ b/src/sass_types/map.cpp @@ -37,7 +37,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied index should be an integer"); } - Sass_Value* map = unwrap(info.This())->value; + Sass_Value* map = Map::Unwrap(info.This())->value; size_t index = Nan::To(info[0]).FromJust(); @@ -63,14 +63,13 @@ namespace SassTypes Value* sass_value = Factory::unwrap(info[1]); if (sass_value) { - sass_map_set_value(unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); + sass_map_set_value(Map::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); } else { Nan::ThrowTypeError("A SassValue is expected as a map value"); } } NAN_METHOD(Map::GetKey) { - if (info.Length() != 1) { return Nan::ThrowTypeError("Expected just one argument"); } @@ -79,7 +78,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied index should be an integer"); } - Sass_Value* map = unwrap(info.This())->value; + Sass_Value* map = Map::Unwrap(info.This())->value; size_t index = Nan::To(info[0]).FromJust(); @@ -87,7 +86,9 @@ namespace SassTypes return Nan::ThrowRangeError(Nan::New("Out of bound index").ToLocalChecked()); } - info.GetReturnValue().Set(Factory::create(sass_map_get_key(map, Nan::To(info[0]).FromJust()))->get_js_object()); + SassTypes::Value* obj = Factory::create(sass_map_get_key(map, Nan::To(info[0]).FromJust())); + v8::Local js_obj = obj->get_js_object(); + info.GetReturnValue().Set(js_obj); } NAN_METHOD(Map::SetKey) { @@ -105,13 +106,13 @@ namespace SassTypes Value* sass_value = Factory::unwrap(info[1]); if (sass_value) { - sass_map_set_key(unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); + sass_map_set_key(Map::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); } else { Nan::ThrowTypeError("A SassValue is expected as a map key"); } } NAN_METHOD(Map::GetLength) { - info.GetReturnValue().Set(Nan::New(sass_map_get_length(unwrap(info.This())->value))); + info.GetReturnValue().Set(Nan::New(sass_map_get_length(Map::Unwrap(info.This())->value))); } } diff --git a/src/sass_types/null.cpp b/src/sass_types/null.cpp index 766cdf935..69f4c216d 100644 --- a/src/sass_types/null.cpp +++ b/src/sass_types/null.cpp @@ -6,7 +6,9 @@ namespace SassTypes Nan::Persistent Null::constructor; bool Null::constructor_locked = false; - Null::Null() {} + Null::Null() { + value = sass_make_null(); + } Null& Null::get_singleton() { static Null singleton_instance; @@ -37,10 +39,6 @@ namespace SassTypes return scope.Escape(conslocal); } - Sass_Value* Null::get_sass_value() { - return sass_make_null(); - } - v8::Local Null::get_js_object() { return Nan::New(this->js_object); } diff --git a/src/sass_types/number.cpp b/src/sass_types/number.cpp index d74a0d14d..d8d303ee0 100644 --- a/src/sass_types/number.cpp +++ b/src/sass_types/number.cpp @@ -41,11 +41,11 @@ namespace SassTypes } NAN_METHOD(Number::GetValue) { - info.GetReturnValue().Set(Nan::New(sass_number_get_value(unwrap(info.This())->value))); + info.GetReturnValue().Set(Nan::New(sass_number_get_value(Number::Unwrap(info.This())->value))); } NAN_METHOD(Number::GetUnit) { - info.GetReturnValue().Set(Nan::New(sass_number_get_unit(unwrap(info.This())->value)).ToLocalChecked()); + info.GetReturnValue().Set(Nan::New(sass_number_get_unit(Number::Unwrap(info.This())->value)).ToLocalChecked()); } NAN_METHOD(Number::SetValue) { @@ -58,7 +58,7 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a number"); } - sass_number_set_value(unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + sass_number_set_value(Number::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); } NAN_METHOD(Number::SetUnit) { @@ -70,6 +70,6 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a string"); } - sass_number_set_unit(unwrap(info.This())->value, create_string(info[0])); + sass_number_set_unit(Number::Unwrap(info.This())->value, create_string(info[0])); } } diff --git a/src/sass_types/sass_value_wrapper.h b/src/sass_types/sass_value_wrapper.h index 54eb16a02..52a351187 100644 --- a/src/sass_types/sass_value_wrapper.h +++ b/src/sass_types/sass_value_wrapper.h @@ -12,122 +12,90 @@ namespace SassTypes // Include this in any SassTypes::Value subclasses to handle all the heavy lifting of constructing JS // objects and wrapping sass values inside them template - class SassValueWrapper : public SassTypes::Value { - public: - static char const* get_constructor_name() { return "SassValue"; } + /* class SassValueWrapper : public SassTypes::Value { */ + class SassValueWrapper : public SassTypes::Value { + public: + static char const* get_constructor_name() { return "SassValue"; } - SassValueWrapper(Sass_Value*); - virtual ~SassValueWrapper(); + SassValueWrapper(Sass_Value* v) : Value(v) { } + v8::Local get_js_object(); - Sass_Value* get_sass_value(); - v8::Local get_js_object(); + static v8::Local get_constructor(); + static v8::Local get_constructor_template(); + static NAN_METHOD(New); + static Sass_Value *fail(const char *, Sass_Value **); - static v8::Local get_constructor(); - static v8::Local get_constructor_template(); - static NAN_METHOD(New); - static Sass_Value *fail(const char *, Sass_Value **); - - protected: - Sass_Value* value; - static T* unwrap(v8::Local); - - private: - static Nan::Persistent constructor; - Nan::Persistent js_object; - }; + /* private: */ + static Nan::Persistent constructor; + }; template - Nan::Persistent SassValueWrapper::constructor; + Nan::Persistent SassValueWrapper::constructor; template - SassValueWrapper::SassValueWrapper(Sass_Value* v) { - this->value = sass_clone_value(v); - } - - template - SassValueWrapper::~SassValueWrapper() { - this->js_object.Reset(); - sass_delete_value(this->value); - } + v8::Local SassValueWrapper::get_js_object() { + if (this->persistent().IsEmpty()) { + v8::Local wrapper = Nan::NewInstance(T::get_constructor()).ToLocalChecked(); + this->Wrap(wrapper); + } - template - Sass_Value* SassValueWrapper::get_sass_value() { - return sass_clone_value(this->value); - } + return this->handle(); + } template - v8::Local SassValueWrapper::get_js_object() { - if (this->js_object.IsEmpty()) { - v8::Local wrapper = Nan::NewInstance(T::get_constructor()).ToLocalChecked(); - delete static_cast(Nan::GetInternalFieldPointer(wrapper, 0)); - Nan::SetInternalFieldPointer(wrapper, 0, this); - this->js_object.Reset(wrapper); + v8::Local SassValueWrapper::get_constructor_template() { + Nan::EscapableHandleScope scope; + v8::Local tpl = Nan::New(New); + tpl->SetClassName(Nan::New(T::get_constructor_name()).ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + T::initPrototype(tpl); + + return scope.Escape(tpl); } - return Nan::New(this->js_object); - } - template - v8::Local SassValueWrapper::get_constructor_template() { - Nan::EscapableHandleScope scope; - v8::Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New(T::get_constructor_name()).ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - T::initPrototype(tpl); - - return scope.Escape(tpl); - } + v8::Local SassValueWrapper::get_constructor() { + if (constructor.IsEmpty()) { + constructor.Reset(Nan::GetFunction(T::get_constructor_template()).ToLocalChecked()); + } - template - v8::Local SassValueWrapper::get_constructor() { - if (constructor.IsEmpty()) { - constructor.Reset(Nan::GetFunction(T::get_constructor_template()).ToLocalChecked()); + return Nan::New(constructor); } - return Nan::New(constructor); - } - template - NAN_METHOD(SassValueWrapper::New) { - std::vector> localArgs(info.Length()); + NAN_METHOD(SassValueWrapper::New) { + std::vector> localArgs(info.Length()); - for (auto i = 0; i < info.Length(); ++i) { - localArgs[i] = info[i]; - } - if (info.IsConstructCall()) { - Sass_Value* value; - if (T::construct(localArgs, &value) != NULL) { - T* obj = new T(value); - sass_delete_value(value); - - Nan::SetInternalFieldPointer(info.This(), 0, obj); - obj->js_object.Reset(info.This()); - } else { - return Nan::ThrowError(Nan::New(sass_error_get_message(value)).ToLocalChecked()); + for (auto i = 0; i < info.Length(); ++i) { + localArgs[i] = info[i]; } - } else { - v8::Local cons = T::get_constructor(); - v8::Local inst; - if (Nan::NewInstance(cons, info.Length(), &localArgs[0]).ToLocal(&inst)) { - info.GetReturnValue().Set(inst); + if (info.IsConstructCall()) { + Sass_Value* value; + if (T::construct(localArgs, &value) != NULL) { + T* obj = new T(value); + sass_delete_value(value); + + obj->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); + } else { + return Nan::ThrowError(Nan::New(sass_error_get_message(value)).ToLocalChecked()); + } } else { - info.GetReturnValue().Set(Nan::Undefined()); + v8::Local cons = T::get_constructor(); + v8::Local inst; + if (Nan::NewInstance(cons, info.Length(), &localArgs[0]).ToLocal(&inst)) { + info.GetReturnValue().Set(inst); + } else { + info.GetReturnValue().Set(Nan::Undefined()); + } } } - } template - T* SassValueWrapper::unwrap(v8::Local obj) { - /* This maybe NULL */ - return static_cast(Factory::unwrap(obj)); - } - - template - Sass_Value *SassValueWrapper::fail(const char *reason, Sass_Value **out) { - *out = sass_make_error(reason); - return NULL; - } + Sass_Value *SassValueWrapper::fail(const char *reason, Sass_Value **out) { + *out = sass_make_error(reason); + return NULL; + } } - #endif diff --git a/src/sass_types/string.cpp b/src/sass_types/string.cpp index 9235f9e02..c6f2c48fc 100644 --- a/src/sass_types/string.cpp +++ b/src/sass_types/string.cpp @@ -31,7 +31,7 @@ namespace SassTypes } NAN_METHOD(String::GetValue) { - info.GetReturnValue().Set(Nan::New(sass_string_get_value(unwrap(info.This())->value)).ToLocalChecked()); + info.GetReturnValue().Set(Nan::New(sass_string_get_value(String::Unwrap(info.This())->value)).ToLocalChecked()); } NAN_METHOD(String::SetValue) { @@ -43,6 +43,6 @@ namespace SassTypes return Nan::ThrowTypeError("Supplied value should be a string"); } - sass_string_set_value(unwrap(info.This())->value, create_string(info[0])); + sass_string_set_value(String::Unwrap(info.This())->value, create_string(info[0])); } } diff --git a/src/sass_types/value.h b/src/sass_types/value.h index c1cfdaff1..fa4703cb3 100644 --- a/src/sass_types/value.h +++ b/src/sass_types/value.h @@ -7,10 +7,35 @@ namespace SassTypes { // This is the interface that all sass values must comply with - class Value { + class Value : public Nan::ObjectWrap { + public: - virtual Sass_Value* get_sass_value() =0; virtual v8::Local get_js_object() =0; + + Value() { + + } + + Sass_Value* get_sass_value() { + return sass_clone_value(this->value); + } + + protected: + + Sass_Value* value; + + Value(Sass_Value* v) { + this->value = sass_clone_value(v); + } + + ~Value() { + sass_delete_value(this->value); + } + + static Sass_Value* fail(const char *reason, Sass_Value **out) { + *out = sass_make_error(reason); + return NULL; + } }; } From 811042218f709bb26267f24df50a5acbc542429a Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 31 Oct 2017 16:29:27 -0400 Subject: [PATCH 073/286] build: Add Node 8 (carbon) to Travis --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5fa910f3c..8893c565e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,13 +16,19 @@ jobs: after_success: npm run-script coverage; - stage: platform-test node_js: "node" - os: osx + os: osx - stage: platform-test node_js: "7" os: linux - stage: platform-test node_js: "7" os: osx + - stage: platform-test + node_js: "lts/carbon" + os: linux + - stage: platform-test + node_js: "lts/carbon" + os: osx - stage: platform-test node_js: "lts/boron" os: linux From 82dc901c6876660cc5253f598afb157f2935f297 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 31 Oct 2017 16:31:30 -0400 Subject: [PATCH 074/286] Install: Add Node 9 detection Removed 55 since it was part of a prerelease Node 8 only --- lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index 8681be11a..37aea1f81 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -72,8 +72,8 @@ function getHumanNodeVersion(abi) { case 50: return 'Electron 1.4.x'; case 51: return 'Node.js 7.x'; case 53: return 'Electron 1.6.x'; - case 55: return 'Node.js 8.x'; case 57: return 'Node.js 8.x'; + case 59: return 'Node.js 9.x'; default: return false; } } From 0e8ce9c02b34506a730aed51fdf909dfc23afb57 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 31 Oct 2017 16:41:05 -0400 Subject: [PATCH 075/286] Build: Add Node 9 to Appveyor --- appveyor.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 0a41bf3d6..cad233dc6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -49,6 +49,9 @@ - nodejs_version: 8 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 9 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform @@ -127,6 +130,9 @@ - nodejs_version: 8 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 9 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform From 54f9873c99dbb2dab2c6443dcf25b6c2ef1414ae Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 3 Nov 2017 20:28:54 +1100 Subject: [PATCH 076/286] 4.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3bcbbc82..a9b3c7dc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.5.3", + "version": "4.6.0", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From 56a19a5adb3b676807f39e898818a129a8edfd49 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Sat, 4 Nov 2017 00:43:38 -0400 Subject: [PATCH 077/286] docs: Update the Node version support message (#2136) Pointing them to the release notes is safer since it will accurately reflect the support for that version of node-sass --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ae258f94..5a01fed36 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # node-sass -#### Supported Node.js versions 0.10, 0.12, 1, 2, 3, 4, 5, 6, 7 and 8. +#### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases)
From 82ac87fb4808b320b58591ac0f4b9f5c199594bd Mon Sep 17 00:00:00 2001 From: ruedap Date: Tue, 7 Nov 2017 02:46:40 +0900 Subject: [PATCH 078/286] Update changelog for 4.6.0 (#2141) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1ebee79..4bd5ffd66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v4.6.0 + +https://github.com/sass/node-sass/releases/tag/v4.6.0 + ## v4.5.0 https://github.com/sass/node-sass/releases/tag/v4.5.0 From 824fbd5f76506d8314d64198273b6fcf2d121ad9 Mon Sep 17 00:00:00 2001 From: Evan Payne Date: Sat, 11 Nov 2017 12:45:19 +0100 Subject: [PATCH 079/286] gaze.watched returns an object (#2147) * gaze.watched returns an object Since it returns an object, we need to run through the keys, a simple index of isn't enough. This should fix https://github.com/sass/node-sass/issues/2139 --- bin/node-sass | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index 4139f71d1..515ed9c17 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -232,9 +232,11 @@ function watch(options, emitter) { var handler = function(files) { files.added.forEach(function(file) { var watch = gaze.watched(); - if (watch.indexOf(file) === -1) { - gaze.add(file); - } + Object.keys(watch).forEach(function (dir) { + if (watch[dir].indexOf(file) !== -1) { + gaze.add(file); + } + }); }); files.changed.forEach(function(file) { From 6b7b67901bc7c1f803073089ad93b95ec5fc61d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Cie=C5=9Blak?= Date: Sat, 11 Nov 2017 20:33:34 +0000 Subject: [PATCH 080/286] 4.6.1 ## Fixes - Problem with watcher (@evanfuture, #2147) ## Supported Environments | OS | Architecture | Node | | --- | --- | --- | | Windows | x86 & x64 | 0.10, 0.12, 1, 2, 3, 4, 5, 6, 7, 8, 9 | | OSX | x64 | 0.10, 0.12, 1, 2, 3, 4, 5, 6, 7, 8, 9 | | Linux* | x86 & x64 | 0.10, 0.12, 1, 2, 3, 4, 5, 6, 7, 8, 9 | | Alpine | x64 | 4, 6, 7, 8, 9 | | FreeBSD 10+ | i386 | 4, 6, 8 | | FreeBSD 10+ | amd64 | 4, 6, 8 | *Linux support refers to Ubuntu, Debian, and CentOS 5 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd5ffd66..a61141267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v4.6.1 + +https://github.com/sass/node-sass/releases/tag/v4.6.1 + ## v4.6.0 https://github.com/sass/node-sass/releases/tag/v4.6.0 diff --git a/package.json b/package.json index a9b3c7dc3..a310e5be9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.6.0", + "version": "4.6.1", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From 0a0f5c6456558bc2fe40e2aef5dd9bda72829153 Mon Sep 17 00:00:00 2001 From: Michael Loughry Date: Sat, 11 Nov 2017 15:23:57 -0800 Subject: [PATCH 081/286] Use true-case-path to fix re-entrancy issue on Windows (#2149) * Use true-case-path * Fix failing tests * Delete package-lock.json * .gitignore package-lock.json --- .gitignore | 1 + lib/extensions.js | 5 +++-- package.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cb3424c46..24e13692a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ test/lcov.info test/fixtures/watching-css-out-01 test/fixtures/watching-css-out-02 coverage +package-lock.json \ No newline at end of file diff --git a/lib/extensions.js b/lib/extensions.js index 37aea1f81..4b956344a 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -7,7 +7,8 @@ var eol = require('os').EOL, pkg = require('../package.json'), mkdir = require('mkdirp'), path = require('path'), - defaultBinaryPath = path.join(__dirname, '..', 'vendor'); + defaultBinaryPath = path.join(__dirname, '..', 'vendor'), + trueCasePathSync = require('true-case-path'); /** * Get the human readable name of the Platform that is running @@ -272,7 +273,7 @@ function getBinaryPath() { binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_(?=binding\.node)/, '/')); } - return binaryPath; + return trueCasePathSync(binaryPath) || binaryPath; } /** diff --git a/package.json b/package.json index a310e5be9..778d4f24c 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "npmlog": "^4.0.0", "request": "^2.79.0", "sass-graph": "^2.2.4", - "stdout-stream": "^1.4.0" + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" }, "devDependencies": { "coveralls": "^2.11.8", From 6c5f110159c175fd36e8693332600f2a966e293c Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 15 Nov 2017 00:32:13 +1100 Subject: [PATCH 082/286] Fix typo in watcher test config --- test/watcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/watcher.js b/test/watcher.js index 2b3c95057..2ce16c97e 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -20,7 +20,7 @@ describe('watcher', function() { beforeEach(function() { watcher.reset({ directory: main, - loadPaths: [main] + includePath: [main] }); }); @@ -126,7 +126,7 @@ describe('watcher', function() { beforeEach(function() { watcher.reset({ src: path.join(main, 'one.scss'), - loadPaths: [main] + includePath: [main] }); }); From 2326b5f4648cbe1d203114a42dfed67f474f2871 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 15 Nov 2017 00:32:20 +1100 Subject: [PATCH 083/286] Fix watching of entry points Currently changes to non-partials are being picked up. This PR Fixes that and adds addition test coverage. Fixes #2139 --- bin/node-sass | 2 - lib/watcher.js | 15 +- test/fixtures/watcher/main/partials/_one.scss | 2 + test/fixtures/watcher/main/partials/_two.scss | 2 + test/fixtures/watcher/main/three.scss | 3 + test/fixtures/watcher/main/two.scss | 2 - test/watcher.js | 531 ++++++++++++++---- 7 files changed, 441 insertions(+), 116 deletions(-) create mode 100644 test/fixtures/watcher/main/three.scss diff --git a/bin/node-sass b/bin/node-sass index 515ed9c17..c8bd0b899 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -250,8 +250,6 @@ function watch(options, emitter) { }); }; - watcher.reset(options); - var gaze = new Gaze(); gaze.add(watcher.reset(options)); gaze.on('error', emitter.emit.bind(emitter, 'error')); diff --git a/lib/watcher.js b/lib/watcher.js index ab79cc16f..cdb965c53 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -1,5 +1,6 @@ var grapher = require('sass-graph'), clonedeep = require('lodash.clonedeep'), + path = require('path'), config = {}, watcher = {}, graph = null; @@ -30,8 +31,14 @@ watcher.changed = function(absolutePath) { this.reset(); + if (absolutePath && path.basename(absolutePath)[0] !== '_') { + files.changed.push(absolutePath); + } + graph.visitAncestors(absolutePath, function(parent) { - files.changed.push(parent); + if (path.basename(parent)[0] !== '_') { + files.changed.push(parent); + } }); graph.visitDescendents(absolutePath, function(child) { @@ -50,7 +57,7 @@ watcher.added = function(absolutePath) { this.reset(); - if (Object.keys(graph.index).indexOf(absolutePath) !== -1) { + if (Object.keys(graph.index).indexOf(absolutePath) === -1) { files.added.push(absolutePath); } @@ -69,7 +76,9 @@ watcher.removed = function(absolutePath) { }; graph.visitAncestors(absolutePath, function(parent) { - files.changed.push(parent); + if (path.basename(parent)[0] !== '_') { + files.changed.push(parent); + } }); if (Object.keys(graph.index).indexOf(absolutePath) !== -1) { diff --git a/test/fixtures/watcher/main/partials/_one.scss b/test/fixtures/watcher/main/partials/_one.scss index b2a72d637..379ec6577 100644 --- a/test/fixtures/watcher/main/partials/_one.scss +++ b/test/fixtures/watcher/main/partials/_one.scss @@ -1,3 +1,5 @@ +@import "partials/three"; + .one { color: darkred; } diff --git a/test/fixtures/watcher/main/partials/_two.scss b/test/fixtures/watcher/main/partials/_two.scss index 6a3b4ed90..7a1ace9f2 100644 --- a/test/fixtures/watcher/main/partials/_two.scss +++ b/test/fixtures/watcher/main/partials/_two.scss @@ -1,3 +1,5 @@ +@import "partials/three"; + .two { color: darkblue; } diff --git a/test/fixtures/watcher/main/three.scss b/test/fixtures/watcher/main/three.scss new file mode 100644 index 000000000..24cab7239 --- /dev/null +++ b/test/fixtures/watcher/main/three.scss @@ -0,0 +1,3 @@ +.three { + color: green; +} diff --git a/test/fixtures/watcher/main/two.scss b/test/fixtures/watcher/main/two.scss index 3e9c0fbf9..68036dbc5 100644 --- a/test/fixtures/watcher/main/two.scss +++ b/test/fixtures/watcher/main/two.scss @@ -1,5 +1,3 @@ -@import "partials/two"; - .two { color: blue; } diff --git a/test/watcher.js b/test/watcher.js index 2ce16c97e..dca632fad 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -24,99 +24,214 @@ describe('watcher', function() { }); }); - describe('when a file is changed in the graph', function() { - it('should return the files to compile', function() { - var files = watcher.changed(path.join(main, 'partials', '_one.scss')); - assert.deepEqual(files, { - added: [], - changed: [path.join(main, 'one.scss')], - removed: [], + describe('when a file is changed', function() { + describe('and it is in the graph', function() { + describe('if it is a partial', function() { + it('should record its ancestors as changed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.changed, [ + path.join(main, 'one.scss'), + ]); + }); + + it('should record its decendants as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.added, [ + path.join(main, 'partials', '_three.scss'), + ]); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.removed, []); + }); }); - }); - describe('and @imports previously not imported files', function() { - it('should also return the new imported files', function() { - var file = path.join(main, 'partials', '_one.scss'); - fs.writeFileSync(file, '@import "partials/three.scss";', { flag: 'a' }); - var files = watcher.changed(file); - assert.deepEqual(files, { - added: [path.join(main, 'partials', '_three.scss')], - changed: [path.join(main, 'one.scss')], - removed: [], + describe('if it is not a partial', function() { + it('should record itself as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.changed, [ + file, + ]); + }); + + it('should record its decendants as added', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.added, [ + path.join(main, 'partials', '_one.scss'), + path.join(main, 'partials', '_three.scss'), + ]); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.removed, []); }); }); }); - }); - describe('when a file is changed outside the graph', function() { - it('should return an empty array', function() { - var files = watcher.changed(path.join(sibling, 'partials', '_three.scss')); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], + describe('and is not in the graph', function() { + describe('if it is a partial', function() { + it('should not record anything', function() { + var file = path.join(sibling, 'partials', '_three.scss'); + var files = watcher.changed(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); }); - }); - }); - describe('when a file is added in the graph', function() { - it('should return only the added file', function() { - var file = path.join(main, 'three.scss'); - fs.writeFileSync(file, '@import "paritals/two.scss";'); - var files = watcher.added(file); - assert.deepEqual(files, { - added: [file], - changed: [], - removed: [], + describe('if it is not a partial', function() { + it('should record itself as changed', function() { + var file = path.join(sibling, 'three.scss'); + var files = watcher.changed(file); + assert.deepEqual(files, { + added: [], + changed: [file], + removed: [], + }); + }); }); }); + }); - describe('and @imports previously not imported files', function() { - it('should also return the new imported files', function() { - var file = path.join(main, 'three.scss'); - fs.writeFileSync(file, '@import "partials/three.scss";'); - var files = watcher.added(file); - assert.deepEqual(files, { - added: [ - file, + describe('when a file is added', function() { + describe('and it is in the graph', function() { + describe('if it is a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, []); + }); + + it('should record its decendants as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, [ path.join(main, 'partials', '_three.scss') - ], - changed: [], - removed: [], + ]); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.changed, []); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.removed, []); }); }); - }); - }); - describe('when a file is added outside the graph', function() { - it('should return an empty array', function() { - var files = watcher.added(path.join(sibling, 'three.scss')); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], + describe('if it is not a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, []); + }); + + it('should record its decendants as added', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, [ + path.join(main, 'partials', '_one.scss'), + path.join(main, 'partials', '_three.scss'), + ]); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.changed, []); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.removed, []); + }); }); }); }); - describe('when a file is removed from the graph', function() { - it('should return the files to compile', function() { - var files = watcher.removed(path.join(main, 'partials', '_one.scss')); - assert.deepEqual(files, { - added: [], - changed: [path.join(main, 'one.scss')], - removed: [path.join(main, 'partials', '_one.scss')], + describe('when a file is removed', function() { + describe('and it is in the graph', function() { + describe('if it is a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.added, []); + }); + + it('should record its ancestors as changed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.changed, [ + path.join(main, 'one.scss'), + ]); + }); + + it('should record itself as removed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.removed, [file]); + }); + }); + + describe('if it is not a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.added, []); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.changed, []); + }); + + it('should record itself as removed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.removed, [file]); + }); }); }); - }); - describe('when a file is removed from outside the graph', function() { - it('should return an empty array', function() { - var files = watcher.removed(path.join(sibling, 'partials', '_three.scss')); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], + describe('and is not in the graph', function() { + describe('if it is a partial', function() { + it('should record nothing', function() { + var file = path.join(sibling, 'partials', '_three.scss'); + var files = watcher.removed(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('if it is not a partial', function() { + it('should record nothing', function() { + var file = path.join(sibling, 'three.scss'); + var files = watcher.removed(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); }); }); }); @@ -130,59 +245,257 @@ describe('watcher', function() { }); }); - describe('when a file is changed in the graph', function() { - it('should return the files to compile', function() { - var files = watcher.changed(path.join(main, 'partials', '_one.scss')); - assert.deepEqual(files, { - added: [], - changed: [path.join(main, 'one.scss')], - removed: [], + describe('when a file is changed', function() { + describe('and it is in the graph', function() { + describe('if it is a partial', function() { + it('should record its decendents as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.added, [ + path.join(main, 'partials', '_three.scss'), + ]); + }); + + it('should record its ancenstors as changed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.changed, [ + path.join(main, 'one.scss'), + ]); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.removed, []); + }); + }); + + describe('if it is not a partial', function() { + it('should record its decendents as added', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.added, [ + path.join(main, 'partials', '_one.scss'), + path.join(main, 'partials', '_three.scss'), + ]); + }); + + it('should record itself as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.changed, [file]); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.removed, []); + }); }); }); - }); - describe('when a file is changed outside the graph', function() { - it('should return an empty array', function() { - var files = watcher.changed(path.join(sibling, 'partials', '_three.scss')); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], + describe('and it is not in the graph', function() { + describe('if it is a partial', function() { + it('should record nothing', function() { + var file = path.join(sibling, 'partials', '_three.scss'); + var files = watcher.changed(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('if it is not a partial', function() { + it('should record nothing as added', function() { + var file = path.join(sibling, 'three.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.added, []); + }); + + it('should record itself as changed', function() { + var file = path.join(sibling, 'three.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.changed, [file]); + }); + + it('should record nothing as removed', function() { + var file = path.join(sibling, 'three.scss'); + var files = watcher.changed(file); + assert.deepEqual(files.removed, []); + }); }); }); }); describe('when a file is added', function() { - it('should return an empty array', function() { - var file = path.join(main, 'three.scss'); - fs.writeFileSync(file, '@import "paritals/one.scss";'); - var files = watcher.added(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], + describe('and it is in the graph', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, []); + }); + + it('should record its decendants as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, [ + path.join(main, 'partials', '_three.scss'), + ]); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.changed, []); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.removed, []); }); }); - }); - describe('when a file is removed from the graph', function() { - it('should return the files to compile', function() { - var files = watcher.removed(path.join(main, 'partials', '_one.scss')); - assert.deepEqual(files, { - added: [], - changed: [path.join(main, 'one.scss')], - removed: [path.join(main, 'partials', '_one.scss')], + describe('and it is not in the graph', function() { + beforeEach(function() { + watcher.reset({ + src: path.join(main, 'two.scss'), + includePath: [main] + }); + }); + + describe('if it is a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, [ + file, + ]); + }); + + it('should not record its decendants as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, [ + file, + ]); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.changed, []); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'partials', '_three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.removed, []); + }); + }); + + describe('if it is not a partial', function() { + it('should record itself as added', function() { + var file = path.join(main, 'three.scss'); + var files = watcher.added(file); + assert.deepEqual(files.added, [ + file, + ]); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.changed, []); + }); + + it('should record nothing as removed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.added(file); + assert.deepEqual(files.removed, []); + }); }); }); }); - describe('when a file is removed from outside the graph', function() { - it('should return an empty array', function() { - var files = watcher.removed(path.join(sibling, 'partials', '_three.scss')); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], + describe('when a file is removed', function() { + describe('and it is in the graph', function() { + describe('if it is a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.added, []); + }); + + it('should record its ancestors as changed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.changed, [ + path.join(main, 'one.scss'), + ]); + }); + + it('should record itself as removed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.removed, [file]); + }); + }); + + describe('if it is not a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.added, []); + }); + + it('should record nothing as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.changed, []); + }); + + it('should record itself as removed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files.removed, [file]); + }); + }); + }); + + describe('and is not in the graph', function() { + beforeEach(function() { + watcher.reset({ + src: path.join(main, 'two.scss'), + includePath: [main] + }); + }); + + describe('if it is a partial', function() { + it('should record nothing as added', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); + }); + + describe('if it is not a partial', function() { + it('should record nothing', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.removed(file); + assert.deepEqual(files, { + added: [], + changed: [], + removed: [], + }); + }); }); }); }); From a51eca7f8bafdc9bafab76d2305fedc64503304c Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 16 Nov 2017 21:46:56 +1100 Subject: [PATCH 084/286] 4.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 778d4f24c..60d46af8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.6.1", + "version": "4.7.0", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From cabbee9f27f8176c7bd5fbb40e2b94e3c30aa11a Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 15 Nov 2017 23:08:54 -0500 Subject: [PATCH 085/286] Add checking release page to the ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index ba06a04af..d8e74f6b3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,5 @@ Before opening an issue: +- Check that the version of node-sass you're trying to install supports your version of Node by looking at the release page for that version https://github.com/sass/node-sass/releases - Read the common workarounds in the [TROUBLESHOOTING.md](https://github.com/sass/node-sass/blob/master/TROUBLESHOOTING.md) - [Search for duplicate or closed issues](https://github.com/sass/node-sass/issues?utf8=%E2%9C%93&q=is%3Aissue) - [Validate](http://sassmeister.com/) that it runs with both Ruby Sass and LibSass From 40d1827c4e3129802fe9143d0dd80ebe3301fc2f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 17 Nov 2017 19:24:02 +1100 Subject: [PATCH 086/286] Bypass true case path for old Node versions This was added in #2149 but doesn't support Node < 4 --- lib/extensions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/extensions.js b/lib/extensions.js index 4b956344a..deee8b2f9 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -273,6 +273,10 @@ function getBinaryPath() { binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_(?=binding\.node)/, '/')); } + if (process.versions.modules < 46) { + return binaryPath; + } + return trueCasePathSync(binaryPath) || binaryPath; } From 187d8c91dff15d4655bfe0cb1155d5ca8a256184 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 17 Nov 2017 20:02:23 +1100 Subject: [PATCH 087/286] Lock request@<2.81.0 request@2.81.0 breaks Node < 4 compat. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 60d46af8f..cb855f493 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "nan": "^2.3.2", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", - "request": "^2.79.0", + "request": "~2.79.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" From e38a59c5133f2fe65345e52e6dee2a56e962356a Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 17 Nov 2017 22:09:37 +1100 Subject: [PATCH 088/286] 4.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb855f493..c26410778 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.7.0", + "version": "4.7.1", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From d7327b4f87bfe3c17bd27cdde19be2fc2525a608 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 21 Nov 2017 08:15:00 +1100 Subject: [PATCH 089/286] Handle true-case-path permissions error Related https://github.com/barsh/true-case-path/issues/2 Fixes #2157 Closes #2161 --- lib/extensions.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index deee8b2f9..b4c406d7a 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -277,7 +277,11 @@ function getBinaryPath() { return binaryPath; } - return trueCasePathSync(binaryPath) || binaryPath; + try { + return trueCasePathSync(binaryPath) || binaryPath; + } catch (e) { + return binaryPath; + } } /** From 0ea34e487b1a26e4b41fed2e9a88eb1b80f7fb92 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 21 Nov 2017 08:44:00 +1100 Subject: [PATCH 090/286] 4.7.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c26410778..e42973a07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.7.1", + "version": "4.7.2", "libsass": "3.5.0.beta.2", "description": "Wrapper around libsass", "license": "MIT", From e09439555a9df1032445610844d99bcab2aa9ba6 Mon Sep 17 00:00:00 2001 From: Saul Ivan Rivas Vega Date: Fri, 22 Dec 2017 18:38:40 -0600 Subject: [PATCH 091/286] Update TROUBLESHOOTING.md (#2198) Fixed a Typo --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 5f7b0d9cf..3433561f9 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -69,7 +69,7 @@ npm install ### Which node runtime am I using? -There are two primary node runtimes, Node.js and io.js, both of which are supported by node-sass. To determine which you are currenty using you first need to determine [which node runtime](#which-node-runtime-am-i-using-glossaryruntime) you are running. +There are two primary node runtimes, Node.js and io.js, both of which are supported by node-sass. To determine which you are currently using you first need to determine [which node runtime](#which-node-runtime-am-i-using-glossaryruntime) you are running. ``` node -v From a96e2ff7c62fbc750ffc8bf3f21fdb0b6ee512bd Mon Sep 17 00:00:00 2001 From: Andrew Luca Date: Tue, 16 Jan 2018 18:14:29 +0200 Subject: [PATCH 092/286] (editorconfig) s/spaces/space/ --- src/libsass/.editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsass/.editorconfig b/src/libsass/.editorconfig index 9a1a67656..c3d859e7a 100755 --- a/src/libsass/.editorconfig +++ b/src/libsass/.editorconfig @@ -7,7 +7,7 @@ root = true charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -indent_style = spaces +indent_style = space indent_size = 2 [{Makefile, GNUmakefile.am}] From a60caf43fccd77fb8d84dc48ee94f44b11dce4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Wed, 22 Nov 2017 10:49:34 +0100 Subject: [PATCH 093/286] Add a Git .mailmap with mgol's new name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this way his past contributions are mapped correctly, including collapsing two different ways of writing "ę" into one. --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..1f2f86feb --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Michał Gołębiowski-Owczarek From e797a34c1da5128b53bf9715b6e7b803d0df177a Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 22 Feb 2018 03:04:23 -0500 Subject: [PATCH 094/286] build: Add back Appveyor 0.12-3 (#2255) We still want these till we officially drop the EOL Node versions --- appveyor.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index cad233dc6..bcf5b4d47 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,6 +34,21 @@ environment: SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: + - nodejs_version: 0.10 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - nodejs_version: 0.12 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - nodejs_version: 1 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - nodejs_version: 2 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - nodejs_version: 3 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 4 GYP_MSVS_VERSION: 2013 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 From abbc65770ea6f16d2591cfc8046021301ed29d2d Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 23 Feb 2018 12:55:09 -0500 Subject: [PATCH 095/286] Use HTML comments to show a reduced Markdown Saw this trick in another repo and though it made sense --- .github/ISSUE_TEMPLATE.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d8e74f6b3..7359135cc 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,4 @@ + - NPM version (`npm -v`): - Node version (`node -v`): @@ -15,10 +23,3 @@ When reporting an bug, **you must provide this information**: - Node architecture (`node -p process.arch`): - node-sass version (`node -p "require('node-sass').info"`): - npm node-sass versions (`npm ls node-sass`): - -When encountering a syntax, or compilation issue: - -- [Open an issue on `LibSass`](https://github.com/sass/LibSass/issues/new). You -may link it back here, but any change will be required there, not here - -*If you delete this text without following it, your issue will be closed.* From dd805fc870b5191ab8c7d19f6ed3594afbdaa322 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 4 Mar 2018 12:40:54 +1100 Subject: [PATCH 096/286] Emit normal output as info not warn --- bin/node-sass | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index c8bd0b899..d9651f7e1 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -329,7 +329,7 @@ function run(options, emitter) { function renderFile(file, options, emitter) { options = getOptions([path.resolve(file)], options); if (options.watch) { - emitter.emit('warn', util.format('=> changed: %s', file)); + emitter.emit('info', util.format('=> changed: %s', file)); } render(options, emitter); } @@ -355,7 +355,7 @@ function renderDir(options, emitter) { renderFile(subject, options, emitter); }, function(successful, arr) { var outputDir = path.join(process.cwd(), options.output); - emitter.emit('warn', util.format('Wrote %s CSS files to %s', arr.length, outputDir)); + emitter.emit('info', util.format('Wrote %s CSS files to %s', arr.length, outputDir)); process.exit(); }); }); From 78a74e910ae4bc9cc609361ee113811637ff9f59 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 4 Mar 2018 12:41:22 +1100 Subject: [PATCH 097/286] Don't emit info output in quiet mode --- bin/node-sass | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index d9651f7e1..d80efb0fd 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -328,7 +328,7 @@ function run(options, emitter) { */ function renderFile(file, options, emitter) { options = getOptions([path.resolve(file)], options); - if (options.watch) { + if (options.watch && !options.quiet) { emitter.emit('info', util.format('=> changed: %s', file)); } render(options, emitter); @@ -355,7 +355,9 @@ function renderDir(options, emitter) { renderFile(subject, options, emitter); }, function(successful, arr) { var outputDir = path.join(process.cwd(), options.output); - emitter.emit('info', util.format('Wrote %s CSS files to %s', arr.length, outputDir)); + if (!options.quiet) { + emitter.emit('info', util.format('Wrote %s CSS files to %s', arr.length, outputDir)); + } process.exit(); }); }); From 639173e46c6e4197ecc649568c168919b43b3f93 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 6 Mar 2018 23:07:50 +1100 Subject: [PATCH 098/286] Bump sass-spec@3.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e42973a07..0d8cb84bf 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.0-1", + "sass-spec": "^3.5.0", "unique-temp-dir": "^1.0.0" } } From fe572eb1868ec7db2ea1a2d51fbae5aab5fe6419 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 6 Mar 2018 23:16:22 +1100 Subject: [PATCH 099/286] Bump LibSass to 3.5.0 https://github.com/sass/libsass/releases/tag/3.5.0 --- package.json | 2 +- src/libsass/.editorconfig | 0 src/libsass/.gitattributes | 0 src/libsass/.github/CONTRIBUTING.md | 14 +- src/libsass/.github/ISSUE_TEMPLATE.md | 37 +- src/libsass/.gitignore | 3 + src/libsass/.travis.yml | 0 src/libsass/COPYING | 0 src/libsass/GNUmakefile.am | 2 +- src/libsass/INSTALL | 0 src/libsass/LICENSE | 0 src/libsass/Makefile | 2 +- src/libsass/Makefile.conf | 0 src/libsass/Readme.md | 131 ++-- src/libsass/SECURITY.md | 0 src/libsass/appveyor.yml | 8 +- src/libsass/configure.ac | 0 src/libsass/contrib/libsass.spec | 0 src/libsass/contrib/plugin.cpp | 0 src/libsass/docs/README.md | 0 src/libsass/docs/api-context-example.md | 0 src/libsass/docs/api-context-internal.md | 0 src/libsass/docs/api-context.md | 0 src/libsass/docs/api-doc.md | 2 +- src/libsass/docs/api-function-example.md | 0 src/libsass/docs/api-function-internal.md | 0 src/libsass/docs/api-function.md | 0 src/libsass/docs/api-importer-example.md | 0 src/libsass/docs/api-importer-internal.md | 0 src/libsass/docs/api-importer.md | 0 src/libsass/docs/api-value-example.md | 0 src/libsass/docs/api-value-internal.md | 0 src/libsass/docs/api-value.md | 2 +- src/libsass/docs/build-on-darwin.md | 0 src/libsass/docs/build-on-gentoo.md | 0 src/libsass/docs/build-on-windows.md | 6 +- src/libsass/docs/build-shared-library.md | 0 src/libsass/docs/build-with-autotools.md | 0 src/libsass/docs/build-with-makefiles.md | 0 src/libsass/docs/build-with-mingw.md | 6 +- src/libsass/docs/build-with-visual-studio.md | 0 src/libsass/docs/build.md | 18 +- src/libsass/docs/compatibility-plan.md | 0 src/libsass/docs/contributing.md | 0 src/libsass/docs/custom-functions-internal.md | 0 src/libsass/docs/dev-ast-memory.md | 0 src/libsass/docs/implementations.md | 0 src/libsass/docs/plugins.md | 0 src/libsass/docs/setup-environment.md | 0 src/libsass/docs/source-map-internals.md | 0 src/libsass/docs/trace.md | 0 src/libsass/docs/triage.md | 0 src/libsass/docs/unicode.md | 0 src/libsass/extconf.rb | 0 src/libsass/include/sass.h | 0 src/libsass/include/sass/base.h | 0 src/libsass/include/sass/context.h | 0 src/libsass/include/sass/functions.h | 0 src/libsass/include/sass/values.h | 0 src/libsass/include/sass/version.h | 0 src/libsass/include/sass/version.h.in | 0 src/libsass/include/sass2scss.h | 2 +- src/libsass/m4/.gitkeep | 0 src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 | 0 src/libsass/res/resource.rc | 12 +- src/libsass/script/ci-build-libsass | 2 +- src/libsass/script/ci-install-compiler | 2 +- src/libsass/script/ci-install-deps | 11 +- src/libsass/script/tap-driver | 2 +- src/libsass/src/GNUmakefile.am | 0 src/libsass/src/ast.cpp | 686 ++++++------------ src/libsass/src/ast.hpp | 329 +++++---- src/libsass/src/ast_def_macros.hpp | 9 + src/libsass/src/ast_fwd_decl.cpp | 0 src/libsass/src/ast_fwd_decl.hpp | 17 +- src/libsass/src/b64/cencode.h | 0 src/libsass/src/b64/encode.h | 4 +- src/libsass/src/backtrace.hpp | 4 +- src/libsass/src/base64vlq.cpp | 0 src/libsass/src/base64vlq.hpp | 0 src/libsass/src/bind.cpp | 71 +- src/libsass/src/bind.hpp | 0 src/libsass/src/c99func.c | 0 src/libsass/src/cencode.c | 6 + src/libsass/src/check_nesting.cpp | 21 +- src/libsass/src/check_nesting.hpp | 1 + src/libsass/src/color_maps.cpp | 16 +- src/libsass/src/color_maps.hpp | 0 src/libsass/src/constants.cpp | 7 +- src/libsass/src/constants.hpp | 7 +- src/libsass/src/context.cpp | 34 +- src/libsass/src/context.hpp | 3 + src/libsass/src/cssize.cpp | 41 +- src/libsass/src/cssize.hpp | 0 src/libsass/src/debug.hpp | 0 src/libsass/src/debugger.hpp | 26 +- src/libsass/src/emitter.cpp | 36 +- src/libsass/src/emitter.hpp | 7 + src/libsass/src/environment.cpp | 79 +- src/libsass/src/environment.hpp | 32 +- src/libsass/src/error_handling.cpp | 39 +- src/libsass/src/error_handling.hpp | 28 +- src/libsass/src/eval.cpp | 537 ++++++++------ src/libsass/src/eval.hpp | 34 +- src/libsass/src/expand.cpp | 63 +- src/libsass/src/expand.hpp | 2 + src/libsass/src/extend.cpp | 471 ++++++------ src/libsass/src/extend.hpp | 57 +- src/libsass/src/file.cpp | 38 +- src/libsass/src/file.hpp | 0 src/libsass/src/functions.cpp | 512 +++++++++---- src/libsass/src/functions.hpp | 4 + src/libsass/src/inspect.cpp | 81 ++- src/libsass/src/inspect.hpp | 4 +- src/libsass/src/json.cpp | 0 src/libsass/src/json.hpp | 0 src/libsass/src/kwd_arg_macros.hpp | 0 src/libsass/src/lexer.cpp | 21 +- src/libsass/src/lexer.hpp | 17 +- src/libsass/src/listize.cpp | 1 + src/libsass/src/listize.hpp | 0 src/libsass/src/mapping.hpp | 0 src/libsass/src/memory/SharedPtr.cpp | 34 +- src/libsass/src/memory/SharedPtr.hpp | 43 +- src/libsass/src/node.cpp | 29 +- src/libsass/src/node.hpp | 12 +- src/libsass/src/operation.hpp | 4 +- src/libsass/src/output.cpp | 3 +- src/libsass/src/output.hpp | 0 src/libsass/src/parser.cpp | 440 +++++++++-- src/libsass/src/parser.hpp | 40 +- src/libsass/src/paths.hpp | 0 src/libsass/src/plugins.cpp | 0 src/libsass/src/plugins.hpp | 0 src/libsass/src/position.cpp | 24 +- src/libsass/src/position.hpp | 1 + src/libsass/src/prelexer.cpp | 100 ++- src/libsass/src/prelexer.hpp | 9 +- src/libsass/src/remove_placeholders.cpp | 4 +- src/libsass/src/remove_placeholders.hpp | 0 src/libsass/src/sass.cpp | 0 src/libsass/src/sass.hpp | 7 +- src/libsass/src/sass2scss.cpp | 0 src/libsass/src/sass_context.cpp | 71 +- src/libsass/src/sass_context.hpp | 0 src/libsass/src/sass_functions.cpp | 2 +- src/libsass/src/sass_functions.hpp | 0 src/libsass/src/sass_util.cpp | 6 +- src/libsass/src/sass_util.hpp | 6 +- src/libsass/src/sass_values.cpp | 35 +- src/libsass/src/sass_values.hpp | 0 src/libsass/src/source_map.cpp | 33 +- src/libsass/src/source_map.hpp | 0 src/libsass/src/subset_map.cpp | 0 src/libsass/src/subset_map.hpp | 0 src/libsass/src/support/libsass.pc.in | 0 src/libsass/src/to_c.cpp | 0 src/libsass/src/to_c.hpp | 0 src/libsass/src/to_value.cpp | 11 +- src/libsass/src/to_value.hpp | 1 + src/libsass/src/units.cpp | 394 ++++++++-- src/libsass/src/units.hpp | 61 +- src/libsass/src/utf8.h | 0 src/libsass/src/utf8/checked.h | 7 + src/libsass/src/utf8/core.h | 0 src/libsass/src/utf8/unchecked.h | 7 + src/libsass/src/utf8_string.cpp | 2 +- src/libsass/src/utf8_string.hpp | 0 src/libsass/src/util.cpp | 98 ++- src/libsass/src/util.hpp | 3 +- src/libsass/src/values.cpp | 15 +- src/libsass/src/values.hpp | 0 src/libsass/test/test_node.cpp | 0 src/libsass/test/test_paths.cpp | 0 src/libsass/test/test_selector_difference.cpp | 0 src/libsass/test/test_specificity.cpp | 0 src/libsass/test/test_subset_map.cpp | 0 src/libsass/test/test_superselector.cpp | 0 src/libsass/test/test_unification.cpp | 0 src/libsass/win/libsass.sln | 15 +- src/libsass/win/libsass.sln.DotSettings | 9 + src/libsass/win/libsass.targets | 0 src/libsass/win/libsass.vcxproj | 0 src/libsass/win/libsass.vcxproj.filters | 0 test/fixtures/source-map-embed/expected.css | 2 +- test/fixtures/source-map/expected.map | 2 +- 186 files changed, 3269 insertions(+), 1870 deletions(-) mode change 100755 => 100644 src/libsass/.editorconfig mode change 100755 => 100644 src/libsass/.gitattributes mode change 100755 => 100644 src/libsass/.github/CONTRIBUTING.md mode change 100755 => 100644 src/libsass/.github/ISSUE_TEMPLATE.md mode change 100755 => 100644 src/libsass/.gitignore mode change 100755 => 100644 src/libsass/.travis.yml mode change 100755 => 100644 src/libsass/COPYING mode change 100755 => 100644 src/libsass/GNUmakefile.am mode change 100755 => 100644 src/libsass/INSTALL mode change 100755 => 100644 src/libsass/LICENSE mode change 100755 => 100644 src/libsass/Makefile mode change 100755 => 100644 src/libsass/Makefile.conf mode change 100755 => 100644 src/libsass/Readme.md mode change 100755 => 100644 src/libsass/SECURITY.md mode change 100755 => 100644 src/libsass/appveyor.yml mode change 100755 => 100644 src/libsass/configure.ac mode change 100755 => 100644 src/libsass/contrib/libsass.spec mode change 100755 => 100644 src/libsass/contrib/plugin.cpp mode change 100755 => 100644 src/libsass/docs/README.md mode change 100755 => 100644 src/libsass/docs/api-context-example.md mode change 100755 => 100644 src/libsass/docs/api-context-internal.md mode change 100755 => 100644 src/libsass/docs/api-context.md mode change 100755 => 100644 src/libsass/docs/api-doc.md mode change 100755 => 100644 src/libsass/docs/api-function-example.md mode change 100755 => 100644 src/libsass/docs/api-function-internal.md mode change 100755 => 100644 src/libsass/docs/api-function.md mode change 100755 => 100644 src/libsass/docs/api-importer-example.md mode change 100755 => 100644 src/libsass/docs/api-importer-internal.md mode change 100755 => 100644 src/libsass/docs/api-importer.md mode change 100755 => 100644 src/libsass/docs/api-value-example.md mode change 100755 => 100644 src/libsass/docs/api-value-internal.md mode change 100755 => 100644 src/libsass/docs/api-value.md mode change 100755 => 100644 src/libsass/docs/build-on-darwin.md mode change 100755 => 100644 src/libsass/docs/build-on-gentoo.md mode change 100755 => 100644 src/libsass/docs/build-on-windows.md mode change 100755 => 100644 src/libsass/docs/build-shared-library.md mode change 100755 => 100644 src/libsass/docs/build-with-autotools.md mode change 100755 => 100644 src/libsass/docs/build-with-makefiles.md mode change 100755 => 100644 src/libsass/docs/build-with-mingw.md mode change 100755 => 100644 src/libsass/docs/build-with-visual-studio.md mode change 100755 => 100644 src/libsass/docs/build.md mode change 100755 => 100644 src/libsass/docs/compatibility-plan.md mode change 100755 => 100644 src/libsass/docs/contributing.md mode change 100755 => 100644 src/libsass/docs/custom-functions-internal.md mode change 100755 => 100644 src/libsass/docs/dev-ast-memory.md mode change 100755 => 100644 src/libsass/docs/implementations.md mode change 100755 => 100644 src/libsass/docs/plugins.md mode change 100755 => 100644 src/libsass/docs/setup-environment.md mode change 100755 => 100644 src/libsass/docs/source-map-internals.md mode change 100755 => 100644 src/libsass/docs/trace.md mode change 100755 => 100644 src/libsass/docs/triage.md mode change 100755 => 100644 src/libsass/docs/unicode.md mode change 100755 => 100644 src/libsass/extconf.rb mode change 100755 => 100644 src/libsass/include/sass.h mode change 100755 => 100644 src/libsass/include/sass/base.h mode change 100755 => 100644 src/libsass/include/sass/context.h mode change 100755 => 100644 src/libsass/include/sass/functions.h mode change 100755 => 100644 src/libsass/include/sass/values.h mode change 100755 => 100644 src/libsass/include/sass/version.h mode change 100755 => 100644 src/libsass/include/sass/version.h.in mode change 100755 => 100644 src/libsass/include/sass2scss.h mode change 100755 => 100644 src/libsass/m4/.gitkeep mode change 100755 => 100644 src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 mode change 100755 => 100644 src/libsass/res/resource.rc mode change 100755 => 100644 src/libsass/src/GNUmakefile.am mode change 100755 => 100644 src/libsass/src/ast.cpp mode change 100755 => 100644 src/libsass/src/ast.hpp mode change 100755 => 100644 src/libsass/src/ast_def_macros.hpp mode change 100755 => 100644 src/libsass/src/ast_fwd_decl.cpp mode change 100755 => 100644 src/libsass/src/ast_fwd_decl.hpp mode change 100755 => 100644 src/libsass/src/b64/cencode.h mode change 100755 => 100644 src/libsass/src/b64/encode.h mode change 100755 => 100644 src/libsass/src/backtrace.hpp mode change 100755 => 100644 src/libsass/src/base64vlq.cpp mode change 100755 => 100644 src/libsass/src/base64vlq.hpp mode change 100755 => 100644 src/libsass/src/bind.cpp mode change 100755 => 100644 src/libsass/src/bind.hpp mode change 100755 => 100644 src/libsass/src/c99func.c mode change 100755 => 100644 src/libsass/src/cencode.c mode change 100755 => 100644 src/libsass/src/check_nesting.cpp mode change 100755 => 100644 src/libsass/src/check_nesting.hpp mode change 100755 => 100644 src/libsass/src/color_maps.cpp mode change 100755 => 100644 src/libsass/src/color_maps.hpp mode change 100755 => 100644 src/libsass/src/constants.cpp mode change 100755 => 100644 src/libsass/src/constants.hpp mode change 100755 => 100644 src/libsass/src/context.cpp mode change 100755 => 100644 src/libsass/src/context.hpp mode change 100755 => 100644 src/libsass/src/cssize.cpp mode change 100755 => 100644 src/libsass/src/cssize.hpp mode change 100755 => 100644 src/libsass/src/debug.hpp mode change 100755 => 100644 src/libsass/src/debugger.hpp mode change 100755 => 100644 src/libsass/src/emitter.cpp mode change 100755 => 100644 src/libsass/src/emitter.hpp mode change 100755 => 100644 src/libsass/src/environment.cpp mode change 100755 => 100644 src/libsass/src/environment.hpp mode change 100755 => 100644 src/libsass/src/error_handling.cpp mode change 100755 => 100644 src/libsass/src/error_handling.hpp mode change 100755 => 100644 src/libsass/src/eval.cpp mode change 100755 => 100644 src/libsass/src/eval.hpp mode change 100755 => 100644 src/libsass/src/expand.cpp mode change 100755 => 100644 src/libsass/src/expand.hpp mode change 100755 => 100644 src/libsass/src/extend.cpp mode change 100755 => 100644 src/libsass/src/extend.hpp mode change 100755 => 100644 src/libsass/src/file.cpp mode change 100755 => 100644 src/libsass/src/file.hpp mode change 100755 => 100644 src/libsass/src/functions.cpp mode change 100755 => 100644 src/libsass/src/functions.hpp mode change 100755 => 100644 src/libsass/src/inspect.cpp mode change 100755 => 100644 src/libsass/src/inspect.hpp mode change 100755 => 100644 src/libsass/src/json.cpp mode change 100755 => 100644 src/libsass/src/json.hpp mode change 100755 => 100644 src/libsass/src/kwd_arg_macros.hpp mode change 100755 => 100644 src/libsass/src/lexer.cpp mode change 100755 => 100644 src/libsass/src/lexer.hpp mode change 100755 => 100644 src/libsass/src/listize.cpp mode change 100755 => 100644 src/libsass/src/listize.hpp mode change 100755 => 100644 src/libsass/src/mapping.hpp mode change 100755 => 100644 src/libsass/src/memory/SharedPtr.cpp mode change 100755 => 100644 src/libsass/src/memory/SharedPtr.hpp mode change 100755 => 100644 src/libsass/src/node.cpp mode change 100755 => 100644 src/libsass/src/node.hpp mode change 100755 => 100644 src/libsass/src/operation.hpp mode change 100755 => 100644 src/libsass/src/output.cpp mode change 100755 => 100644 src/libsass/src/output.hpp mode change 100755 => 100644 src/libsass/src/parser.cpp mode change 100755 => 100644 src/libsass/src/parser.hpp mode change 100755 => 100644 src/libsass/src/paths.hpp mode change 100755 => 100644 src/libsass/src/plugins.cpp mode change 100755 => 100644 src/libsass/src/plugins.hpp mode change 100755 => 100644 src/libsass/src/position.cpp mode change 100755 => 100644 src/libsass/src/position.hpp mode change 100755 => 100644 src/libsass/src/prelexer.cpp mode change 100755 => 100644 src/libsass/src/prelexer.hpp mode change 100755 => 100644 src/libsass/src/remove_placeholders.cpp mode change 100755 => 100644 src/libsass/src/remove_placeholders.hpp mode change 100755 => 100644 src/libsass/src/sass.cpp mode change 100755 => 100644 src/libsass/src/sass.hpp mode change 100755 => 100644 src/libsass/src/sass2scss.cpp mode change 100755 => 100644 src/libsass/src/sass_context.cpp mode change 100755 => 100644 src/libsass/src/sass_context.hpp mode change 100755 => 100644 src/libsass/src/sass_functions.cpp mode change 100755 => 100644 src/libsass/src/sass_functions.hpp mode change 100755 => 100644 src/libsass/src/sass_util.cpp mode change 100755 => 100644 src/libsass/src/sass_util.hpp mode change 100755 => 100644 src/libsass/src/sass_values.cpp mode change 100755 => 100644 src/libsass/src/sass_values.hpp mode change 100755 => 100644 src/libsass/src/source_map.cpp mode change 100755 => 100644 src/libsass/src/source_map.hpp mode change 100755 => 100644 src/libsass/src/subset_map.cpp mode change 100755 => 100644 src/libsass/src/subset_map.hpp mode change 100755 => 100644 src/libsass/src/support/libsass.pc.in mode change 100755 => 100644 src/libsass/src/to_c.cpp mode change 100755 => 100644 src/libsass/src/to_c.hpp mode change 100755 => 100644 src/libsass/src/to_value.cpp mode change 100755 => 100644 src/libsass/src/to_value.hpp mode change 100755 => 100644 src/libsass/src/units.cpp mode change 100755 => 100644 src/libsass/src/units.hpp mode change 100755 => 100644 src/libsass/src/utf8.h mode change 100755 => 100644 src/libsass/src/utf8/checked.h mode change 100755 => 100644 src/libsass/src/utf8/core.h mode change 100755 => 100644 src/libsass/src/utf8/unchecked.h mode change 100755 => 100644 src/libsass/src/utf8_string.cpp mode change 100755 => 100644 src/libsass/src/utf8_string.hpp mode change 100755 => 100644 src/libsass/src/util.cpp mode change 100755 => 100644 src/libsass/src/util.hpp mode change 100755 => 100644 src/libsass/src/values.cpp mode change 100755 => 100644 src/libsass/src/values.hpp mode change 100755 => 100644 src/libsass/test/test_node.cpp mode change 100755 => 100644 src/libsass/test/test_paths.cpp mode change 100755 => 100644 src/libsass/test/test_selector_difference.cpp mode change 100755 => 100644 src/libsass/test/test_specificity.cpp mode change 100755 => 100644 src/libsass/test/test_subset_map.cpp mode change 100755 => 100644 src/libsass/test/test_superselector.cpp mode change 100755 => 100644 src/libsass/test/test_unification.cpp mode change 100755 => 100644 src/libsass/win/libsass.sln create mode 100644 src/libsass/win/libsass.sln.DotSettings mode change 100755 => 100644 src/libsass/win/libsass.targets mode change 100755 => 100644 src/libsass/win/libsass.vcxproj mode change 100755 => 100644 src/libsass/win/libsass.vcxproj.filters diff --git a/package.json b/package.json index 0d8cb84bf..91b28442f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.7.2", - "libsass": "3.5.0.beta.2", + "libsass": "3.5.0", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", diff --git a/src/libsass/.editorconfig b/src/libsass/.editorconfig old mode 100755 new mode 100644 diff --git a/src/libsass/.gitattributes b/src/libsass/.gitattributes old mode 100755 new mode 100644 diff --git a/src/libsass/.github/CONTRIBUTING.md b/src/libsass/.github/CONTRIBUTING.md old mode 100755 new mode 100644 index 0e4e1902d..2ff99d8bd --- a/src/libsass/.github/CONTRIBUTING.md +++ b/src/libsass/.github/CONTRIBUTING.md @@ -5,18 +5,18 @@ The following is a set of guidelines for contributing to LibSass, which is hosted in the [Sass Organization](https://github.com/sass) on GitHub. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. -LibSass is a library that implements a [sass language] [8] compiler. As such it does not directly interface with end users (frontend developers). +LibSass is a library that implements a [sass language][8] compiler. As such it does not directly interface with end users (frontend developers). For direct contributions to the LibSass code base you will need to have at least a rough idea of C++, we will not lie about that. But there are other ways to contribute to the progress of LibSass. All contributions are done via github pull requests. -You can also contribute to the LibSass [documentation] [9] or provide additional [spec tests] [10] (and we will gladly point you in the +You can also contribute to the LibSass [documentation][9] or provide additional [spec tests][10] (and we will gladly point you in the direction for corners that lack test coverage). Foremost we rely on good and concise bug reports for issues the spec tests do not yet catch. ## Precheck: My Sass isn't compiling -- [ ] Check if you can reproduce the issue via [SourceMap Inspector] [5] (updated regularly). -- [ ] Validate official ruby sass compiler via [SassMeister] [6] produces your expected result. -- [ ] Search for similar issue in [LibSass] [1] and [node-sass] [2] (include closed tickets) -- [ ] Optionally test your code directly with [sass] [7] or [sassc] [3] ([installer] [4]) +- [ ] Check if you can reproduce the issue via [SourceMap Inspector][5] (updated regularly). +- [ ] Validate official ruby sass compiler via [SassMeister][6] produces your expected result. +- [ ] Search for similar issue in [LibSass][1] and [node-sass][2] (include closed tickets) +- [ ] Optionally test your code directly with [sass][7] or [sassc][3] ([installer][4]) ## Precheck: My build/install fails - [ ] Problems with building or installing libsass should be directed to implementors first! @@ -47,7 +47,7 @@ direction for corners that lack test coverage). Foremost we rely on good and con ## What makes a code test case Important is that someone else can get the test case up and running to reproduce it locally. For this -we urge you to verify that your sample yields the expected result by testing it via [SassMeister] [6] +we urge you to verify that your sample yields the expected result by testing it via [SassMeister][6] or directly via ruby sass or node-sass (or any other libsass implementor) before submitting your bug report. Once you verified all of the above, you may use the template below to file your bug report. diff --git a/src/libsass/.github/ISSUE_TEMPLATE.md b/src/libsass/.github/ISSUE_TEMPLATE.md old mode 100755 new mode 100644 index 723611139..43ffaaae1 --- a/src/libsass/.github/ISSUE_TEMPLATE.md +++ b/src/libsass/.github/ISSUE_TEMPLATE.md @@ -1,29 +1,54 @@ -### Title: Be as meaningful as possible in 60 chars if possible +[todo]: # (Title: Be as meaningful as possible) +[todo]: # (Title: Try to use 60 or less chars) + +[todo]: # (This is only a template!) +[todo]: # (remove unneeded bits) +[todo]: # (use github preview!) + +## input.scss + +[todo]: # (always test and report with scss syntax) +[todo]: # (use sass only when results differ from scss) -input.scss ```scss test { content: bar } ``` -[libsass 3.5.5] [1] +## Actual results + +[todo]: # (update version info!) + +[libsass 3.X.y][1] ```css test { content: bar; } ``` -ruby sass 3.4.21 +## Expected result + +[todo]: # (update version info!) + +ruby sass 3.X.y ```css test { content: bar; } ``` +[todo]: # (update version info!) +[todo]: # (example for node-sass!) + version info: ```cmd $ node-sass --version -node-sass 3.3.3 (Wrapper) [JavaScript] -libsass 3.2.5 (Sass Compiler) [C/C++] +node-sass 3.X.y (Wrapper) [JavaScript] +libsass 3.X.y (Sass Compiler) [C/C++] ``` +[todo]: # (Go to http://libsass.ocbnet.ch/srcmap) +[todo]: # (Enter your SCSS code and hit compile) +[todo]: # (Click `bookmark` and replace the url) +[todo]: # (link is used in actual results above) + [1]: http://libsass.ocbnet.ch/srcmap/#dGVzdCB7CiAgY29udGVudDogYmFyOyB9Cg== diff --git a/src/libsass/.gitignore b/src/libsass/.gitignore old mode 100755 new mode 100644 index af3786418..f2ee6beac --- a/src/libsass/.gitignore +++ b/src/libsass/.gitignore @@ -12,6 +12,8 @@ VERSION .cproject .project .settings/ +*.db +*.aps # Configuration stuff @@ -68,6 +70,7 @@ bin/* .libs/ win/bin *.user +win/*.db # Final results diff --git a/src/libsass/.travis.yml b/src/libsass/.travis.yml old mode 100755 new mode 100644 diff --git a/src/libsass/COPYING b/src/libsass/COPYING old mode 100755 new mode 100644 diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am old mode 100755 new mode 100644 index 3dfc2b532..9e658a415 --- a/src/libsass/GNUmakefile.am +++ b/src/libsass/GNUmakefile.am @@ -69,7 +69,7 @@ else endif SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -SASS_TESTER += -c $(SASS_LIBSASS_PATH)/tester$(EXEEXT) +SASS_TESTER += -c $(top_srcdir)/tester$(EXEEXT) test: $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) diff --git a/src/libsass/INSTALL b/src/libsass/INSTALL old mode 100755 new mode 100644 diff --git a/src/libsass/LICENSE b/src/libsass/LICENSE old mode 100755 new mode 100644 diff --git a/src/libsass/Makefile b/src/libsass/Makefile old mode 100755 new mode 100644 index 7215a1dc4..f3a294533 --- a/src/libsass/Makefile +++ b/src/libsass/Makefile @@ -299,7 +299,7 @@ install-shared: $(DESTDIR)$(PREFIX)/lib/libsass.so \ install-headers $(SASSC_BIN): $(BUILD) - $(MAKE) -C $(SASS_SASSC_PATH) + $(MAKE) -C $(SASS_SASSC_PATH) build-$(BUILD)-dev sassc: $(SASSC_BIN) $(SASSC_BIN) -v diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf old mode 100755 new mode 100644 diff --git a/src/libsass/Readme.md b/src/libsass/Readme.md old mode 100755 new mode 100644 index 3eaa63a59..908de2dc4 --- a/src/libsass/Readme.md +++ b/src/libsass/Readme.md @@ -1,95 +1,100 @@ -LibSass -======= - -by Aaron Leung ([@akhleung]), Hampton Catlin ([@hcatlin]), Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) - -[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass) -[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master) -[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE) -[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3) -[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/) - -https://github.com/sass/libsass - -LibSass is just a library, but if you want to RUN LibSass, -then go to https://github.com/sass/sassc or -https://github.com/sass/sassc-ruby or -[find your local implementer](docs/implementations.md). - -LibSass requires GCC 4.6+ or Clang/LLVM. If your OS is older, this version may not compile. - -On Windows, you need MinGW with GCC 4.6+ or VS 2013 Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows. +LibSass - Sass compiler written in C++ +====================================== + +Currently maintained by Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) +Originally created by Aaron Leung ([@akhleung]) and Hampton Catlin ([@hcatlin]) + +[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass "Travis CI") +[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master "Appveyor CI") +[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3 "Code coverage of spec tests") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Percentage of issues still open") +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Average time to resolve an issue") +[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE "Bountysource") +[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/ "Slack communication channels") + + +[LibSass](https://github.com/sass/libsass "LibSass GitHub Project") is just a library! +If you want to use LibSass to compile Sass, you need an implementer. Some +implementations are only bindings into other programming languages. But most also +ship with a command line interface (CLI) you can use directly. There is also +[SassC](https://github.com/sass/sassc), which is the official lightweight +CLI tool built by the same people as LibSass. + +### Excerpt of "sanctioned" implementations: + +- https://github.com/sass/node-sass (Node.js) +- https://github.com/sass/perl-libsass (Perl) +- https://github.com/sass/libsass-python (Python) +- https://github.com/wellington/go-libsass (Go) +- https://github.com/sass/sassc-ruby (Ruby) +- https://github.com/sass/libsass-net (C#) +- https://github.com/medialize/sass.js (JS) +- https://github.com/bit3/jsass (Java) + +This list does not say anything about the quality of either the listed or not listed [implementations](docs/implementations.md)! +The authors of the listed projects above are just known to work regularly together with LibSass developers. About ----- -LibSass is a C/C++ port of the Sass CSS precompiler. The original version was written in Ruby, but this version is meant for efficiency and portability. - -This library strives to be light, simple, and easy to build and integrate with a variety of platforms and languages. +LibSass is a C++ port of the original Ruby Sass CSS compiler with a [C API](docs/api-doc.md). +We coded LibSass with portability and efficiency in mind. You can expect LibSass to be a lot +faster than Ruby Sass and on par or faster than the best alternative CSS compilers around. Developing ---------- -As you may have noticed, the LibSass repo itself has -no executables and no tests. Oh noes! How can you develop??? - -Well, luckily, [SassC](http://github.com/sass/sassc) is the official binary wrapper for -LibSass and is *always* kept in sync. SassC is used by continous integration systems in -LibSass repository. When developing LibSass, it is best to actually -checkout SassC and develop in that directory with the SassC spec -and tests there. - -We even run Travis tests for SassC! +As noted above, the LibSass repository does not contain any binaries or other way to execute +LibSass. Therefore, you need an implementer to develop LibSass. Easiest is to start with +the official [SassC](http://github.com/sass/sassc) CLI wrapper. It is *guaranteed* to compile +with the latest code in LibSass master, since it is also used in the CI process. There is no +limitation here, as you may use any other LibSass implementer to test your LibSass branch! -Tests +Testing ------- -Since LibSass is a pure library, tests are run through the [SassSpec](https://github.com/sass/sass-spec) project using the [SassC](http://github.com/sass/sassc) driver. +Since LibSass is a pure library, tests are run through the [Sass-Spec](https://github.com/sass/sass-spec) +project using the [SassC](http://github.com/sass/sassc) CLI wrapper. To run the tests against LibSass while +developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and +then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. +Note that the scripts in the `./script` folder are mainly intended for our CI needs. -To run tests against LibSass while developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. +Building +-------- -### DEBUG builds +To build LibSass you need GCC 4.6+ or Clang/LLVM. If your OS is older, you may need to upgrade +them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.6+ or VS 2013 +Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows with various build chains +and/or command line interpreters. -Set the environment variable `DEBUG` to `1` to enable debug builds that can be debugged -with `gdb`, `lldb` and others. E.g.: use `$ DEBUG=1 ./script/spec` to run the tests with -a debug build. +See the [build docs for further instructions](docs/build.md)! -Library Usage +Compatibility ------------- -While LibSass is a library primarily written in C++, it provides a simple -C interface which should be used by most implementers. LibSass does not do -much on its own without an implementer. This can be a command line tool, like -[sassc](https://github.com/sass/sassc) or a [binding](docs/implementations.md) -to your favorite programing language. - -If you want to build or interface with LibSass, we recommend to check out the -documentation pages about [building LibSass](docs/build.md) and -the [C-API documentation](docs/api-doc.md). +Current LibSass 3.4 should be compatible with Sass 3.4. Please refer to the [sass compatibility +page](http://sass-compatibility.github.io/) for a more detailed comparison. But note that there +are still a few incomplete edges which we are aware of. Otherwise LibSass has reached a good level +of stability, thanks to our ever growing [Sass-Spec test suite](https://github.com/sass/sass-spec). About Sass ---------- -Sass is a CSS pre-processor language to add on exciting, new, -awesome features to CSS. Sass was the first language of its kind -and by far the most mature and up to date codebase. +Sass is a CSS pre-processor language to add on exciting, new, awesome features to CSS. Sass was +the first language of its kind and by far the most mature and up to date codebase. -Sass was originally concieved of by the co-creator of this library, -Hampton Catlin ([@hcatlin]). Most of the language has been the result of years -of work by Natalie Weizenbaum ([@nex3]) and Chris Eppstein ([@chriseppstein]). +Sass was originally conceived of by the co-creator of this library, Hampton Catlin ([@hcatlin]). +Most of the language has been the result of years of work by Natalie Weizenbaum ([@nex3]) and +Chris Eppstein ([@chriseppstein]). For more information about Sass itself, please visit http://sass-lang.com -Initial development of libsass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). +Initial development of LibSass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). Licensing --------- -Our MIT license is designed to be as simple, and liberal as possible. - -sass2scss was originally written by [Marcel Greter][@mgreter] -and he happily agreed to have it merged into the project. - +Our [MIT license](LICENSE) is designed to be as simple and liberal as possible. [@hcatlin]: https://github.com/hcatlin [@akhleung]: https://github.com/akhleung diff --git a/src/libsass/SECURITY.md b/src/libsass/SECURITY.md old mode 100755 new mode 100644 diff --git a/src/libsass/appveyor.yml b/src/libsass/appveyor.yml old mode 100755 new mode 100644 index 767e6738a..d964fade4 --- a/src/libsass/appveyor.yml +++ b/src/libsass/appveyor.yml @@ -7,8 +7,13 @@ environment: matrix: - Compiler: msvc Config: Release + Platform: Win32 - Compiler: msvc Config: Debug + Platform: Win32 + - Compiler: msvc + Config: Release + Platform: Win64 - Compiler: mingw Build: static - Compiler: mingw @@ -38,7 +43,7 @@ build_script: if ($env:Compiler -eq "mingw") { mingw32-make -j4 sassc } else { - msbuild /m:4 /p:Configuration=$env:Config sassc\win\sassc.sln + msbuild /m:4 /p:"Configuration=$env:Config;Platform=$env:Platform" sassc\win\sassc.sln } # print the branding art @@ -84,4 +89,3 @@ test_script: } else { echo "Success!" } - diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac old mode 100755 new mode 100644 diff --git a/src/libsass/contrib/libsass.spec b/src/libsass/contrib/libsass.spec old mode 100755 new mode 100644 diff --git a/src/libsass/contrib/plugin.cpp b/src/libsass/contrib/plugin.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/docs/README.md b/src/libsass/docs/README.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-context-example.md b/src/libsass/docs/api-context-example.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-context-internal.md b/src/libsass/docs/api-context-internal.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-context.md b/src/libsass/docs/api-context.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-doc.md b/src/libsass/docs/api-doc.md old mode 100755 new mode 100644 index 58e427eeb..376561612 --- a/src/libsass/docs/api-doc.md +++ b/src/libsass/docs/api-doc.md @@ -187,7 +187,7 @@ should be on par with the latest LibSass interface version. Some of them may not have all features implemented! 1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) -2. [Go Example](http://godoc.org/github.com/wellington/go-libsass#example-Context-Compile) +2. [Go Example](https://godoc.org/github.com/wellington/go-libsass#example-Compiler--Stdin) 3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) ## ABI forward compatibility diff --git a/src/libsass/docs/api-function-example.md b/src/libsass/docs/api-function-example.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-function-internal.md b/src/libsass/docs/api-function-internal.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-function.md b/src/libsass/docs/api-function.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-importer-example.md b/src/libsass/docs/api-importer-example.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-importer-internal.md b/src/libsass/docs/api-importer-internal.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-importer.md b/src/libsass/docs/api-importer.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-value-example.md b/src/libsass/docs/api-value-example.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-value-internal.md b/src/libsass/docs/api-value-internal.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/api-value.md b/src/libsass/docs/api-value.md old mode 100755 new mode 100644 index 9ac60f2d1..d78625875 --- a/src/libsass/docs/api-value.md +++ b/src/libsass/docs/api-value.md @@ -1,7 +1,7 @@ `Sass_Values` are used to pass values and their types between the implementer and LibSass. Sass knows various different value types (including nested arrays and hash-maps). If you implement a binding to another programming language, you -have to find a way to [marshal] [1] (convert) `Sass_Values` between the target +have to find a way to [marshal][1] (convert) `Sass_Values` between the target language and C. `Sass_Values` are currently only used by custom functions, but it should also be possible to use them without a compiler context. diff --git a/src/libsass/docs/build-on-darwin.md b/src/libsass/docs/build-on-darwin.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/build-on-gentoo.md b/src/libsass/docs/build-on-gentoo.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/build-on-windows.md b/src/libsass/docs/build-on-windows.md old mode 100755 new mode 100644 index 4639d4928..0afaa2e4c --- a/src/libsass/docs/build-on-windows.md +++ b/src/libsass/docs/build-on-windows.md @@ -3,14 +3,14 @@ Both should be considered experimental (MinGW was better tested)! ## Building via MingGW (makefiles) -First grab the latest [MinGW for windows] [1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. You need to have the following components installed: ![Visualization of components installed in the interface](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) -Next we need to install [git for windows] [2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. +Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. -If you want to run the spec test-suite you also need [ruby] [3] and a few gems available. Grab the [latest installer] [3] and make sure to add it the global path. Then install the missing gems: +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: ```bash gem install minitest diff --git a/src/libsass/docs/build-shared-library.md b/src/libsass/docs/build-shared-library.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/build-with-autotools.md b/src/libsass/docs/build-with-autotools.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/build-with-makefiles.md b/src/libsass/docs/build-with-makefiles.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/build-with-mingw.md b/src/libsass/docs/build-with-mingw.md old mode 100755 new mode 100644 index 6d5f4b2fd..416507f3c --- a/src/libsass/docs/build-with-mingw.md +++ b/src/libsass/docs/build-with-mingw.md @@ -1,13 +1,13 @@ ## Building LibSass with MingGW (makefiles) -First grab the latest [MinGW for windows] [1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. You need to have the following components installed: ![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) -Next we need to install [git for windows] [2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. +Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. -If you want to run the spec test-suite you also need [ruby] [3] and a few gems available. Grab the [latest installer] [3] and make sure to add it the global path. Then install the missing gems: +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: ```bash gem install minitest diff --git a/src/libsass/docs/build-with-visual-studio.md b/src/libsass/docs/build-with-visual-studio.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/build.md b/src/libsass/docs/build.md old mode 100755 new mode 100644 index b8c7c8d41..c656d8839 --- a/src/libsass/docs/build.md +++ b/src/libsass/docs/build.md @@ -1,4 +1,4 @@ -`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line] [6]. Or some [[bindings|Implementations]] to use it within your favorite programming language. You should be able to get [`sassc`] [6] running by following the instructions in this guide. +`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line][6]. Or some [bindings|Implementations][9] to use it within your favorite programming language. You should be able to get [`sassc`][6] running by following the instructions in this guide. Before starting, see [setup dev environment](setup-environment.md). @@ -11,27 +11,27 @@ We try to keep the code as OS independent and standard compliant as possible. Re Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). -- [Building with makefiles] [1] -- [Building with autotools] [2] +- [Building with makefiles][1] +- [Building with autotools][2] ### Building on Windows (experimental) Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! -- [Building with MinGW] [3] -- [Building with Visual Studio] [11] +- [Building with MinGW][3] +- [Building with Visual Studio][11] ### Building on Max OS X (untested) Works the same as on linux, but you can also install LibSass via `homebrew`. -- [Building on Mac OS X] [10] +- [Building on Mac OS X][10] ### Building a system library (experimental) Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. -- [Building shared system library] [4] +- [Building shared system library][4] Compiling with clang instead of gcc -- @@ -46,7 +46,7 @@ export CXX=/usr/bin/clang++ Running the spec test-suite -- -We constantly and automatically test `libsass` against the official [spec test-suite] [5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`] [6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: +We constantly and automatically test `libsass` against the official [spec test-suite][5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: ```bash ruby -v @@ -67,7 +67,7 @@ export LIBSASS_VERSION="x.y.z." Continuous Integration -- -We use two CI services to automatically test all commits against the latest [spec test-suite] [5]. +We use two CI services to automatically test all commits against the latest [spec test-suite][5]. - [LibSass on Travis-CI (linux)][7] [![Build Status](https://travis-ci.org/sass/libsass.png?branch=master)](https://travis-ci.org/sass/libsass) diff --git a/src/libsass/docs/compatibility-plan.md b/src/libsass/docs/compatibility-plan.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/contributing.md b/src/libsass/docs/contributing.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/custom-functions-internal.md b/src/libsass/docs/custom-functions-internal.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/dev-ast-memory.md b/src/libsass/docs/dev-ast-memory.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/implementations.md b/src/libsass/docs/implementations.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/plugins.md b/src/libsass/docs/plugins.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/setup-environment.md b/src/libsass/docs/setup-environment.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/source-map-internals.md b/src/libsass/docs/source-map-internals.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/trace.md b/src/libsass/docs/trace.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/triage.md b/src/libsass/docs/triage.md old mode 100755 new mode 100644 diff --git a/src/libsass/docs/unicode.md b/src/libsass/docs/unicode.md old mode 100755 new mode 100644 diff --git a/src/libsass/extconf.rb b/src/libsass/extconf.rb old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass.h b/src/libsass/include/sass.h old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass/base.h b/src/libsass/include/sass/base.h old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass/context.h b/src/libsass/include/sass/context.h old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass/functions.h b/src/libsass/include/sass/functions.h old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass/values.h b/src/libsass/include/sass/values.h old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass/version.h b/src/libsass/include/sass/version.h old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass/version.h.in b/src/libsass/include/sass/version.h.in old mode 100755 new mode 100644 diff --git a/src/libsass/include/sass2scss.h b/src/libsass/include/sass2scss.h old mode 100755 new mode 100644 index 5ddef1006..8736b2cb9 --- a/src/libsass/include/sass2scss.h +++ b/src/libsass/include/sass2scss.h @@ -37,7 +37,7 @@ #ifndef SASS2SCSS_VERSION // Hardcode once the file is copied from // https://github.com/mgreter/sass2scss -#define SASS2SCSS_VERSION "1.1.0" +#define SASS2SCSS_VERSION "1.1.1" #endif // add namespace for c++ diff --git a/src/libsass/m4/.gitkeep b/src/libsass/m4/.gitkeep old mode 100755 new mode 100644 diff --git a/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 b/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 old mode 100755 new mode 100644 diff --git a/src/libsass/res/resource.rc b/src/libsass/res/resource.rc old mode 100755 new mode 100644 index 1262b182c..fc49e6a87 --- a/src/libsass/res/resource.rc +++ b/src/libsass/res/resource.rc @@ -18,18 +18,18 @@ BEGIN BEGIN BLOCK "080904b0" BEGIN - VALUE "CompanyName", "Libsass Organization" + VALUE "CompanyName", "Sass Open Source Foundation" VALUE "FileDescription", "A C/C++ implementation of a Sass compiler" - VALUE "FileVersion", "0.9.0.0" + VALUE "FileVersion", "1.0.0.0" VALUE "InternalName", "libsass" - VALUE "LegalCopyright", "©2014 libsass.org" + VALUE "LegalCopyright", "\251 2017 libsass.org" VALUE "OriginalFilename", "libsass.dll" - VALUE "ProductName", "Libsass Library" - VALUE "ProductVersion", "0.9.0.0" + VALUE "ProductName", "LibSass Library" + VALUE "ProductVersion", "1.0.0.0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1200 END -END \ No newline at end of file +END diff --git a/src/libsass/script/ci-build-libsass b/src/libsass/script/ci-build-libsass index a5085fec6..40ea22ff7 100755 --- a/src/libsass/script/ci-build-libsass +++ b/src/libsass/script/ci-build-libsass @@ -114,7 +114,7 @@ then then echo "Travis rate limit on github exceeded" echo "Retrying via 'special purpose proxy'" - JSON=$(curl -L -sS http://libsass.ocbnet.ch/libsass-spec-pr.psgi/$TRAVIS_PULL_REQUEST) + JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) fi RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" diff --git a/src/libsass/script/ci-install-compiler b/src/libsass/script/ci-install-compiler index c36211a31..3a68b3a39 100755 --- a/src/libsass/script/ci-install-compiler +++ b/src/libsass/script/ci-install-compiler @@ -3,4 +3,4 @@ gem install minitest gem install minitap -pip install --user 'requests[security]' +pip2 install --user 'requests[security]' diff --git a/src/libsass/script/ci-install-deps b/src/libsass/script/ci-install-deps index b560f7441..27b485aa7 100755 --- a/src/libsass/script/ci-install-deps +++ b/src/libsass/script/ci-install-deps @@ -1,7 +1,7 @@ #!/bin/bash if [ "x$COVERAGE" == "xyes" ]; then - pip install --user gcovr - pip install --user cpp-coveralls + pip2 install --user gcovr + pip2 install --user cpp-coveralls else echo "no dependencies to install" fi @@ -15,9 +15,6 @@ if [ "x$AUTOTOOLS" == "xyes" ]; then sudo apt-get -qq install automake fi - # https://github.com/sass/libsass/pull/2183 - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - brew uninstall libtool - brew install libtool - fi fi + +exit 0 diff --git a/src/libsass/script/tap-driver b/src/libsass/script/tap-driver index 19aa531de..ed8a9a9dd 100755 --- a/src/libsass/script/tap-driver +++ b/src/libsass/script/tap-driver @@ -1,4 +1,4 @@ -#! /bin/sh +#!/usr/bin/env sh # Copyright (C) 2011-2013 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify diff --git a/src/libsass/src/GNUmakefile.am b/src/libsass/src/GNUmakefile.am old mode 100755 new mode 100644 diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp old mode 100755 new mode 100644 index d8c67e339..269e122b9 --- a/src/libsass/src/ast.cpp +++ b/src/libsass/src/ast.cpp @@ -19,6 +19,45 @@ namespace Sass { static Null sass_null(ParserState("null")); + bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + if (selector_) { + if (selector_->find(f)) return true; + } + // execute last + return f(this); + } + + bool Selector_List::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + for (Complex_Selector_Obj sel : elements()) { + if (sel->find(f)) return true; + } + // execute last + return f(this); + } + + bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + for (Simple_Selector_Obj sel : elements()) { + if (sel->find(f)) return true; + } + // execute last + return f(this); + } + + bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + if (head_ && head_->find(f)) return true; + if (tail_ && tail_->find(f)) return true; + // execute last + return f(this); + } + bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { if (Supports_Operator_Obj op = Cast(cond)) { return op->operand() != operand(); @@ -222,7 +261,6 @@ namespace Sass { // heads are not equal else return *l_h < *r_h; } - return true; } bool Complex_Selector::operator== (const Complex_Selector& rhs) const @@ -276,7 +314,7 @@ namespace Sass { else if ((!l_h && !r_h) || (!l_h && r_h->empty()) || (!r_h && l_h->empty()) || - (*l_h == *r_h)) + (l_h && r_h && *l_h == *r_h)) { // check combinator after heads if (l->combinator() != r->combinator()) @@ -296,14 +334,14 @@ namespace Sass { return false; } - Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs) { if (empty()) return rhs; Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); for (size_t i = 0, L = length(); i < L; ++i) { if (unified.isNull()) break; - unified = at(i)->unify_with(unified, ctx); + unified = at(i)->unify_with(unified); } return unified.detach(); } @@ -315,7 +353,6 @@ namespace Sass { if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); - return false; } @@ -326,7 +363,6 @@ namespace Sass { if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); - return false; } bool Compound_Selector::operator== (const Selector& rhs) const @@ -336,7 +372,6 @@ namespace Sass { if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); - return false; } bool Compound_Selector::operator< (const Selector& rhs) const @@ -346,7 +381,6 @@ namespace Sass { if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); - return false; } bool Selector_Schema::operator== (const Selector& rhs) const @@ -356,7 +390,6 @@ namespace Sass { if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); - return false; } bool Selector_Schema::operator< (const Selector& rhs) const @@ -366,7 +399,6 @@ namespace Sass { if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); - return false; } bool Simple_Selector::operator== (const Selector& rhs) const @@ -386,6 +418,7 @@ namespace Sass { // solve the double dispatch problem by using RTTI information via dynamic cast if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (const Element_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (name_ == rhs.name_) { return is_ns_eq(rhs); } @@ -397,6 +430,7 @@ namespace Sass { // solve the double dispatch problem by using RTTI information via dynamic cast if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs < rhs; } else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs < rhs; } + else if (const Element_Selector* lhs = Cast(this)) {return *lhs < rhs; } else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs < rhs; } if (is_ns_eq(rhs)) { return name_ < rhs.name_; } @@ -406,18 +440,18 @@ namespace Sass { bool Selector_List::operator== (const Selector& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast - if (Selector_List_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } - else if (Complex_Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } - else if (Compound_Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } + if (Selector_List_Ptr_Const sl = Cast(&rhs)) { return *this == *sl; } + else if (Complex_Selector_Ptr_Const cpx = Cast(&rhs)) { return *this == *cpx; } + else if (Compound_Selector_Ptr_Const cpd = Cast(&rhs)) { return *this == *cpd; } // no compare method return this == &rhs; } // Selector lists can be compared to comma lists - bool Selector_List::operator==(const Expression& rhs) const + bool Selector_List::operator== (const Expression& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast - if (List_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } + if (List_Ptr_Const ls = Cast(&rhs)) { return *ls == *this; } if (Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } // compare invalid (maybe we should error?) return false; @@ -452,8 +486,7 @@ namespace Sass { // advance ++i; ++n; } - // no mismatch - return true; + // there is no break?! } bool Selector_List::operator< (const Selector& rhs) const @@ -472,19 +505,19 @@ namespace Sass { return false; } - Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs) { for (size_t i = 0, L = rhs->length(); i < L; ++i) - { if (to_string(ctx.c_options) == rhs->at(i)->to_string(ctx.c_options)) return rhs; } + { if (to_string() == rhs->at(i)->to_string()) return rhs; } // check for pseudo elements because they are always last size_t i, L; bool found = false; - if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector)) + if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector) || typeid(*this) == typeid(Attribute_Selector)) { for (i = 0, L = rhs->length(); i < L; ++i) { - if ((Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) + if ((Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) { found = true; break; } } } @@ -492,20 +525,20 @@ namespace Sass { { for (i = 0, L = rhs->length(); i < L; ++i) { - if (Cast((*rhs)[i]) || Cast((*rhs)[i])) + if (Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) { found = true; break; } } } if (!found) { rhs->append(this); - return rhs; + } else { + rhs->elements().insert(rhs->elements().begin() + i, this); } - rhs->elements().insert(rhs->elements().begin() + i, this); return rhs; } - Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs, Context& ctx) + Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs) { // check if ns can be extended // true for no ns or universal @@ -536,7 +569,7 @@ namespace Sass { return this; } - Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs) { // TODO: handle namespaces @@ -554,7 +587,7 @@ namespace Sass { { // if rhs is universal, just return this tagname + rhs's qualifiers Element_Selector_Ptr ts = Cast(rhs_0); - rhs->at(0) = this->unify_with(ts, ctx); + rhs->at(0) = this->unify_with(ts); return rhs; } else if (Cast(rhs_0) || Cast(rhs_0)) { @@ -574,7 +607,7 @@ namespace Sass { // if rhs is universal, just return this tagname + rhs's qualifiers if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; // otherwise create new compound and unify first simple selector - rhs->at(0) = this->unify_with(rhs_0, ctx); + rhs->at(0) = this->unify_with(rhs_0); return rhs; } @@ -583,13 +616,13 @@ namespace Sass { return rhs; } - Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs) { rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs, ctx); + return Simple_Selector::unify_with(rhs); } - Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs) { for (size_t i = 0, L = rhs->length(); i < L; ++i) { @@ -598,10 +631,10 @@ namespace Sass { } } rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs, ctx); + return Simple_Selector::unify_with(rhs); } - Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs) { if (is_pseudo_element()) { @@ -612,7 +645,7 @@ namespace Sass { } } } - return Simple_Selector::unify_with(rhs, ctx); + return Simple_Selector::unify_with(rhs); } bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const @@ -669,12 +702,48 @@ namespace Sass { { if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) { - return *this == *w; + return is_ns_eq(rhs) && + name() == rhs.name() && + *this == *w; } + return false; + } + + bool Element_Selector::operator< (const Element_Selector& rhs) const + { + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Element_Selector::operator< (const Simple_Selector& rhs) const + { + if (Element_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Element_Selector::operator== (const Element_Selector& rhs) const + { return is_ns_eq(rhs) && name() == rhs.name(); } + bool Element_Selector::operator== (const Simple_Selector& rhs) const + { + if (Element_Selector_Ptr_Const w = Cast(&rhs)) + { + return is_ns_eq(rhs) && + name() == rhs.name() && + *this == *w; + } + return false; + } + bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const { if (is_ns_eq(rhs) && name() == rhs.name()) @@ -834,9 +903,9 @@ namespace Sass { for (size_t i = 0, iL = length(); i < iL; ++i) { - Selector_Obj lhs = (*this)[i]; + Selector_Obj wlhs = (*this)[i]; // very special case for wrapped matches selector - if (Wrapped_Selector_Obj wrapped = Cast(lhs)) { + if (Wrapped_Selector_Obj wrapped = Cast(wlhs)) { if (wrapped->name() == ":not") { if (Selector_List_Obj not_list = Cast(wrapped->selector())) { if (not_list->is_superselector_of(rhs, wrapped->name())) return false; @@ -845,7 +914,7 @@ namespace Sass { } } if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - lhs = wrapped->selector(); + wlhs = wrapped->selector(); if (Selector_List_Obj list = Cast(wrapped->selector())) { if (Compound_Selector_Obj comp = Cast(rhs)) { if (!wrapping.empty() && wrapping != wrapped->name()) return false; @@ -861,13 +930,11 @@ namespace Sass { if (wrapped->name() == wrapped_r->name()) { if (wrapped->is_superselector_of(wrapped_r)) { continue; - rset.insert(lhs->to_string()); - }} } } // match from here on as strings - lset.insert(lhs->to_string()); + lset.insert(wlhs->to_string()); } for (size_t n = 0, nL = rhs->length(); n < nL; ++n) @@ -913,7 +980,7 @@ namespace Sass { 0); } - Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other, Context& ctx) + Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other) { // get last tails (on the right side) @@ -939,7 +1006,7 @@ namespace Sass { SASS_ASSERT(r_last_head, "rhs head is null"); // get the unification of the last compound selectors - Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head, ctx); + Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); // abort if we could not unify heads if (unified == 0) return 0; @@ -956,28 +1023,28 @@ namespace Sass { } // create nodes from both selectors - Node lhsNode = complexSelectorToNode(this, ctx); - Node rhsNode = complexSelectorToNode(other, ctx); + Node lhsNode = complexSelectorToNode(this); + Node rhsNode = complexSelectorToNode(other); // overwrite universal base if (!is_universal) { // create some temporaries to convert to node Complex_Selector_Obj fake = unified->to_complex(); - Node unified_node = complexSelectorToNode(fake, ctx); + Node unified_node = complexSelectorToNode(fake); // add to permutate the list? rhsNode.plus(unified_node); } // do some magic we inherit from node and extend - Node node = Extend::subweave(lhsNode, rhsNode, ctx); - Selector_List_Ptr result = SASS_MEMORY_NEW(Selector_List, pstate()); + Node node = subweave(lhsNode, rhsNode); + Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate()); NodeDequePtr col = node.collection(); // move from collection to list for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) - { result->append(nodeToComplexSelector(Node::naiveTrim(*it, ctx), ctx)); } + { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); } // only return if list has some entries - return result->length() ? result : 0; + return result->length() ? result.detach() : 0; } @@ -1010,8 +1077,7 @@ namespace Sass { // advance now ++i; ++n; } - // no mismatch - return true; + // there is no break?! } bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) @@ -1091,12 +1157,7 @@ namespace Sass { { return false; } return lhs->tail()->is_superselector_of(marker->tail()); } - else - { - return lhs->tail()->is_superselector_of(marker->tail()); - } - // catch-all - return false; + return lhs->tail()->is_superselector_of(marker->tail()); } size_t Complex_Selector::length() const @@ -1110,7 +1171,7 @@ namespace Sass { // check if we need to append some headers // then we need to check for the combinator // only then we can safely set the new tail - void Complex_Selector::append(Context& ctx, Complex_Selector_Obj ss) + void Complex_Selector::append(Complex_Selector_Obj ss) { Complex_Selector_Obj t = ss->tail(); @@ -1127,17 +1188,18 @@ namespace Sass { error("Invalid parent selector", pstate_); } else if (last()->head_ && last()->head_->length()) { Compound_Selector_Obj rh = last()->head(); - size_t i = 0, L = h->length(); + size_t i; + size_t L = h->length(); if (Cast(h->first())) { - if (Class_Selector_Ptr sq = Cast(rh->last())) { - Class_Selector_Ptr sqs = SASS_MEMORY_COPY(sq); + if (Class_Selector_Ptr cs = Cast(rh->last())) { + Class_Selector_Ptr sqs = SASS_MEMORY_COPY(cs); sqs->name(sqs->name() + (*h)[0]->name()); sqs->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = sqs; rh->pstate(h->pstate()); for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Id_Selector_Ptr sq = Cast(rh->last())) { - Id_Selector_Ptr sqs = SASS_MEMORY_COPY(sq); + } else if (Id_Selector_Ptr is = Cast(rh->last())) { + Id_Selector_Ptr sqs = SASS_MEMORY_COPY(is); sqs->name(sqs->name() + (*h)[0]->name()); sqs->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = sqs; @@ -1163,7 +1225,7 @@ namespace Sass { } else { last()->head_->concat(h); } - } else { + } else if (last()->head_) { last()->head_->concat(h); } } else { @@ -1196,21 +1258,21 @@ namespace Sass { return list; } - Selector_List_Ptr Selector_List::resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent) + Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, bool implicit_parent) { if (!this->has_parent_ref()) return this; Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); Selector_List_Ptr ps = pstack.back(); for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { for (size_t si = 0, sL = this->length(); si < sL; ++si) { - Selector_List_Obj rv = at(si)->resolve_parent_refs(ctx, pstack, implicit_parent); + Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, implicit_parent); ss->concat(rv); } } return ss; } - Selector_List_Ptr Complex_Selector::resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent) + Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, bool implicit_parent) { Complex_Selector_Obj tail = this->tail(); Compound_Selector_Obj head = this->head(); @@ -1223,7 +1285,7 @@ namespace Sass { } // first resolve_parent_refs the tail (which may return an expanded list) - Selector_List_Obj tails = tail ? tail->resolve_parent_refs(ctx, pstack, implicit_parent) : 0; + Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, implicit_parent) : 0; if (head && head->length() > 0) { @@ -1260,16 +1322,16 @@ namespace Sass { ss->head(NULL); } // adjust for parent selector (1 char) - if (h->length()) { - ParserState state(h->at(0)->pstate()); - state.offset.column += 1; - state.column -= 1; - (*h)[0]->pstate(state); - } + // if (h->length()) { + // ParserState state(h->at(0)->pstate()); + // state.offset.column += 1; + // state.column -= 1; + // (*h)[0]->pstate(state); + // } // keep old parser state s->pstate(pstate()); // append new tail - s->append(ctx, ss); + s->append(ss); retval->append(s); } } @@ -1298,16 +1360,16 @@ namespace Sass { // \/ IMO ruby sass bug \/ ss->has_line_feed(false); // adjust for parent selector (1 char) - if (h->length()) { - ParserState state(h->at(0)->pstate()); - state.offset.column += 1; - state.column -= 1; - (*h)[0]->pstate(state); - } + // if (h->length()) { + // ParserState state(h->at(0)->pstate()); + // state.offset.column += 1; + // state.column -= 1; + // (*h)[0]->pstate(state); + // } // keep old parser state s->pstate(pstate()); // append new tail - s->append(ctx, ss); + s->append(ss); retval->append(s); } } @@ -1338,13 +1400,13 @@ namespace Sass { } // no parent selector in head else { - retval = this->tails(ctx, tails); + retval = this->tails(tails); } for (Simple_Selector_Obj ss : head->elements()) { if (Wrapped_Selector_Ptr ws = Cast(ss)) { if (Selector_List_Ptr sl = Cast(ws->selector())) { - if (parents) ws->selector(sl->resolve_parent_refs(ctx, pstack, implicit_parent)); + if (parents) ws->selector(sl->resolve_parent_refs(pstack, implicit_parent)); } } } @@ -1353,15 +1415,10 @@ namespace Sass { } // has no head - else { - return this->tails(ctx, tails); - } - - // unreachable - return 0; + return this->tails(tails); } - Selector_List_Ptr Complex_Selector::tails(Context& ctx, Selector_List_Ptr tails) + Selector_List_Ptr Complex_Selector::tails(Selector_List_Ptr tails) { Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_); if (tails && tails->length()) { @@ -1586,7 +1643,7 @@ namespace Sass { return false; } - Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs, Context& ctx) { + Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) { std::vector unified_complex_selectors; // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { @@ -1594,7 +1651,7 @@ namespace Sass { for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { Complex_Selector_Ptr seq2 = rhs->at(rhs_i); - Selector_List_Obj result = seq1->unify_with(seq2, ctx); + Selector_List_Obj result = seq1->unify_with(seq2); if( result ) { for(size_t i = 0, L = result->length(); i < L; ++i) { unified_complex_selectors.push_back( (*result)[i] ); @@ -1611,7 +1668,7 @@ namespace Sass { return final_result; } - void Selector_List::populate_extends(Selector_List_Obj extendee, Context& ctx, Subset_Map& extends) + void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends) { Selector_List_Ptr extender = this; @@ -1650,7 +1707,7 @@ namespace Sass { pstate_.offset += element->pstate().offset; } - Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs, Context& ctx) + Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs) { Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate()); // result->has_parent_reference(has_parent_reference()); @@ -1659,10 +1716,10 @@ namespace Sass { for (size_t i = 0, L = length(); i < L; ++i) { bool found = false; - std::string thisSelector((*this)[i]->to_string(ctx.c_options)); + std::string thisSelector((*this)[i]->to_string()); for (size_t j = 0, M = rhs->length(); j < M; ++j) { - if (thisSelector == (*rhs)[j]->to_string(ctx.c_options)) + if (thisSelector == (*rhs)[j]->to_string()) { found = true; break; @@ -1674,7 +1731,7 @@ namespace Sass { return result; } - void Compound_Selector::mergeSources(ComplexSelectorSet& sources, Context& ctx) + void Compound_Selector::mergeSources(ComplexSelectorSet& sources) { for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); @@ -1708,31 +1765,31 @@ namespace Sass { void Arguments::adjust_after_pushing(Argument_Obj a) { if (!a->name().empty()) { - if (/* has_rest_argument_ || */ has_keyword_argument_) { + if (has_keyword_argument()) { error("named arguments must precede variable-length argument", a->pstate()); } - has_named_arguments_ = true; + has_named_arguments(true); } else if (a->is_rest_argument()) { - if (has_rest_argument_) { + if (has_rest_argument()) { error("functions and mixins may only be called with one variable-length argument", a->pstate()); } if (has_keyword_argument_) { error("only keyword arguments may follow variable arguments", a->pstate()); } - has_rest_argument_ = true; + has_rest_argument(true); } else if (a->is_keyword_argument()) { - if (has_keyword_argument_) { + if (has_keyword_argument()) { error("functions and mixins may only be called with one keyword argument", a->pstate()); } - has_keyword_argument_ = true; + has_keyword_argument(true); } else { - if (has_rest_argument_) { + if (has_rest_argument()) { error("ordinal arguments must precede variable-length arguments", a->pstate()); } - if (has_named_arguments_) { + if (has_named_arguments()) { error("ordinal arguments must precede named arguments", a->pstate()); } } @@ -1756,362 +1813,47 @@ namespace Sass { Number::Number(ParserState pstate, double val, std::string u, bool zero) : Value(pstate), + Units(), value_(val), zero_(zero), - numerator_units_(std::vector()), - denominator_units_(std::vector()), hash_(0) { - size_t l = 0, r = 0; + size_t l = 0; + size_t r; if (!u.empty()) { bool nominator = true; while (true) { r = u.find_first_of("*/", l); std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); if (!unit.empty()) { - if (nominator) numerator_units_.push_back(unit); - else denominator_units_.push_back(unit); + if (nominator) numerators.push_back(unit); + else denominators.push_back(unit); } if (r == std::string::npos) break; // ToDo: should error for multiple slashes // if (!nominator && u[r] == '/') error(...) if (u[r] == '/') nominator = false; + // strange math parsing? + // else if (u[r] == '*') + // nominator = true; l = r + 1; } } concrete_type(NUMBER); } - std::string Number::unit() const + // cancel out unnecessary units + void Number::reduce() { - std::string u; - for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { - if (i) u += '*'; - u += numerator_units_[i]; - } - if (!denominator_units_.empty()) u += '/'; - for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) { - if (i) u += '*'; - u += denominator_units_[i]; - } - return u; + // apply conversion factor + value_ *= this->Units::reduce(); } - bool Number::is_valid_css_unit() const + void Number::normalize() { - return numerator_units().size() <= 1 && - denominator_units().size() == 0; - } - - bool Number::is_unitless() const - { return numerator_units_.empty() && denominator_units_.empty(); } - - void Number::normalize(const std::string& prefered, bool strict) - { - - // first make sure same units cancel each other out - // it seems that a map table will fit nicely to do this - // we basically construct exponents for each unit - // has the advantage that they will be pre-sorted - std::map exponents; - - // initialize by summing up occurences in unit vectors - for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]]; - for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]]; - - // the final conversion factor - double factor = 1; - - // get the first entry of numerators - // forward it when entry is converted - std::vector::iterator nom_it = numerator_units_.begin(); - std::vector::iterator nom_end = numerator_units_.end(); - std::vector::iterator denom_it = denominator_units_.begin(); - std::vector::iterator denom_end = denominator_units_.end(); - - // main normalization loop - // should be close to optimal - while (denom_it != denom_end) - { - // get and increment afterwards - const std::string denom = *(denom_it ++); - // skip already canceled out unit - if (exponents[denom] >= 0) continue; - // skip all units we don't know how to convert - if (string_to_unit(denom) == UNKNOWN) continue; - // now search for nominator - while (nom_it != nom_end) - { - // get and increment afterwards - const std::string nom = *(nom_it ++); - // skip already canceled out unit - if (exponents[nom] <= 0) continue; - // skip all units we don't know how to convert - if (string_to_unit(nom) == UNKNOWN) continue; - // we now have two convertable units - // add factor for current conversion - factor *= conversion_factor(nom, denom, strict); - // update nominator/denominator exponent - -- exponents[nom]; ++ exponents[denom]; - // inner loop done - break; - } - } - - // now we can build up the new unit arrays - numerator_units_.clear(); - denominator_units_.clear(); - - // build them by iterating over the exponents - for (auto exp : exponents) - { - // maybe there is more effecient way to push - // the same item multiple times to a vector? - for(size_t i = 0, S = abs(exp.second); i < S; ++i) - { - // opted to have these switches in the inner loop - // makes it more readable and should not cost much - if (!exp.first.empty()) { - if (exp.second < 0) denominator_units_.push_back(exp.first); - else if (exp.second > 0) numerator_units_.push_back(exp.first); - } - } - } - - // apply factor to value_ - // best precision this way - value_ *= factor; - - // maybe convert to other unit - // easier implemented on its own - try { convert(prefered, strict); } - catch (incompatibleUnits& err) - { error(err.what(), pstate()); } - catch (...) { throw; } - - } - - // this does not cover all cases (multiple prefered units) - double Number::convert_factor(const Number& n) const - { - - // first make sure same units cancel each other out - // it seems that a map table will fit nicely to do this - // we basically construct exponents for each unit class - // std::map exponents; - // initialize by summing up occurences in unit vectors - // for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[unit_to_class(numerator_units_[i])]; - // for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[unit_to_class(denominator_units_[i])]; - - std::vector l_miss_nums(0); - std::vector l_miss_dens(0); - // create copy since we need these for state keeping - std::vector r_nums(n.numerator_units_); - std::vector r_dens(n.denominator_units_); - - std::vector::const_iterator l_num_it = numerator_units_.begin(); - std::vector::const_iterator l_num_end = numerator_units_.end(); - - bool l_unitless = is_unitless(); - bool r_unitless = n.is_unitless(); - - // overall conversion - double factor = 1; - - // process all left numerators - while (l_num_it != l_num_end) - { - // get and increment afterwards - const std::string l_num = *(l_num_it ++); - - std::vector::iterator r_num_it = r_nums.begin(); - std::vector::iterator r_num_end = r_nums.end(); - - bool found = false; - // search for compatible numerator - while (r_num_it != r_num_end) - { - // get and increment afterwards - const std::string r_num = *(r_num_it); - // get possible converstion factor for units - double conversion = conversion_factor(l_num, r_num, false); - // skip incompatible numerator - if (conversion == 0) { - ++ r_num_it; - continue; - } - // apply to global factor - factor *= conversion; - // remove item from vector - r_nums.erase(r_num_it); - // found numerator - found = true; - break; - } - // maybe we did not find any - // left numerator is leftover - if (!found) l_miss_nums.push_back(l_num); - } - - std::vector::const_iterator l_den_it = denominator_units_.begin(); - std::vector::const_iterator l_den_end = denominator_units_.end(); - - // process all left denominators - while (l_den_it != l_den_end) - { - // get and increment afterwards - const std::string l_den = *(l_den_it ++); - - std::vector::iterator r_den_it = r_dens.begin(); - std::vector::iterator r_den_end = r_dens.end(); - - bool found = false; - // search for compatible denominator - while (r_den_it != r_den_end) - { - // get and increment afterwards - const std::string r_den = *(r_den_it); - // get possible converstion factor for units - double conversion = conversion_factor(l_den, r_den, false); - // skip incompatible denominator - if (conversion == 0) { - ++ r_den_it; - continue; - } - // apply to global factor - factor *= conversion; - // remove item from vector - r_dens.erase(r_den_it); - // found denominator - found = true; - break; - } - // maybe we did not find any - // left denominator is leftover - if (!found) l_miss_dens.push_back(l_den); - } - - // check left-overs (ToDo: might cancel out) - if (l_miss_nums.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(n, *this); - } - if (l_miss_dens.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(n, *this); - } - if (r_nums.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(n, *this); - } - if (r_dens.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(n, *this); - } - - return factor; - } - - // this does not cover all cases (multiple prefered units) - bool Number::convert(const std::string& prefered, bool strict) - { - // no conversion if unit is empty - if (prefered.empty()) return true; - - // first make sure same units cancel each other out - // it seems that a map table will fit nicely to do this - // we basically construct exponents for each unit - // has the advantage that they will be pre-sorted - std::map exponents; - - // initialize by summing up occurences in unit vectors - for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]]; - for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]]; - - // the final conversion factor - double factor = 1; - - std::vector::iterator denom_it = denominator_units_.begin(); - std::vector::iterator denom_end = denominator_units_.end(); - - // main normalization loop - // should be close to optimal - while (denom_it != denom_end) - { - // get and increment afterwards - const std::string denom = *(denom_it ++); - // check if conversion is needed - if (denom == prefered) continue; - // skip already canceled out unit - if (exponents[denom] >= 0) continue; - // skip all units we don't know how to convert - if (string_to_unit(denom) == UNKNOWN) continue; - // we now have two convertable units - // add factor for current conversion - factor *= conversion_factor(denom, prefered, strict); - // update nominator/denominator exponent - ++ exponents[denom]; -- exponents[prefered]; - } - - std::vector::iterator nom_it = numerator_units_.begin(); - std::vector::iterator nom_end = numerator_units_.end(); - - // now search for nominator - while (nom_it != nom_end) - { - // get and increment afterwards - const std::string nom = *(nom_it ++); - // check if conversion is needed - if (nom == prefered) continue; - // skip already canceled out unit - if (exponents[nom] <= 0) continue; - // skip all units we don't know how to convert - if (string_to_unit(nom) == UNKNOWN) continue; - // we now have two convertable units - // add factor for current conversion - factor *= conversion_factor(nom, prefered, strict); - // update nominator/denominator exponent - -- exponents[nom]; ++ exponents[prefered]; - } - - // now we can build up the new unit arrays - numerator_units_.clear(); - denominator_units_.clear(); - - // build them by iterating over the exponents - for (auto exp : exponents) - { - // maybe there is more effecient way to push - // the same item multiple times to a vector? - for(size_t i = 0, S = abs(exp.second); i < S; ++i) - { - // opted to have these switches in the inner loop - // makes it more readable and should not cost much - if (!exp.first.empty()) { - if (exp.second < 0) denominator_units_.push_back(exp.first); - else if (exp.second > 0) numerator_units_.push_back(exp.first); - } - } - } - - // apply factor to value_ - // best precision this way - value_ *= factor; - - // success? - return true; - - } - - // useful for making one number compatible with another - std::string Number::find_convertible_unit() const - { - for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { - std::string u(numerator_units_[i]); - if (string_to_unit(u) != UNKNOWN) return u; - } - for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) { - std::string u(denominator_units_[i]); - if (string_to_unit(u) != UNKNOWN) return u; - } - return std::string(); + // apply conversion factor + value_ *= this->Units::normalize(); } bool Custom_Warning::operator== (const Expression& rhs) const @@ -2132,37 +1874,43 @@ namespace Sass { bool Number::operator== (const Expression& rhs) const { - if (Number_Ptr_Const r = Cast(&rhs)) { - size_t lhs_units = numerator_units_.size() + denominator_units_.size(); - size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); - // unitless and only having one unit seems equivalent (will change in future) - if (!lhs_units || !rhs_units) { - return std::fabs(value() - r->value()) < NUMBER_EPSILON; - } - return (numerator_units_ == r->numerator_units_) && - (denominator_units_ == r->denominator_units_) && - std::fabs(value() - r->value()) < NUMBER_EPSILON; + if (auto rhsnr = Cast(&rhs)) { + return *this == *rhsnr; } return false; } - bool Number::operator< (const Number& rhs) const + bool Number::operator== (const Number& rhs) const { - size_t lhs_units = numerator_units_.size() + denominator_units_.size(); - size_t rhs_units = rhs.numerator_units_.size() + rhs.denominator_units_.size(); + Number l(*this), r(rhs); l.reduce(); r.reduce(); + size_t lhs_units = l.numerators.size() + l.denominators.size(); + size_t rhs_units = r.numerators.size() + r.denominators.size(); // unitless and only having one unit seems equivalent (will change in future) if (!lhs_units || !rhs_units) { - return value() < rhs.value(); + return NEAR_EQUAL(l.value(), r.value()); } + l.normalize(); r.normalize(); + Units &lhs_unit = l, &rhs_unit = r; + return lhs_unit == rhs_unit && + NEAR_EQUAL(l.value(), r.value()); + } - Number tmp_r(&rhs); // copy - tmp_r.normalize(find_convertible_unit()); - std::string l_unit(unit()); - std::string r_unit(tmp_r.unit()); - if (unit() != tmp_r.unit()) { - error("cannot compare numbers with incompatible units", pstate()); + bool Number::operator< (const Number& rhs) const + { + Number l(*this), r(rhs); l.reduce(); r.reduce(); + size_t lhs_units = l.numerators.size() + l.denominators.size(); + size_t rhs_units = r.numerators.size() + r.denominators.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return l.value() < r.value(); + } + l.normalize(); r.normalize(); + Units &lhs_unit = l, &rhs_unit = r; + if (!(lhs_unit == rhs_unit)) { + throw Exception::IncompatibleUnits(rhs, *this); } - return value() < tmp_r.value(); + return lhs_unit < rhs_unit || + l.value() < r.value(); } bool String_Quoted::operator== (const Expression& rhs) const @@ -2269,6 +2017,16 @@ namespace Sass { return rhs.concrete_type() == NULL_VAL; } + bool Function::operator== (const Expression& rhs) const + { + if (Function_Ptr_Const r = Cast(&rhs)) { + Definition_Ptr_Const d1 = Cast(definition()); + Definition_Ptr_Const d2 = Cast(r->definition()); + return d1 && d2 && d1 == d2 && is_css() == r->is_css(); + } + return false; + } + size_t List::size() const { if (!is_arglist_) return length(); // arglist expects a list of arguments @@ -2324,6 +2082,13 @@ namespace Sass { return quote(value_, '*'); } + bool Declaration::is_invisible() const + { + if (is_custom_property()) return false; + + return !(value_ && value_->concrete_type() != Expression::NULL_VAL); + } + ////////////////////////////////////////////////////////////////////////////////////////// // Additional method on Lists to retrieve values directly or from an encompassed Argument. ////////////////////////////////////////////////////////////////////////////////////////// @@ -2343,7 +2108,7 @@ namespace Sass { ////////////////////////////////////////////////////////////////////////////////////////// // Convert map to (key, value) list. ////////////////////////////////////////////////////////////////////////////////////////// - List_Obj Map::to_list(Context& ctx, ParserState& pstate) { + List_Obj Map::to_list(ParserState& pstate) { List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); for (auto key : keys()) { @@ -2404,6 +2169,7 @@ namespace Sass { IMPLEMENT_AST_OPERATORS(Custom_Error); IMPLEMENT_AST_OPERATORS(List); IMPLEMENT_AST_OPERATORS(Map); + IMPLEMENT_AST_OPERATORS(Function); IMPLEMENT_AST_OPERATORS(Number); IMPLEMENT_AST_OPERATORS(Binary_Expression); IMPLEMENT_AST_OPERATORS(String_Schema); @@ -2447,7 +2213,6 @@ namespace Sass { IMPLEMENT_AST_OPERATORS(Function_Call_Schema); IMPLEMENT_AST_OPERATORS(Block); IMPLEMENT_AST_OPERATORS(Content); - IMPLEMENT_AST_OPERATORS(Textual); IMPLEMENT_AST_OPERATORS(Trace); IMPLEMENT_AST_OPERATORS(Keyframe_Rule); IMPLEMENT_AST_OPERATORS(Bubble); @@ -2455,5 +2220,4 @@ namespace Sass { IMPLEMENT_AST_OPERATORS(Placeholder_Selector); IMPLEMENT_AST_OPERATORS(Definition); IMPLEMENT_AST_OPERATORS(Declaration); - } diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp old mode 100755 new mode 100644 index d41e434c0..8bd2e6af3 --- a/src/libsass/src/ast.hpp +++ b/src/libsass/src/ast.hpp @@ -75,6 +75,9 @@ namespace Sass { // Note: most methods follow precision option const double NUMBER_EPSILON = 0.00000000000001; + // macro to test if numbers are equal within a small error margin + #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON + // ToDo: where does this fit best? // We don't share this with C-API? class Operand { @@ -124,6 +127,9 @@ namespace Sass { virtual const std::string to_string(Sass_Inspect_Options opt) const; virtual const std::string to_string() const; virtual void cloneChildren() {}; + // generic find function (not fully implemented yet) + // ToDo: add specific implementions to all children + virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; public: void update_pstate(const ParserState& pstate); public: @@ -166,9 +172,11 @@ namespace Sass { MAP, SELECTOR, NULL_VAL, + FUNCTION_VAL, C_WARNING, C_ERROR, FUNCTION, + VARIABLE, NUM_TYPES }; enum Simple_Type { @@ -358,8 +366,11 @@ namespace Sass { void reset_duplicate_key() { duplicate_key_ = 0; } virtual void adjust_after_pushing(std::pair p) { } public: - Hashed(size_t s = 0) : elements_(ExpressionMap(s)), list_(std::vector()) - { elements_.reserve(s); list_.reserve(s); reset_duplicate_key(); } + Hashed(size_t s = 0) + : elements_(ExpressionMap(s)), + list_(std::vector()), + hash_(0), duplicate_key_(NULL) + { elements_.reserve(s); list_.reserve(s); } virtual ~Hashed(); size_t length() const { return list_.size(); } bool empty() const { return list_.empty(); } @@ -652,19 +663,22 @@ namespace Sass { ADD_PROPERTY(String_Obj, property) ADD_PROPERTY(Expression_Obj, value) ADD_PROPERTY(bool, is_important) + ADD_PROPERTY(bool, is_custom_property) ADD_PROPERTY(bool, is_indented) public: Declaration(ParserState pstate, - String_Obj prop, Expression_Obj val, bool i = false, Block_Obj b = 0) - : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_indented_(false) + String_Obj prop, Expression_Obj val, bool i = false, bool c = false, Block_Obj b = 0) + : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) { statement_type(DECLARATION); } Declaration(const Declaration* ptr) : Has_Block(ptr), property_(ptr->property_), value_(ptr->value_), is_important_(ptr->is_important_), + is_custom_property_(ptr->is_custom_property_), is_indented_(ptr->is_indented_) { statement_type(DECLARATION); } + virtual bool is_invisible() const; ATTACH_AST_OPERATIONS(Declaration) ATTACH_OPERATIONS() }; @@ -1036,9 +1050,13 @@ namespace Sass { class Content : public Statement { ADD_PROPERTY(Media_Block_Ptr, media_block) public: - Content(ParserState pstate) : Statement(pstate) + Content(ParserState pstate) + : Statement(pstate), + media_block_(NULL) { statement_type(CONTENT); } - Content(const Content* ptr) : Statement(ptr) + Content(const Content* ptr) + : Statement(ptr), + media_block_(ptr->media_block_) { statement_type(CONTENT); } ATTACH_AST_OPERATIONS(Content) ATTACH_OPERATIONS() @@ -1125,7 +1143,7 @@ namespace Sass { std::string type() const { return "map"; } static std::string type_name() { return "map"; } bool is_invisible() const { return empty(); } - List_Obj to_list(Context& ctx, ParserState& pstate); + List_Obj to_list(ParserState& pstate); virtual size_t hash() { @@ -1147,22 +1165,22 @@ namespace Sass { inline static const std::string sass_op_to_name(enum Sass_OP op) { switch (op) { - case AND: return "and"; break; - case OR: return "or"; break; - case EQ: return "eq"; break; - case NEQ: return "neq"; break; - case GT: return "gt"; break; - case GTE: return "gte"; break; - case LT: return "lt"; break; - case LTE: return "lte"; break; - case ADD: return "plus"; break; - case SUB: return "sub"; break; - case MUL: return "times"; break; - case DIV: return "div"; break; - case MOD: return "mod"; break; + case AND: return "and"; + case OR: return "or"; + case EQ: return "eq"; + case NEQ: return "neq"; + case GT: return "gt"; + case GTE: return "gte"; + case LT: return "lt"; + case LTE: return "lte"; + case ADD: return "plus"; + case SUB: return "sub"; + case MUL: return "times"; + case DIV: return "div"; + case MOD: return "mod"; // this is only used internally! - case NUM_OPS: return "[OPS]"; break; - default: return "invalid"; break; + case NUM_OPS: return "[OPS]"; + default: return "invalid"; } } @@ -1191,42 +1209,42 @@ namespace Sass { { } const std::string type_name() { switch (optype()) { - case AND: return "and"; break; - case OR: return "or"; break; - case EQ: return "eq"; break; - case NEQ: return "neq"; break; - case GT: return "gt"; break; - case GTE: return "gte"; break; - case LT: return "lt"; break; - case LTE: return "lte"; break; - case ADD: return "add"; break; - case SUB: return "sub"; break; - case MUL: return "mul"; break; - case DIV: return "div"; break; - case MOD: return "mod"; break; + case AND: return "and"; + case OR: return "or"; + case EQ: return "eq"; + case NEQ: return "neq"; + case GT: return "gt"; + case GTE: return "gte"; + case LT: return "lt"; + case LTE: return "lte"; + case ADD: return "add"; + case SUB: return "sub"; + case MUL: return "mul"; + case DIV: return "div"; + case MOD: return "mod"; // this is only used internally! - case NUM_OPS: return "[OPS]"; break; - default: return "invalid"; break; + case NUM_OPS: return "[OPS]"; + default: return "invalid"; } } const std::string separator() { switch (optype()) { - case AND: return "&&"; break; - case OR: return "||"; break; - case EQ: return "=="; break; - case NEQ: return "!="; break; - case GT: return ">"; break; - case GTE: return ">="; break; - case LT: return "<"; break; - case LTE: return "<="; break; - case ADD: return "+"; break; - case SUB: return "-"; break; - case MUL: return "*"; break; - case DIV: return "/"; break; - case MOD: return "%"; break; + case AND: return "&&"; + case OR: return "||"; + case EQ: return "=="; + case NEQ: return "!="; + case GT: return ">"; + case GTE: return ">="; + case LT: return "<"; + case LTE: return "<="; + case ADD: return "+"; + case SUB: return "-"; + case MUL: return "*"; + case DIV: return "/"; + case MOD: return "%"; // this is only used internally! - case NUM_OPS: return "[OPS]"; break; - default: return "invalid"; break; + case NUM_OPS: return "[OPS]"; + default: return "invalid"; } } bool is_left_interpolant(void) const; @@ -1277,7 +1295,7 @@ namespace Sass { //////////////////////////////////////////////////////////////////////////// class Unary_Expression : public Expression { public: - enum Type { PLUS, MINUS, NOT }; + enum Type { PLUS, MINUS, NOT, SLASH }; private: HASH_PROPERTY(Type, optype) HASH_PROPERTY(Expression_Obj, operand) @@ -1294,10 +1312,11 @@ namespace Sass { { } const std::string type_name() { switch (optype_) { - case PLUS: return "plus"; break; - case MINUS: return "minus"; break; - case NOT: return "not"; break; - default: return "invalid"; break; + case PLUS: return "plus"; + case MINUS: return "minus"; + case SLASH: return "slash"; + case NOT: return "not"; + default: return "invalid"; } } virtual bool operator==(const Expression& rhs) const @@ -1422,18 +1441,54 @@ namespace Sass { ATTACH_OPERATIONS() }; + //////////////////////////////////////////////////// + // Function reference. + //////////////////////////////////////////////////// + class Function : public Value { + public: + ADD_PROPERTY(Definition_Obj, definition) + ADD_PROPERTY(bool, is_css) + public: + Function(ParserState pstate, Definition_Obj def, bool css) + : Value(pstate), definition_(def), is_css_(css) + { concrete_type(FUNCTION_VAL); } + Function(const Function* ptr) + : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) + { concrete_type(FUNCTION_VAL); } + + std::string type() const { return "function"; } + static std::string type_name() { return "function"; } + bool is_invisible() const { return true; } + + std::string name() { + if (definition_) { + return definition_->name(); + } + return ""; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Function) + ATTACH_OPERATIONS() + }; + ////////////////// // Function calls. ////////////////// class Function_Call : public PreValue { HASH_CONSTREF(std::string, name) HASH_PROPERTY(Arguments_Obj, arguments) + HASH_PROPERTY(Function_Obj, func) ADD_PROPERTY(bool, via_call) ADD_PROPERTY(void*, cookie) size_t hash_; public: Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie) - : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(cookie), hash_(0) + : PreValue(pstate), name_(n), arguments_(args), func_(0), via_call_(false), cookie_(cookie), hash_(0) + { concrete_type(FUNCTION); } + Function_Call(ParserState pstate, std::string n, Arguments_Obj args, Function_Obj func) + : PreValue(pstate), name_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) { concrete_type(FUNCTION); } Function_Call(ParserState pstate, std::string n, Arguments_Obj args) : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) @@ -1442,11 +1497,17 @@ namespace Sass { : PreValue(ptr), name_(ptr->name_), arguments_(ptr->arguments_), + func_(ptr->func_), via_call_(ptr->via_call_), cookie_(ptr->cookie_), hash_(ptr->hash_) { concrete_type(FUNCTION); } + bool is_css() { + if (func_) return func_->is_css(); + return false; + } + virtual bool operator==(const Expression& rhs) const { try @@ -1505,10 +1566,10 @@ namespace Sass { public: Variable(ParserState pstate, std::string n) : PreValue(pstate), name_(n) - { } + { concrete_type(VARIABLE); } Variable(const Variable* ptr) : PreValue(ptr), name_(ptr->name_) - { } + { concrete_type(VARIABLE); } virtual bool operator==(const Expression& rhs) const { @@ -1533,106 +1594,44 @@ namespace Sass { ATTACH_OPERATIONS() }; - //////////////////////////////////////////////////////////////////////////// - // Textual (i.e., unevaluated) numeric data. Variants are distinguished with - // a type tag. - //////////////////////////////////////////////////////////////////////////// - class Textual : public Expression { - public: - enum Type { NUMBER, PERCENTAGE, DIMENSION, HEX }; - private: - HASH_PROPERTY(Type, valtype) - HASH_CONSTREF(std::string, value) - size_t hash_; - public: - Textual(ParserState pstate, Type t, std::string val) - : Expression(pstate, DELAYED), valtype_(t), value_(val), - hash_(0) - { } - Textual(const Textual* ptr) - : Expression(ptr), - valtype_(ptr->valtype_), - value_(ptr->value_), - hash_(ptr->hash_) - { } - - virtual bool operator==(const Expression& rhs) const - { - try - { - Textual_Ptr_Const e = Cast(&rhs); - return e && value() == e->value() && type() == e->type(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(value_); - hash_combine(hash_, std::hash()(valtype_)); - } - return hash_; - } - - ATTACH_AST_OPERATIONS(Textual) - ATTACH_OPERATIONS() - }; - //////////////////////////////////////////////// // Numbers, percentages, dimensions, and colors. //////////////////////////////////////////////// - class Number : public Value { + class Number : public Value, public Units { HASH_PROPERTY(double, value) ADD_PROPERTY(bool, zero) - std::vector numerator_units_; - std::vector denominator_units_; size_t hash_; public: Number(ParserState pstate, double val, std::string u = "", bool zero = true); Number(const Number* ptr) : Value(ptr), + Units(ptr), value_(ptr->value_), zero_(ptr->zero_), - numerator_units_(ptr->numerator_units_), - denominator_units_(ptr->denominator_units_), hash_(ptr->hash_) { concrete_type(NUMBER); } bool zero() { return zero_; } - bool is_valid_css_unit() const; - std::vector& numerator_units() { return numerator_units_; } - std::vector& denominator_units() { return denominator_units_; } - const std::vector& numerator_units() const { return numerator_units_; } - const std::vector& denominator_units() const { return denominator_units_; } std::string type() const { return "number"; } static std::string type_name() { return "number"; } - std::string unit() const; - bool is_unitless() const; - double convert_factor(const Number&) const; - bool convert(const std::string& unit = "", bool strict = false); - void normalize(const std::string& unit = "", bool strict = false); - // useful for making one number compatible with another - std::string find_convertible_unit() const; + void reduce(); + void normalize(); virtual size_t hash() { if (hash_ == 0) { hash_ = std::hash()(value_); - for (const auto numerator : numerator_units()) + for (const auto numerator : numerators) hash_combine(hash_, std::hash()(numerator)); - for (const auto denominator : denominator_units()) + for (const auto denominator : denominators) hash_combine(hash_, std::hash()(denominator)); } return hash_; } virtual bool operator< (const Number& rhs) const; + virtual bool operator== (const Number& rhs) const; virtual bool operator== (const Expression& rhs) const; ATTACH_AST_OPERATIONS(Number) ATTACH_OPERATIONS() @@ -2217,22 +2216,22 @@ namespace Sass { void adjust_after_pushing(Parameter_Obj p) { if (p->default_value()) { - if (has_rest_parameter_) { + if (has_rest_parameter()) { error("optional parameters may not be combined with variable-length parameters", p->pstate()); } - has_optional_parameters_ = true; + has_optional_parameters(true); } else if (p->is_rest_parameter()) { - if (has_rest_parameter_) { + if (has_rest_parameter()) { error("functions and mixins cannot have more than one variable-length parameter", p->pstate()); } - has_rest_parameter_ = true; + has_rest_parameter(true); } else { - if (has_rest_parameter_) { + if (has_rest_parameter()) { error("required parameters must precede variable-length parameters", p->pstate()); } - if (has_optional_parameters_) { + if (has_optional_parameters()) { error("required parameters must precede optional parameters", p->pstate()); } } @@ -2326,13 +2325,15 @@ namespace Sass { : AST_Node(pstate), contents_(c), connect_parent_(true), - media_block_(NULL) + media_block_(NULL), + hash_(0) { } Selector_Schema(const Selector_Schema* ptr) : AST_Node(ptr), contents_(ptr->contents_), connect_parent_(ptr->connect_parent_), - media_block_(ptr->media_block_) + media_block_(ptr->media_block_), + hash_(ptr->hash_) { } virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; @@ -2428,7 +2429,7 @@ namespace Sass { } virtual ~Simple_Selector() = 0; - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); virtual bool has_parent_ref() const { return false; }; virtual bool has_real_parent_ref() const { return false; }; virtual bool is_pseudo_element() const { return false; } @@ -2515,8 +2516,12 @@ namespace Sass { if (name() == "*") return 0; else return Constants::Specificity_Element; } - virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr, Context&); - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Element_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Element_Selector& rhs) const; ATTACH_AST_OPERATIONS(Element_Selector) ATTACH_OPERATIONS() }; @@ -2536,7 +2541,7 @@ namespace Sass { { return Constants::Specificity_Class; } - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); ATTACH_AST_OPERATIONS(Class_Selector) ATTACH_OPERATIONS() }; @@ -2556,7 +2561,7 @@ namespace Sass { { return Constants::Specificity_ID; } - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); ATTACH_AST_OPERATIONS(Id_Selector) ATTACH_OPERATIONS() }; @@ -2568,14 +2573,16 @@ namespace Sass { ADD_CONSTREF(std::string, matcher) // this cannot be changed to obj atm!!!!!!????!!!!!!! ADD_PROPERTY(String_Obj, value) // might be interpolated + ADD_PROPERTY(char, modifier); public: - Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v) - : Simple_Selector(pstate, n), matcher_(m), value_(v) + Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0) + : Simple_Selector(pstate, n), matcher_(m), value_(v), modifier_(o) { simple_type(ATTR_SEL); } Attribute_Selector(const Attribute_Selector* ptr) : Simple_Selector(ptr), matcher_(ptr->matcher_), - value_(ptr->value_) + value_(ptr->value_), + modifier_(ptr->modifier_) { simple_type(ATTR_SEL); } virtual size_t hash() { @@ -2655,7 +2662,7 @@ namespace Sass { virtual bool operator==(const Pseudo_Selector& rhs) const; virtual bool operator<(const Simple_Selector& rhs) const; virtual bool operator<(const Pseudo_Selector& rhs) const; - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); ATTACH_AST_OPERATIONS(Pseudo_Selector) ATTACH_OPERATIONS() }; @@ -2679,6 +2686,7 @@ namespace Sass { virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; virtual unsigned long specificity() const; + virtual bool find ( bool (*f)(AST_Node_Obj) ); virtual bool operator==(const Simple_Selector& rhs) const; virtual bool operator==(const Wrapped_Selector& rhs) const; virtual bool operator<(const Simple_Selector& rhs) const; @@ -2731,7 +2739,7 @@ namespace Sass { } Complex_Selector_Obj to_complex(); - Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs, Context& ctx); + Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs); // virtual Placeholder_Selector_Ptr find_placeholder(); virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; @@ -2776,6 +2784,7 @@ namespace Sass { Cast((*this)[0]); } + virtual bool find ( bool (*f)(AST_Node_Obj) ); virtual bool operator<(const Selector& rhs) const; virtual bool operator==(const Selector& rhs) const; virtual bool operator<(const Compound_Selector& rhs) const; @@ -2784,9 +2793,9 @@ namespace Sass { ComplexSelectorSet& sources() { return sources_; } void clearSources() { sources_.clear(); } - void mergeSources(ComplexSelectorSet& sources, Context& ctx); + void mergeSources(ComplexSelectorSet& sources); - Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs, Context& ctx); + Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs); virtual void cloneChildren(); ATTACH_AST_OPERATIONS(Compound_Selector) ATTACH_OPERATIONS() @@ -2850,7 +2859,7 @@ namespace Sass { combinator() == Combinator::ANCESTOR_OF; } - Selector_List_Ptr tails(Context& ctx, Selector_List_Ptr tails); + Selector_List_Ptr tails(Selector_List_Ptr tails); // front returns the first real tail // skips over parent and empty ones @@ -2862,13 +2871,13 @@ namespace Sass { Complex_Selector_Obj innermost() { return last(); }; size_t length() const; - Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent = true); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); - Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs, Context& ctx); + Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs); Combinator clear_innermost(); - void append(Context&, Complex_Selector_Obj); + void append(Complex_Selector_Obj); void set_innermost(Complex_Selector_Obj, Combinator); virtual size_t hash() { @@ -2897,6 +2906,7 @@ namespace Sass { if (tail_ && tail_->has_placeholder()) return true; return false; } + virtual bool find ( bool (*f)(AST_Node_Obj) ); virtual bool operator<(const Selector& rhs) const; virtual bool operator==(const Selector& rhs) const; virtual bool operator<(const Complex_Selector& rhs) const; @@ -2925,14 +2935,14 @@ namespace Sass { return srcs; } - void addSources(ComplexSelectorSet& sources, Context& ctx) { + void addSources(ComplexSelectorSet& sources) { // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} Complex_Selector_Ptr pIter = this; while (pIter) { Compound_Selector_Ptr pHead = pIter->head(); if (pHead) { - pHead->mergeSources(sources, ctx); + pHead->mergeSources(sources); } pIter = pIter->tail(); @@ -2983,12 +2993,12 @@ namespace Sass { virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; void remove_parent_selectors(); - Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector& pstack, bool implicit_parent = true); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); - Selector_List_Ptr unify_with(Selector_List_Ptr, Context&); - void populate_extends(Selector_List_Obj, Context&, Subset_Map&); + Selector_List_Ptr unify_with(Selector_List_Ptr); + void populate_extends(Selector_List_Obj, Subset_Map&); Selector_List_Obj eval(Eval& eval); virtual size_t hash() { @@ -3001,7 +3011,7 @@ namespace Sass { virtual unsigned long specificity() const { unsigned long sum = 0; - unsigned long specificity = 0; + unsigned long specificity; for (size_t i = 0, L = length(); i < L; ++i) { specificity = (*this)[i]->specificity(); @@ -3021,6 +3031,7 @@ namespace Sass { } return false; } + virtual bool find ( bool (*f)(AST_Node_Obj) ); virtual bool operator<(const Selector& rhs) const; virtual bool operator==(const Selector& rhs) const; virtual bool operator<(const Selector_List& rhs) const; diff --git a/src/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp old mode 100755 new mode 100644 index 0ff30d1e3..2d399471c --- a/src/libsass/src/ast_def_macros.hpp +++ b/src/libsass/src/ast_def_macros.hpp @@ -19,12 +19,21 @@ class LocalOption { this->orig = var; *(this->var) = orig; } + void reset() + { + *(this->var) = this->orig; + } ~LocalOption() { *(this->var) = this->orig; } }; #define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) +#define LOCAL_COUNT(name,opt) LocalOption cnt_##name(name, opt) + +#define NESTING_GUARD(name) \ + LocalOption cnt_##name(name, name + 1); \ + if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate); \ #define ATTACH_OPERATIONS()\ virtual void perform(Operation* op) { (*op)(this); }\ diff --git a/src/libsass/src/ast_fwd_decl.cpp b/src/libsass/src/ast_fwd_decl.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp old mode 100755 new mode 100644 index a92cac2b3..147b8b559 --- a/src/libsass/src/ast_fwd_decl.hpp +++ b/src/libsass/src/ast_fwd_decl.hpp @@ -132,6 +132,9 @@ namespace Sass { class Map; typedef Map* Map_Ptr; typedef Map const* Map_Ptr_Const; + class Function; + typedef Function* Function_Ptr; + typedef Function const* Function_Ptr_Const; class Mixin_Call; typedef Mixin_Call* Mixin_Call_Ptr; @@ -158,9 +161,6 @@ namespace Sass { class Variable; typedef Variable* Variable_Ptr; typedef Variable const* Variable_Ptr_Const; - class Textual; - typedef Textual* Textual_Ptr; - typedef Textual const* Textual_Ptr_Const; class Number; typedef Number* Number_Ptr; typedef Number const* Number_Ptr_Const; @@ -314,6 +314,7 @@ namespace Sass { IMPL_MEM_OBJ(Expression); IMPL_MEM_OBJ(List); IMPL_MEM_OBJ(Map); + IMPL_MEM_OBJ(Function); IMPL_MEM_OBJ(Binary_Expression); IMPL_MEM_OBJ(Unary_Expression); IMPL_MEM_OBJ(Function_Call); @@ -321,7 +322,6 @@ namespace Sass { IMPL_MEM_OBJ(Custom_Warning); IMPL_MEM_OBJ(Custom_Error); IMPL_MEM_OBJ(Variable); - IMPL_MEM_OBJ(Textual); IMPL_MEM_OBJ(Number); IMPL_MEM_OBJ(Color); IMPL_MEM_OBJ(Boolean); @@ -376,6 +376,11 @@ namespace Sass { struct CompareNodes { template bool operator() (const T& lhs, const T& rhs) const { + // code around sass logic issue. 1px == 1 is true + // but both items are still different keys in maps + if (dynamic_cast(lhs.ptr())) + if (dynamic_cast(rhs.ptr())) + return lhs->hash() == rhs->hash(); return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs; } }; @@ -410,8 +415,12 @@ namespace Sass { typedef std::deque ComplexSelectorDeque; typedef std::set SimpleSelectorSet; typedef std::set ComplexSelectorSet; + typedef std::set CompoundSelectorSet; typedef std::unordered_set SimpleSelectorDict; + // only to switch implementations for testing + #define environment_map std::map + // ########################################################################### // explicit type conversion functions // ########################################################################### diff --git a/src/libsass/src/b64/cencode.h b/src/libsass/src/b64/cencode.h old mode 100755 new mode 100644 diff --git a/src/libsass/src/b64/encode.h b/src/libsass/src/b64/encode.h old mode 100755 new mode 100644 index fac91e767..92df8ec70 --- a/src/libsass/src/b64/encode.h +++ b/src/libsass/src/b64/encode.h @@ -24,7 +24,9 @@ namespace base64 encoder(int buffersize_in = BUFFERSIZE) : _buffersize(buffersize_in) - {} + { + base64_init_encodestate(&_state); + } int encode(char value_in) { diff --git a/src/libsass/src/backtrace.hpp b/src/libsass/src/backtrace.hpp old mode 100755 new mode 100644 index 213da2f86..57ce1786f --- a/src/libsass/src/backtrace.hpp +++ b/src/libsass/src/backtrace.hpp @@ -60,13 +60,13 @@ namespace Sass { size_t depth() { - size_t d = 0; + size_t d = std::string::npos; Backtrace* p = parent; while (p) { ++d; p = p->parent; } - return d-1; + return d; } }; diff --git a/src/libsass/src/base64vlq.cpp b/src/libsass/src/base64vlq.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/base64vlq.hpp b/src/libsass/src/base64vlq.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp old mode 100755 new mode 100644 index ea11041c2..e579deac6 --- a/src/libsass/src/bind.cpp +++ b/src/libsass/src/bind.cpp @@ -14,6 +14,8 @@ namespace Sass { std::string callee(type + " " + name); std::map param_map; + List_Obj varargs = SASS_MEMORY_NEW(List, as->pstate()); + varargs->is_arglist(true); // enable keyword size handling for (size_t i = 0, L = as->length(); i < L; ++i) { if (auto str = Cast((*as)[i]->value())) { @@ -95,13 +97,17 @@ namespace Sass { env->local_frame()[p->name()] = arglist; Map_Obj argmap = Cast(a->value()); for (auto key : argmap->keys()) { - std::string name = unquote(Cast(key)->value()); - arglist->append(SASS_MEMORY_NEW(Argument, - key->pstate(), - argmap->at(key), - "$" + name, - false, - false)); + if (String_Constant_Obj str = Cast(key)) { + std::string param = unquote(str->value()); + arglist->append(SASS_MEMORY_NEW(Argument, + key->pstate(), + argmap->at(key), + "$" + param, + false, + false)); + } else { + throw Exception::InvalidVarKwdType(key->pstate(), key->inspect(), a); + } } } else { @@ -131,8 +137,8 @@ namespace Sass { if (List_Obj rest = Cast(a->value())) { arglist->separator(rest->separator()); - for (size_t i = 0, L = rest->size(); i < L; ++i) { - Expression_Obj obj = rest->at(i); + for (size_t i = 0, L = rest->length(); i < L; ++i) { + Expression_Obj obj = rest->value_at_index(i); arglist->append(SASS_MEMORY_NEW(Argument, obj->pstate(), obj, @@ -167,8 +173,15 @@ namespace Sass { else if (a->is_rest_argument()) { // normal param and rest arg List_Obj arglist = Cast(a->value()); + if (!arglist) { + if (Expression_Obj arg = Cast(a->value())) { + arglist = SASS_MEMORY_NEW(List, a->pstate(), 1); + arglist->append(arg); + } + } + // empty rest arg - treat all args as default values - if (!arglist->length()) { + if (!arglist || !arglist->length()) { break; } else { if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { @@ -205,14 +218,16 @@ namespace Sass { Map_Obj argmap = Cast(a->value()); for (auto key : argmap->keys()) { - std::string name = "$" + unquote(Cast(key)->value()); + String_Constant_Ptr val = Cast(key); + if (val == NULL) throw Exception::InvalidVarKwdType(key->pstate(), key->inspect(), a); + std::string param = "$" + unquote(val->value()); - if (!param_map.count(name)) { + if (!param_map.count(param)) { std::stringstream msg; - msg << callee << " has no parameter named " << name; + msg << callee << " has no parameter named " << param; error(msg.str(), a->pstate()); } - env->local_frame()[name] = argmap->at(key); + env->local_frame()[param] = argmap->at(key); } ++ia; continue; @@ -234,15 +249,21 @@ namespace Sass { else { // named arg -- bind it to the appropriately named param if (!param_map.count(a->name())) { - std::stringstream msg; - msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate()); + if (ps->has_rest_parameter()) { + varargs->append(a); + } else { + std::stringstream msg; + msg << callee << " has no parameter named " << a->name(); + error(msg.str(), a->pstate()); + } } - if (param_map[a->name()]->is_rest_parameter()) { - std::stringstream msg; - msg << "argument " << a->name() << " of " << callee - << "cannot be used as named argument"; - error(msg.str(), a->pstate()); + if (param_map[a->name()]) { + if (param_map[a->name()]->is_rest_parameter()) { + std::stringstream msg; + msg << "argument " << a->name() << " of " << callee + << "cannot be used as named argument"; + error(msg.str(), a->pstate()); + } } if (env->has_local(a->name())) { std::stringstream msg; @@ -265,11 +286,7 @@ namespace Sass { // cerr << "********" << endl; if (!env->has_local(leftover->name())) { if (leftover->is_rest_parameter()) { - env->local_frame()[leftover->name()] = SASS_MEMORY_NEW(List, - leftover->pstate(), - 0, - SASS_COMMA, - true); + env->local_frame()[leftover->name()] = varargs; } else if (leftover->default_value()) { Expression_Ptr dv = leftover->default_value()->perform(eval); diff --git a/src/libsass/src/bind.hpp b/src/libsass/src/bind.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/c99func.c b/src/libsass/src/c99func.c old mode 100755 new mode 100644 diff --git a/src/libsass/src/cencode.c b/src/libsass/src/cencode.c old mode 100755 new mode 100644 index 18f1806c9..00ac24e28 --- a/src/libsass/src/cencode.c +++ b/src/libsass/src/cencode.c @@ -46,6 +46,9 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, result = (fragment & 0x0fc) >> 2; *codechar++ = base64_encode_value(result); result = (fragment & 0x003) << 4; + #ifndef _MSC_VER + __attribute__ ((fallthrough)); + #endif case step_B: if (plainchar == plaintextend) { @@ -57,6 +60,9 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, result |= (fragment & 0x0f0) >> 4; *codechar++ = base64_encode_value(result); result = (fragment & 0x00f) << 2; + #ifndef _MSC_VER + __attribute__ ((fallthrough)); + #endif case step_C: if (plainchar == plaintextend) { diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp old mode 100755 new mode 100644 index 5d0a8366e..952bb3380 --- a/src/libsass/src/check_nesting.cpp +++ b/src/libsass/src/check_nesting.cpp @@ -40,7 +40,13 @@ namespace Sass { } At_Root_Block_Ptr ar = Cast(parent); - Statement_Ptr ret = this->visit_children(ar->block()); + Block_Ptr ret = ar->block(); + + if (ret != NULL) { + for (auto n : ret->elements()) { + n->perform(this); + } + } this->parent = old_parent; this->parents = old_parents; @@ -97,6 +103,19 @@ namespace Sass { return n; } + Statement_Ptr CheckNesting::operator()(If_Ptr i) + { + this->visit_children(i); + + if (Block_Ptr b = Cast(i->alternative())) { + for (auto n : i->alternative()->elements()) { + n->perform(this); + } + } + + return i; + } + Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) { Block_Ptr b1 = Cast(s); diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp old mode 100755 new mode 100644 index ec9ee2ae0..5ba303ee1 --- a/src/libsass/src/check_nesting.hpp +++ b/src/libsass/src/check_nesting.hpp @@ -22,6 +22,7 @@ namespace Sass { Statement_Ptr operator()(Block_Ptr); Statement_Ptr operator()(Definition_Ptr); + Statement_Ptr operator()(If_Ptr); template Statement_Ptr fallback(U x) { diff --git a/src/libsass/src/color_maps.cpp b/src/libsass/src/color_maps.cpp old mode 100755 new mode 100644 index f21e9e029..129e47c5a --- a/src/libsass/src/color_maps.cpp +++ b/src/libsass/src/color_maps.cpp @@ -607,16 +607,20 @@ namespace Sass { Color_Ptr_Const name_to_color(const char* key) { - auto p = names_to_colors.find(key); - if (p != names_to_colors.end()) { - return p->second; - } - return 0; + return name_to_color(std::string(key)); } Color_Ptr_Const name_to_color(const std::string& key) { - return name_to_color(key.c_str()); + // case insensitive lookup. See #2462 + std::string lower{key}; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + auto p = names_to_colors.find(lower.c_str()); + if (p != names_to_colors.end()) { + return p->second; + } + return 0; } const char* color_to_name(const int key) diff --git a/src/libsass/src/color_maps.hpp b/src/libsass/src/color_maps.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/constants.cpp b/src/libsass/src/constants.cpp old mode 100755 new mode 100644 index 4246b3e52..0ba28e20c --- a/src/libsass/src/constants.cpp +++ b/src/libsass/src/constants.cpp @@ -79,11 +79,12 @@ namespace Sass { extern const char supports_kwd[] = "@supports"; extern const char keyframes_kwd[] = "keyframes"; extern const char only_kwd[] = "only"; - extern const char rgb_kwd[] = "rgb("; + extern const char rgb_fn_kwd[] = "rgb("; + extern const char url_fn_kwd[] = "url("; extern const char url_kwd[] = "url"; - // extern const char url_prefix_kwd[] = "url-prefix("; + // extern const char url_prefix_fn_kwd[] = "url-prefix("; extern const char important_kwd[] = "important"; - extern const char pseudo_not_kwd[] = ":not("; + extern const char pseudo_not_fn_kwd[] = ":not("; extern const char even_kwd[] = "even"; extern const char odd_kwd[] = "odd"; extern const char progid_kwd[] = "progid"; diff --git a/src/libsass/src/constants.hpp b/src/libsass/src/constants.hpp old mode 100755 new mode 100644 index 8470d5d6a..4fe93571e --- a/src/libsass/src/constants.hpp +++ b/src/libsass/src/constants.hpp @@ -79,11 +79,12 @@ namespace Sass { extern const char supports_kwd[]; extern const char keyframes_kwd[]; extern const char only_kwd[]; - extern const char rgb_kwd[]; + extern const char rgb_fn_kwd[]; + extern const char url_fn_kwd[]; extern const char url_kwd[]; - // extern const char url_prefix_kwd[]; + // extern const char url_prefix_fn_kwd[]; extern const char important_kwd[]; - extern const char pseudo_not_kwd[]; + extern const char pseudo_not_fn_kwd[]; extern const char even_kwd[]; extern const char odd_kwd[]; extern const char progid_kwd[]; diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp old mode 100755 new mode 100644 index 42b41c364..2c51db223 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -67,11 +67,14 @@ namespace Sass { plugins(), emitter(c_options), + ast_gc(), strings(), resources(), sheets(), subset_map(), import_stack(), + callee_stack(), + c_compiler(NULL), c_headers (std::vector()), c_importers (std::vector()), @@ -130,7 +133,7 @@ namespace Sass { Context::~Context() { - // resources were allocated by strdup or malloc + // resources were allocated by malloc for (size_t i = 0; i < resources.size(); ++i) { free(resources[i].contents); free(resources[i].srcmap); @@ -416,12 +419,12 @@ namespace Sass { // need one correct import bool has_import = false; // process all custom importers (or custom headers) - for (Sass_Importer_Entry& importer : importers) { + for (Sass_Importer_Entry& importer_ent : importers) { // int priority = sass_importer_get_priority(importer); - Sass_Importer_Fn fn = sass_importer_get_function(importer); + Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); // skip importer if it returns NULL if (Sass_Import_List includes = - fn(load_path.c_str(), importer, c_compiler) + fn(load_path.c_str(), importer_ent, c_compiler) ) { // get c pointer copy to iterate over Sass_Import_List it_includes = includes; @@ -436,15 +439,15 @@ namespace Sass { // create the importer struct Importer importer(uniq_path, ctx_path); // query data from the current include - Sass_Import_Entry include = *it_includes; - char* source = sass_import_take_source(include); - char* srcmap = sass_import_take_srcmap(include); - size_t line = sass_import_get_error_line(include); - size_t column = sass_import_get_error_column(include); - const char *abs_path = sass_import_get_abs_path(include); + Sass_Import_Entry include_ent = *it_includes; + char* source = sass_import_take_source(include_ent); + char* srcmap = sass_import_take_srcmap(include_ent); + size_t line = sass_import_get_error_line(include_ent); + size_t column = sass_import_get_error_column(include_ent); + const char *abs_path = sass_import_get_abs_path(include_ent); // handle error message passed back from custom importer // it may (or may not) override the line and column info - if (const char* err_message = sass_import_get_error_message(include)) { + if (const char* err_message = sass_import_get_error_message(include_ent)) { if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, &pstate); if (line == std::string::npos && column == std::string::npos) error(err_message, pstate); else error(err_message, ParserState(ctx_path, source, Position(line, column))); @@ -661,7 +664,8 @@ namespace Sass { // should we extend something? if (!subset_map.empty()) { // create crtp visitor object - Extend extend(*this, subset_map); + Extend extend(subset_map); + extend.setEval(expand.eval); // extend tree nodes extend(root); } @@ -696,10 +700,8 @@ namespace Sass { char* Context::render_srcmap() { if (source_map_file == "") return 0; - char* result = 0; std::string map = emitter.render_srcmap(*this); - result = sass_copy_c_string(map.c_str()); - return result; + return sass_copy_c_string(map.c_str()); } @@ -831,6 +833,8 @@ namespace Sass { register_function(ctx, mixin_exists_sig, mixin_exists, env); register_function(ctx, feature_exists_sig, feature_exists, env); register_function(ctx, call_sig, call, env); + register_function(ctx, content_exists_sig, content_exists, env); + register_function(ctx, get_function_sig, get_function, env); // Boolean Functions register_function(ctx, not_sig, sass_not, env); register_function(ctx, if_sig, sass_if, env); diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp old mode 100755 new mode 100644 index 44a32ed06..07a21a5d2 --- a/src/libsass/src/context.hpp +++ b/src/libsass/src/context.hpp @@ -43,6 +43,9 @@ namespace Sass { Plugins plugins; Output emitter; + // generic ast node garbage container + // used to avoid possible circular refs + std::vector ast_gc; // resources add under our control // these are guaranteed to be freed std::vector strings; diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp old mode 100755 new mode 100644 index ae112f391..e1f083c5c --- a/src/libsass/src/cssize.cpp +++ b/src/libsass/src/cssize.cpp @@ -23,12 +23,12 @@ namespace Sass { Block_Ptr Cssize::operator()(Block_Ptr b) { - Block_Ptr bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); + Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); // bb->tabs(b->tabs()); block_stack.push_back(bb); append_block(b, bb); block_stack.pop_back(); - return bb; + return bb.detach(); } Statement_Ptr Cssize::operator()(Trace_Ptr t) @@ -54,7 +54,8 @@ namespace Sass { d->pstate(), property, d->value(), - d->is_important()); + d->is_important(), + d->is_custom_property()); dd->is_indented(d->is_indented()); dd->tabs(d->tabs()); @@ -174,9 +175,9 @@ namespace Sass { if (props->length()) { - Block_Obj bb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - bb->concat(props); - rr->block(bb); + Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + pb->concat(props); + rr->block(pb); for (size_t i = 0, L = rules->length(); i < L; i++) { @@ -259,7 +260,7 @@ namespace Sass { tmp |= m->exclude_node(s); } - if (!tmp) + if (!tmp && m->block()) { Block_Ptr bb = operator()(m->block()); for (size_t i = 0, L = bb->length(); i < L; ++i) { @@ -302,14 +303,17 @@ namespace Sass { Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) { + if (!m || !m->block()) return NULL; Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); + if (new_rule) { + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + wrapper_block->append(new_rule); + } + At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, m->pstate(), wrapper_block, @@ -442,7 +446,7 @@ namespace Sass { for (size_t j = 0, K = slice->length(); j < K; ++j) { - Statement_Ptr ss = NULL; + Statement_Ptr ss; Statement_Obj stm = slice->at(j); // this has to go now here (too bad) Bubble_Obj node = Cast(stm); @@ -476,8 +480,6 @@ namespace Sass { ss->tabs(ss->tabs() + node->tabs()); ss->group_end(node->group_end()); - if (!ss) continue; - Block_Obj bb = SASS_MEMORY_NEW(Block, children->pstate(), children->length(), @@ -584,10 +586,11 @@ namespace Sass { } Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, - -mq1->pstate(), 0, -mq1->length() + mq2->length(), mod == "not", mod == "only" -); + mq1->pstate(), + 0, + mq1->length() + mq2->length(), + mod == "not", + mod == "only"); if (!type.empty()) { mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/debug.hpp b/src/libsass/src/debug.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp old mode 100755 new mode 100644 index ea21ddbbc..e9fc8a279 --- a/src/libsass/src/debugger.hpp +++ b/src/libsass/src/debugger.hpp @@ -103,7 +103,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (selector->has_line_break() ? " [line-break]": " -"); std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - debug_ast(selector->schema(), "#{} "); + debug_ast(selector->schema(), ind + "#{} "); for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } @@ -415,6 +415,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) Declaration_Ptr block = Cast(node); std::cerr << ind << "Declaration " << block; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->property(), ind + " prop: ", env); debug_ast(block->value(), ind + " value: ", env); @@ -488,18 +489,6 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); std::cerr << " [indent: " << block->tabs() << "]" << std::endl; for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Textual_Ptr expression = Cast(node); - std::cerr << ind << "Textual " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->type() == Textual::NUMBER) std::cerr << " [NUMBER]"; - else if (expression->type() == Textual::PERCENTAGE) std::cerr << " [PERCENTAGE]"; - else if (expression->type() == Textual::DIMENSION) std::cerr << " [DIMENSION]"; - else if (expression->type() == Textual::HEX) std::cerr << " [HEX]"; - std::cerr << " [" << expression->value() << "]"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - if (expression->is_delayed()) std::cerr << " [delayed]"; - std::cerr << std::endl; } else if (Cast(node)) { Variable_Ptr expression = Cast(node); std::cerr << ind << "Variable " << expression; @@ -524,8 +513,17 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " [" << expression->name() << "]"; if (expression->is_delayed()) std::cerr << " [delayed]"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->is_css()) std::cerr << " [css]"; std::cerr << std::endl; debug_ast(expression->arguments(), ind + " args: ", env); + debug_ast(expression->func(), ind + " func: ", env); + } else if (Cast(node)) { + Function_Ptr expression = Cast(node); + std::cerr << ind << "Function " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->definition(), ind + " definition: ", env); } else if (Cast(node)) { Arguments_Ptr expression = Cast(node); std::cerr << ind << "Arguments " << expression; @@ -698,6 +696,8 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; + case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; + case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; } std::cerr << std::endl; } else if (Cast(node)) { diff --git a/src/libsass/src/emitter.cpp b/src/libsass/src/emitter.cpp old mode 100755 new mode 100644 index f315b840b..161e689a9 --- a/src/libsass/src/emitter.cpp +++ b/src/libsass/src/emitter.cpp @@ -14,7 +14,9 @@ namespace Sass { scheduled_space(0), scheduled_linefeed(0), scheduled_delimiter(false), + scheduled_crutch(0), scheduled_mapping(0), + in_custom_property(false), in_comment(false), in_wrapped(false), in_media_block(false), @@ -101,10 +103,30 @@ namespace Sass { // prepend some text or token to the buffer void Emitter::prepend_string(const std::string& text) { - wbuf.smap.prepend(Offset(text)); + // do not adjust mappings for utf8 bom + // seems they are not counted in any UA + if (text.compare("\xEF\xBB\xBF") != 0) { + wbuf.smap.prepend(Offset(text)); + } wbuf.buffer = text + wbuf.buffer; } + char Emitter::last_char() + { + return wbuf.buffer.back(); + } + + // append a single char to the buffer + void Emitter::append_char(const char chr) + { + // write space/lf + flush_schedules(); + // add to buffer + wbuf.buffer += chr; + // account for data in source-maps + wbuf.smap.append(Offset(chr)); + } + // append some text or token to the buffer void Emitter::append_string(const std::string& text) { @@ -145,9 +167,9 @@ namespace Sass { add_open_mapping(node); // hotfix for browser issues // this is pretty ugly indeed - if (scheduled_mapping) { - add_open_mapping(scheduled_mapping); - scheduled_mapping = 0; + if (scheduled_crutch) { + add_open_mapping(scheduled_crutch); + scheduled_crutch = 0; } append_string(text); add_close_mapping(node); @@ -193,7 +215,7 @@ namespace Sass { { scheduled_space = 0; append_string(":"); - append_optional_space(); + if (!in_custom_property) append_optional_space(); } void Emitter::append_mandatory_space() @@ -206,7 +228,9 @@ namespace Sass { if ((output_style() != COMPRESSED) && buffer().size()) { unsigned char lst = buffer().at(buffer().length() - 1); if (!isspace(lst) || scheduled_delimiter) { - append_mandatory_space(); + if (last_char() != '(') { + append_mandatory_space(); + } } } } diff --git a/src/libsass/src/emitter.hpp b/src/libsass/src/emitter.hpp old mode 100755 new mode 100644 index 94b2a08bd..3bf8f60da --- a/src/libsass/src/emitter.hpp +++ b/src/libsass/src/emitter.hpp @@ -37,9 +37,12 @@ namespace Sass { size_t scheduled_space; size_t scheduled_linefeed; bool scheduled_delimiter; + AST_Node_Ptr scheduled_crutch; AST_Node_Ptr scheduled_mapping; public: + // output strings different in custom css properties + bool in_custom_property; // output strings different in comments bool in_comment; // selector list does not get linefeeds @@ -66,11 +69,15 @@ namespace Sass { void prepend_output(const OutputBuffer& out); // append some text or token to the buffer void append_string(const std::string& text); + // append a single character to buffer + void append_char(const char chr); // append some white-space only text void append_wspace(const std::string& text); // append some text or token to the buffer // this adds source-mappings for node start and end void append_token(const std::string& text, const AST_Node_Ptr node); + // query last appended character + char last_char(); public: // syntax sugar void append_indentation(); diff --git a/src/libsass/src/environment.cpp b/src/libsass/src/environment.cpp old mode 100755 new mode 100644 index 7a7e9b1d1..e382e7e05 --- a/src/libsass/src/environment.cpp +++ b/src/libsass/src/environment.cpp @@ -6,17 +6,17 @@ namespace Sass { template Environment::Environment(bool is_shadow) - : local_frame_(std::map()), + : local_frame_(environment_map()), parent_(0), is_shadow_(false) { } template Environment::Environment(Environment* env, bool is_shadow) - : local_frame_(std::map()), + : local_frame_(environment_map()), parent_(env), is_shadow_(is_shadow) { } template Environment::Environment(Environment& env, bool is_shadow) - : local_frame_(std::map()), + : local_frame_(environment_map()), parent_(&env), is_shadow_(is_shadow) { } @@ -45,7 +45,7 @@ namespace Sass { } template - std::map& Environment::local_frame() { + environment_map& Environment::local_frame() { return local_frame_; } @@ -53,12 +53,25 @@ namespace Sass { bool Environment::has_local(const std::string& key) const { return local_frame_.find(key) != local_frame_.end(); } + template EnvResult + Environment::find_local(const std::string& key) + { + auto end = local_frame_.end(); + auto it = local_frame_.find(key); + return EnvResult(it, it != end); + } + template T& Environment::get_local(const std::string& key) { return local_frame_[key]; } template - void Environment::set_local(const std::string& key, T val) + void Environment::set_local(const std::string& key, const T& val) + { + local_frame_[key] = val; + } + template + void Environment::set_local(const std::string& key, T&& val) { local_frame_[key] = val; } @@ -86,7 +99,12 @@ namespace Sass { { return (*global_env())[key]; } template - void Environment::set_global(const std::string& key, T val) + void Environment::set_global(const std::string& key, const T& val) + { + global_env()->local_frame_[key] = val; + } + template + void Environment::set_global(const std::string& key, T&& val) { global_env()->local_frame_[key] = val; } @@ -126,12 +144,31 @@ namespace Sass { // either update already existing lexical value // or if flag is set, we create one if no lexical found template - void Environment::set_lexical(const std::string& key, T val) + void Environment::set_lexical(const std::string& key, const T& val) { - auto cur = this; bool shadow = false; - while (cur->is_lexical() || shadow) { - if (cur->has_local(key)) { - cur->set_local(key, val); + Environment* cur = this; + bool shadow = false; + while ((cur && cur->is_lexical()) || shadow) { + EnvResult rv(cur->find_local(key)); + if (rv.found) { + rv.it->second = val; + return; + } + shadow = cur->is_shadow(); + cur = cur->parent_; + } + set_local(key, val); + } + // this one moves the value + template + void Environment::set_lexical(const std::string& key, T&& val) + { + Environment* cur = this; + bool shadow = false; + while ((cur && cur->is_lexical()) || shadow) { + EnvResult rv(cur->find_local(key)); + if (rv.found) { + rv.it->second = val; return; } shadow = cur->is_shadow(); @@ -155,6 +192,20 @@ namespace Sass { return false; } + // look on the full stack for key + // include all scopes available + template EnvResult + Environment::find(const std::string& key) + { + auto cur = this; + while (true) { + EnvResult rv(cur->find_local(key)); + if (rv.found) return rv; + cur = cur->parent_; + if (!cur) return rv; + } + }; + // use array access for getter and setter functions template T& Environment::operator[](const std::string& key) @@ -168,7 +219,7 @@ namespace Sass { } return get_local(key); } - +/* #ifdef DEBUG template size_t Environment::print(std::string prefix) @@ -176,7 +227,7 @@ namespace Sass { size_t indent = 0; if (parent_) indent = parent_->print(prefix) + 1; std::cerr << prefix << std::string(indent, ' ') << "== " << this << std::endl; - for (typename std::map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { + for (typename environment_map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second; if (Value_Ptr val = Cast(i->second)) @@ -187,7 +238,7 @@ namespace Sass { return indent ; } #endif - +*/ // compile implementation for AST_Node template class Environment; diff --git a/src/libsass/src/environment.hpp b/src/libsass/src/environment.hpp old mode 100755 new mode 100644 index dd985b15c..a6939be23 --- a/src/libsass/src/environment.hpp +++ b/src/libsass/src/environment.hpp @@ -2,17 +2,26 @@ #define SASS_ENVIRONMENT_H #include -#include - #include "ast_fwd_decl.hpp" #include "ast_def_macros.hpp" namespace Sass { + typedef environment_map::iterator EnvIter; + + class EnvResult { + public: + EnvIter it; + bool found; + public: + EnvResult(EnvIter it, bool found) + : it(it), found(found) {} + }; + template class Environment { // TODO: test with map - std::map local_frame_; + environment_map local_frame_; ADD_PROPERTY(Environment*, parent) ADD_PROPERTY(bool, is_shadow) @@ -37,14 +46,17 @@ namespace Sass { // scope operates on the current frame - std::map& local_frame(); + environment_map& local_frame(); bool has_local(const std::string& key) const; + EnvResult find_local(const std::string& key); + T& get_local(const std::string& key); // set variable on the current frame - void set_local(const std::string& key, T val); + void set_local(const std::string& key, const T& val); + void set_local(const std::string& key, T&& val); void del_local(const std::string& key); @@ -60,7 +72,8 @@ namespace Sass { T& get_global(const std::string& key); // set a variable on the global frame - void set_global(const std::string& key, T val); + void set_global(const std::string& key, const T& val); + void set_global(const std::string& key, T&& val); void del_global(const std::string& key); @@ -72,12 +85,17 @@ namespace Sass { // see if we have a lexical we could update // either update already existing lexical value // or we create a new one on the current frame - void set_lexical(const std::string& key, T val); + void set_lexical(const std::string& key, T&& val); + void set_lexical(const std::string& key, const T& val); // look on the full stack for key // include all scopes available bool has(const std::string& key) const; + // look on the full stack for key + // include all scopes available + EnvResult find(const std::string& key); + // use array access for getter and setter functions T& operator[](const std::string& key); diff --git a/src/libsass/src/error_handling.cpp b/src/libsass/src/error_handling.cpp old mode 100755 new mode 100644 index 81e016fb0..67affc81e --- a/src/libsass/src/error_handling.cpp +++ b/src/libsass/src/error_handling.cpp @@ -31,6 +31,13 @@ namespace Sass { msg += "\""; } + InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, std::string name, const Argument_Ptr arg) + : Base(pstate), name(name), arg(arg) + { + msg = "Variable keyword argument map must have string keys.\n"; + msg += name + " is not a string in " + arg->to_string() + "."; + } + InvalidArgumentType::InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value) : Base(pstate), fn(fn), arg(arg), type(type), value(value) { @@ -52,6 +59,10 @@ namespace Sass { : Base(pstate, msg, import_stack) { } + NestingLimitError::NestingLimitError(ParserState pstate, std::string msg, std::vector* import_stack) + : Base(pstate, msg, import_stack) + { } + UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) : lhs(lhs), rhs(rhs), op(op) { @@ -110,8 +121,7 @@ namespace Sass { msg = "stack level too deep"; } - IncompatibleUnits::IncompatibleUnits(const Number& lhs, const Number& rhs) - : lhs(lhs), rhs(rhs) + IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) { msg = "Incompatible units: '"; msg += rhs.unit(); @@ -120,6 +130,15 @@ namespace Sass { msg += "'."; } + IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs) + { + msg = "Incompatible units: '"; + msg += unit_to_string(rhs); + msg += "' and '"; + msg += unit_to_string(lhs); + msg += "'."; + } + AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) : lhs(lhs), rhs(rhs), op(op) { @@ -146,6 +165,17 @@ namespace Sass { std::cerr << "Warning: " << msg<< std::endl; } + void warning(std::string msg, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); + + std::cerr << "WARNING on line " << pstate.line+1 << ", column " << pstate.column+1 << " of " << output_path << ":" << std::endl; + std::cerr << msg << std::endl << std::endl; + } + void warn(std::string msg, ParserState pstate, Backtrace* bt) { Backtrace top(bt, pstate, ""); @@ -165,7 +195,7 @@ namespace Sass { std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; } - void deprecated(std::string msg, std::string msg2, ParserState pstate) + void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate) { std::string cwd(Sass::File::get_cwd()); std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); @@ -173,9 +203,10 @@ namespace Sass { std::string output_path(Sass::File::path_for_console(rel_path, pstate.path, pstate.path)); std::cerr << "DEPRECATION WARNING on line " << pstate.line + 1; + if (with_column) std::cerr << ", column " << pstate.column + pstate.offset.column + 1; if (output_path.length()) std::cerr << " of " << output_path; std::cerr << ":" << std::endl; - std::cerr << msg << " and will be an error in future versions of Sass." << std::endl; + std::cerr << msg << std::endl; if (msg2.length()) std::cerr << msg2 << std::endl; std::cerr << std::endl; } diff --git a/src/libsass/src/error_handling.hpp b/src/libsass/src/error_handling.hpp old mode 100755 new mode 100644 index 174d91cab..e7890f6ed --- a/src/libsass/src/error_handling.hpp +++ b/src/libsass/src/error_handling.hpp @@ -5,6 +5,8 @@ #include #include #include "position.hpp" +#include "ast_fwd_decl.hpp" +#include "sass/functions.h" namespace Sass { @@ -15,6 +17,7 @@ namespace Sass { const std::string def_msg = "Invalid sass detected"; const std::string def_op_msg = "Undefined operation"; const std::string def_op_null_msg = "Invalid null operation"; + const std::string def_nesting_limit = "Code too deeply neested"; class Base : public std::runtime_error { protected: @@ -66,12 +69,27 @@ namespace Sass { virtual ~InvalidArgumentType() throw() {}; }; + class InvalidVarKwdType : public Base { + protected: + std::string name; + const Argument_Ptr arg; + public: + InvalidVarKwdType(ParserState pstate, std::string name, const Argument_Ptr arg = 0); + virtual ~InvalidVarKwdType() throw() {}; + }; + class InvalidSyntax : public Base { public: InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack = 0); virtual ~InvalidSyntax() throw() {}; }; + class NestingLimitError : public Base { + public: + NestingLimitError(ParserState pstate, std::string msg = def_nesting_limit, std::vector* import_stack = 0); + virtual ~NestingLimitError() throw() {}; + }; + /* common virtual base class (has no pstate) */ class OperationError : public std::runtime_error { protected: @@ -136,10 +154,11 @@ namespace Sass { class IncompatibleUnits : public OperationError { protected: - const Number& lhs; - const Number& rhs; + // const Sass::UnitType lhs; + // const Sass::UnitType rhs; public: - IncompatibleUnits(const Number& lhs, const Number& rhs); + IncompatibleUnits(const Units& lhs, const Units& rhs); + IncompatibleUnits(const UnitType lhs, const UnitType rhs); virtual ~IncompatibleUnits() throw() {}; }; @@ -181,9 +200,10 @@ namespace Sass { void warn(std::string msg, ParserState pstate); void warn(std::string msg, ParserState pstate, Backtrace* bt); + void warning(std::string msg, ParserState pstate); void deprecated_function(std::string msg, ParserState pstate); - void deprecated(std::string msg, std::string msg2, ParserState pstate); + void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate); void deprecated_bind(std::string msg, ParserState pstate); // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp old mode 100755 new mode 100644 index 219d47e45..9ea80c05d --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -51,8 +51,12 @@ namespace Sass { : exp(exp), ctx(exp.ctx), force(false), - is_in_comment(false) - { } + is_in_comment(false), + is_in_selector_schema(false) + { + bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); + bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); + } Eval::~Eval() { } Env* Eval::environment() @@ -185,8 +189,6 @@ namespace Sass { // only create iterator once in this environment Env env(environment(), true); exp.env_stack.push_back(&env); - Number_Ptr it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit()); - env.set_local(variable, it); Block_Obj body = f->block(); Expression_Ptr val = 0; if (start < end) { @@ -194,7 +196,7 @@ namespace Sass { for (double i = start; i < end; ++i) { - it->value(i); + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); env.set_local(variable, it); val = body->perform(this); if (val) break; @@ -204,7 +206,7 @@ namespace Sass { for (double i = start; i > end; --i) { - it->value(i); + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); env.set_local(variable, it); val = body->perform(this); if (val) break; @@ -266,11 +268,11 @@ namespace Sass { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Ptr e = list->at(i); + Expression_Ptr item = list->at(i); // unwrap value if the expression is an argument - if (Argument_Ptr arg = Cast(e)) e = arg->value(); + if (Argument_Ptr arg = Cast(item)) item = arg->value(); // check if we got passed a list of args (investigate) - if (List_Ptr scalars = Cast(e)) { + if (List_Ptr scalars = Cast(item)) { if (variables.size() == 1) { Expression_Ptr var = scalars; env.set_local(variables[0], var); @@ -285,7 +287,7 @@ namespace Sass { } } else { if (variables.size() > 0) { - env.set_local(variables.at(0), e); + env.set_local(variables.at(0), item); for (size_t j = 1, K = variables.size(); j < K; ++j) { // XXX: this is never hit via spec tests Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); @@ -521,7 +523,9 @@ namespace Sass { m->length()); for (auto key : m->keys()) { Expression_Ptr ex_key = key->perform(this); - Expression_Ptr ex_val = m->at(key)->perform(this); + Expression_Ptr ex_val = m->at(key); + if (ex_val == NULL) continue; + ex_val = ex_val->perform(this); *mm << std::make_pair(ex_key, ex_val); } @@ -537,9 +541,138 @@ namespace Sass { Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) { - String_Schema_Obj ret_schema; + Expression_Obj lhs = b_in->left(); + Expression_Obj rhs = b_in->right(); + enum Sass_OP op_type = b_in->optype(); + + if (op_type == Sass_OP::AND) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (!*lhs) return lhs.detach(); + return rhs->perform(this); + } + else if (op_type == Sass_OP::OR) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (*lhs) return lhs.detach(); + return rhs->perform(this); + } + + // Evaluate variables as early o + while (Variable_Ptr l_v = Cast(lhs)) { + lhs = operator()(l_v); + } + while (Variable_Ptr r_v = Cast(rhs)) { + rhs = operator()(r_v); + } + Binary_Expression_Obj b = b_in; - enum Sass_OP op_type = b->optype(); + + // Evaluate sub-expressions early on + while (Binary_Expression_Ptr l_b = Cast(lhs)) { + if (!force && l_b->is_delayed()) break; + lhs = operator()(l_b); + } + while (Binary_Expression_Ptr r_b = Cast(rhs)) { + if (!force && r_b->is_delayed()) break; + rhs = operator()(r_b); + } + + // don't eval delayed expressions (the '/' when used as a separator) + if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { + b->right(b->right()->perform(this)); + b->left(b->left()->perform(this)); + return b.detach(); + } + + // specific types we know are final + // handle them early to avoid overhead + if (Number_Ptr l_n = Cast(lhs)) { + // lhs is number and rhs is number + if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; + case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; + case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + throw Exception::SassValueError(b_in->pstate(), err); + } + } + // lhs is number and rhs is color + else if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; + case Sass_OP::LT: return *l_n < *r_c ? bool_true : bool_false; + case Sass_OP::GTE: return *l_n < *r_c ? bool_false : bool_true; + case Sass_OP::LTE: return *l_n < *r_c || *l_n == *r_c ? bool_true : bool_false; + case Sass_OP::GT: return *l_n < *r_c || *l_n == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + throw Exception::SassValueError(b_in->pstate(), err); + } + } + } + else if (Color_Ptr l_c = Cast(lhs)) { + // lhs is color and rhs is color + if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; + case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; + case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + throw Exception::SassValueError(b_in->pstate(), err); + } + } + // lhs is color and rhs is number + else if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; + case Sass_OP::LT: return *l_c < *r_n ? bool_true : bool_false; + case Sass_OP::GTE: return *l_c < *r_n ? bool_false : bool_true; + case Sass_OP::LTE: return *l_c < *r_n || *l_c == *r_n ? bool_true : bool_false; + case Sass_OP::GT: return *l_c < *r_n || *l_c == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + throw Exception::SassValueError(b_in->pstate(), err); + } + } + } + + String_Schema_Obj ret_schema; // only the last item will be used to eval the binary expression if (String_Schema_Ptr s_l = Cast(b->left())) { @@ -570,16 +703,6 @@ namespace Sass { } } - // don't eval delayed expressions (the '/' when used as a separator) - if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { - b->right(b->right()->perform(this)); - b->left(b->left()->perform(this)); - return b.detach(); - } - - Expression_Obj lhs = b->left(); - Expression_Obj rhs = b->right(); - // fully evaluate their values if (op_type == Sass_OP::EQ || op_type == Sass_OP::NEQ || @@ -600,26 +723,13 @@ namespace Sass { lhs = lhs->perform(this); } - Binary_Expression_Obj u3 = b; - switch (op_type) { - case Sass_OP::AND: { - return *lhs ? b->right()->perform(this) : lhs.detach(); - } break; - - case Sass_OP::OR: { - return *lhs ? lhs.detach() : b->right()->perform(this); - } break; - - default: - break; - } // not a logical connective, so go ahead and eval the rhs rhs = rhs->perform(this); AST_Node_Obj lu = lhs; AST_Node_Obj ru = rhs; - Expression::Concrete_Type l_type = lhs->concrete_type(); - Expression::Concrete_Type r_type = rhs->concrete_type(); + Expression::Concrete_Type l_type; + Expression::Concrete_Type r_type; // Is one of the operands an interpolant? String_Schema_Obj s1 = Cast(b->left()); @@ -643,8 +753,7 @@ namespace Sass { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { - Textual_Obj l = SASS_MEMORY_NEW(Textual, b->pstate(), Textual::DIMENSION, str->value()); - lhs = l->perform(this); + lhs = Parser::lexed_dimension(b->pstate(), str->value()); } } // If possible upgrade RHS to a number (for string to number compare) @@ -652,8 +761,7 @@ namespace Sass { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { - Textual_Obj r = SASS_MEMORY_NEW(Textual, b->pstate(), Textual::DIMENSION, str->value()); - rhs = r->perform(this); + rhs = Parser::lexed_dimension(b->pstate(), str->value()); } } } @@ -661,18 +769,6 @@ namespace Sass { To_Value to_value(ctx); Value_Obj v_l = Cast(lhs->perform(&to_value)); Value_Obj v_r = Cast(rhs->perform(&to_value)); - l_type = lhs->concrete_type(); - r_type = rhs->concrete_type(); - - if (s2 && s2->has_interpolants() && s2->length()) { - Textual_Obj front = Cast(s2->elements().front()); - if (front && !front->is_interpolant()) - { - // XXX: this is never hit via spec tests - schema_op = true; - rhs = front->perform(this); - } - } if (force_delay) { std::string str(""); @@ -710,28 +806,29 @@ namespace Sass { // ToDo: throw error in op functions // ToDo: then catch and re-throw them - Expression_Obj rv = 0; + Expression_Obj rv; try { ParserState pstate(b->pstate()); if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { Number_Ptr l_n = Cast(lhs); Number_Ptr r_n = Cast(rhs); - rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, &pstate); + l_n->reduce(); r_n->reduce(); + rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { Number_Ptr l_n = Cast(lhs); Color_Ptr r_c = Cast(rhs); - rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, &pstate); + rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); } else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { Color_Ptr l_c = Cast(lhs); Number_Ptr r_n = Cast(rhs); - rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, &pstate); + rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); } else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { Color_Ptr l_c = Cast(lhs); Color_Ptr r_c = Cast(rhs); - rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, &pstate); + rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); } else { To_Value to_value(ctx); @@ -744,13 +841,13 @@ namespace Sass { if (op_type == Sass_OP::SUB) interpolant = false; // if (op_type == Sass_OP::DIV) interpolant = true; // check for type violations - if (l_type == Expression::MAP) { + if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { throw Exception::InvalidValue(*v_l); } - if (r_type == Expression::MAP) { + if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { throw Exception::InvalidValue(*v_r); } - Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, &pstate, !interpolant); // pass true to compress + Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress if (String_Constant_Ptr str = Cast(ex)) { if (str->concrete_type() == Expression::STRING) @@ -800,6 +897,10 @@ namespace Sass { cpy->value( - cpy->value() ); // negate value return cpy.detach(); // return the copy } + else if (u->optype() == Unary_Expression::SLASH) { + std::string str = '/' + nr->to_string(ctx.c_options); + return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); + } // nothing for positive return nr.detach(); } @@ -878,6 +979,8 @@ namespace Sass { } Definition_Ptr def = Cast((*env)[full_name]); + if (c->func()) def = c->func()->definition(); + if (def->is_overload_stub()) { std::stringstream ss; size_t L = args->length(); @@ -900,6 +1003,8 @@ namespace Sass { Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); + if (c->is_css()) return result.detach(); + Parameters_Obj params = def->parameters(); Env fn_env(def->environment()); exp.env_stack.push_back(&fn_env); @@ -1005,104 +1110,22 @@ namespace Sass { Expression_Ptr Eval::operator()(Variable_Ptr v) { - std::string name(v->name()); Expression_Obj value = 0; Env* env = environment(); - if (env->has(name)) { - value = Cast((*env)[name]); - } + const std::string& name(v->name()); + EnvResult rv(env->find(name)); + if (rv.found) value = static_cast(rv.it->second.ptr()); else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); - if (Argument* arg = Cast(value)) { - value = arg->value(); - } - - // behave according to as ruby sass (add leading zero) - if (Number_Ptr nr = Cast(value)) { - nr->zero(true); - } - + if (Argument_Ptr arg = Cast(value)) value = arg->value(); + if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag value->is_interpolant(v->is_interpolant()); if (force) value->is_expanded(false); value->set_delayed(false); // verified value = value->perform(this); - if(!force) (*env)[name] = value; + if(!force) rv.it->second = value; return value.detach(); } - Expression_Ptr Eval::operator()(Textual_Ptr t) - { - using Prelexer::number; - Expression_Obj result = 0; - size_t L = t->value().length(); - bool zero = !( (L > 0 && t->value().substr(0, 1) == ".") || - (L > 1 && t->value().substr(0, 2) == "0.") || - (L > 1 && t->value().substr(0, 2) == "-.") || - (L > 2 && t->value().substr(0, 3) == "-0.") - ); - - const std::string& text = t->value(); - size_t num_pos = text.find_first_not_of(" \n\r\t"); - if (num_pos == std::string::npos) num_pos = text.length(); - size_t unit_pos = text.find_first_not_of("-+0123456789.", num_pos); - if (unit_pos == std::string::npos) unit_pos = text.length(); - const std::string& num = text.substr(num_pos, unit_pos - num_pos); - - switch (t->valtype()) - { - case Textual::NUMBER: - result = SASS_MEMORY_NEW(Number, - t->pstate(), - sass_atof(num.c_str()), - "", - zero); - break; - case Textual::PERCENTAGE: - result = SASS_MEMORY_NEW(Number, - t->pstate(), - sass_atof(num.c_str()), - "%", - true); - break; - case Textual::DIMENSION: - result = SASS_MEMORY_NEW(Number, - t->pstate(), - sass_atof(num.c_str()), - Token(number(text.c_str())), - zero); - break; - case Textual::HEX: { - if (t->value().substr(0, 1) != "#") { - result = SASS_MEMORY_NEW(String_Quoted, t->pstate(), t->value()); - break; - } - std::string hext(t->value().substr(1)); // chop off the '#' - if (hext.length() == 6) { - std::string r(hext.substr(0,2)); - std::string g(hext.substr(2,2)); - std::string b(hext.substr(4,2)); - result = SASS_MEMORY_NEW(Color, - t->pstate(), - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - t->value()); - } - else { - result = SASS_MEMORY_NEW(Color, - t->pstate(), - static_cast(strtol(std::string(2,hext[0]).c_str(), NULL, 16)), - static_cast(strtol(std::string(2,hext[1]).c_str(), NULL, 16)), - static_cast(strtol(std::string(2,hext[2]).c_str(), NULL, 16)), - 1, // alpha channel - t->value()); - } - } break; - } - result->is_interpolant(t->is_interpolant()); - return result.detach(); - } - Expression_Ptr Eval::operator()(Color_Ptr c) { return c; @@ -1133,7 +1156,9 @@ namespace Sass { ex = ll; } if (Number_Ptr nr = Cast(ex)) { - if (!nr->is_valid_css_unit()) { + Number reduced(nr); + reduced.reduce(); + if (!reduced.is_valid_css_unit()) { throw Exception::InvalidValue(*nr); } } @@ -1171,6 +1196,7 @@ namespace Sass { if (l->size() > 1) { // string_to_output would fail "#{'_\a' '_\a'}"; std::string str(ll->to_string(ctx.c_options)); + str = read_hex_escapes(str); // read escapes newline_to_space(str); // replace directly res += str; // append to result string } else { @@ -1180,7 +1206,6 @@ namespace Sass { } // Value - // Textual // Function_Call // Selector_List // String_Quoted @@ -1192,7 +1217,9 @@ namespace Sass { if (into_quotes && ex->is_interpolant()) { res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); } else { - res += ex ? ex->to_string(ctx.c_options) : ""; + std::string str(ex ? ex->to_string(ctx.c_options) : ""); + if (into_quotes) str = read_hex_escapes(str); + res += str; // append to result string } } @@ -1319,7 +1346,7 @@ namespace Sass { return ee; } - Expression_Ptr Eval::operator()(Media_Query_Ptr q) + Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) { String_Obj t = q->media_type(); t = static_cast(t.isNull() ? 0 : t->perform(this)); @@ -1471,92 +1498,107 @@ namespace Sass { return *l < *r; } - Value_Ptr Eval::op_numbers(enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate) + Value_Ptr Eval::op_numbers(enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, const ParserState& pstate) { double lv = l.value(); double rv = r.value(); + if (op == Sass_OP::DIV && rv == 0) { // XXX: this is never hit via spec tests - return SASS_MEMORY_NEW(String_Quoted, pstate ? *pstate : l.pstate(), lv ? "Infinity" : "NaN"); + return SASS_MEMORY_NEW(String_Quoted, pstate, lv ? "Infinity" : "NaN"); } + if (op == Sass_OP::MOD && !rv) { // XXX: this is never hit via spec tests throw Exception::ZeroDivisionError(l, r); } - Number tmp(&r); // copy - bool strict = op != Sass_OP::MUL && op != Sass_OP::DIV; - tmp.normalize(l.find_convertible_unit(), strict); - std::string l_unit(l.unit()); - std::string r_unit(tmp.unit()); - Number_Obj v = SASS_MEMORY_COPY(&l); // copy - v->pstate(pstate ? *pstate : l.pstate()); - if (l_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerator_units() = r.numerator_units(); - v->denominator_units() = r.denominator_units(); + size_t l_n_units = l.numerators.size(); + size_t l_d_units = l.numerators.size(); + size_t r_n_units = r.denominators.size(); + size_t r_d_units = r.denominators.size(); + // optimize out the most common and simplest case + if (l_n_units == r_n_units && l_d_units == r_d_units) { + if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { + if (l.numerators == r.numerators) { + if (l.denominators == r.denominators) { + Number_Ptr v = SASS_MEMORY_COPY(&l); + v->value(ops[op](lv, rv)); + return v; + } + } + } + } + + Number_Obj v = SASS_MEMORY_COPY(&l); + + if (l.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerators = r.numerators; + v->denominators = r.denominators; } if (op == Sass_OP::MUL) { v->value(ops[op](lv, rv)); - for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) { - v->numerator_units().push_back(r.numerator_units()[i]); - } - for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) { - v->denominator_units().push_back(r.denominator_units()[i]); - } + v->numerators.insert(v->numerators.end(), + r.numerators.begin(), r.numerators.end() + ); + v->denominators.insert(v->denominators.end(), + r.denominators.begin(), r.denominators.end() + ); } else if (op == Sass_OP::DIV) { v->value(ops[op](lv, rv)); - for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) { - v->denominator_units().push_back(r.numerator_units()[i]); - } - for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) { - v->numerator_units().push_back(r.denominator_units()[i]); - } - } else { - v->value(ops[op](lv, r.value() * r.convert_factor(l))); - // v->normalize(); - return v.detach(); - - v->value(ops[op](lv, tmp.value())); + v->numerators.insert(v->numerators.end(), + r.denominators.begin(), r.denominators.end() + ); + v->denominators.insert(v->denominators.end(), + r.numerators.begin(), r.numerators.end() + ); + } + else { + Number ln(l), rn(r); + ln.reduce(); rn.reduce(); + double f(rn.convert_factor(ln)); + v->value(ops[op](lv, rn.value() * f)); } - v->normalize(); + + v->pstate(pstate); return v.detach(); } - Value_Ptr Eval::op_number_color(enum Sass_OP op, const Number& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate) + Value_Ptr Eval::op_number_color(enum Sass_OP op, const Number& l, const Color& r, struct Sass_Inspect_Options opt, const ParserState& pstate) { double lv = l.value(); switch (op) { case Sass_OP::ADD: case Sass_OP::MUL: { return SASS_MEMORY_NEW(Color, - pstate ? *pstate : l.pstate(), + pstate, ops[op](lv, r.r()), ops[op](lv, r.g()), ops[op](lv, r.b()), r.a()); - } break; + } case Sass_OP::SUB: case Sass_OP::DIV: { std::string sep(op == Sass_OP::SUB ? "-" : "/"); std::string color(r.to_string(opt)); return SASS_MEMORY_NEW(String_Quoted, - pstate ? *pstate : l.pstate(), + pstate, l.to_string(opt) + sep + color); - } break; + } case Sass_OP::MOD: { throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op)); - } break; + } default: break; // caller should ensure that we don't get here } // unreachable return NULL; } - Value_Ptr Eval::op_color_number(enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate) + Value_Ptr Eval::op_color_number(enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, const ParserState& pstate) { double rv = r.value(); if (op == Sass_OP::DIV && !rv) { @@ -1564,14 +1606,14 @@ namespace Sass { throw Exception::ZeroDivisionError(l, r); } return SASS_MEMORY_NEW(Color, - pstate ? *pstate : l.pstate(), + pstate, ops[op](l.r(), rv), ops[op](l.g(), rv), ops[op](l.b(), rv), l.a()); } - Value_Ptr Eval::op_colors(enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate) + Value_Ptr Eval::op_colors(enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, const ParserState& pstate) { if (l.a() != r.a()) { throw Exception::AlphaChannelsNotEqual(&l, &r, "+"); @@ -1581,14 +1623,14 @@ namespace Sass { throw Exception::ZeroDivisionError(l, r); } return SASS_MEMORY_NEW(Color, - pstate ? *pstate : l.pstate(), + pstate, ops[op](l.r(), r.r()), ops[op](l.g(), r.g()), ops[op](l.b(), r.b()), l.a()); } - Value_Ptr Eval::op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, ParserState* pstate, bool delayed) + Value_Ptr Eval::op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) { Expression::Concrete_Type ltype = lhs.concrete_type(); Expression::Concrete_Type rtype = rhs.concrete_type(); @@ -1602,20 +1644,19 @@ namespace Sass { if (ltype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); if (rtype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); - if (op == Sass_OP::MOD) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); - if (op == Sass_OP::MUL) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); std::string sep; switch (op) { case Sass_OP::SUB: sep = "-"; break; case Sass_OP::DIV: sep = "/"; break; - case Sass_OP::MUL: sep = "*"; break; - case Sass_OP::MOD: sep = "%"; break; + // cases are already handled above case Sass_OP::EQ: sep = "=="; break; case Sass_OP::NEQ: sep = "!="; break; case Sass_OP::LT: sep = "<"; break; case Sass_OP::GT: sep = ">"; break; case Sass_OP::LTE: sep = "<="; break; case Sass_OP::GTE: sep = ">="; break; + case Sass_OP::MUL: throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); + case Sass_OP::MOD: throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); default: break; } @@ -1623,7 +1664,7 @@ namespace Sass { (sep != "/" || !rqstr || !rqstr->quote_mark()) */ ) { // create a new string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate ? *pstate : lhs.pstate(), lstr + rstr, 0, false, true); + return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); } if (sep != "" && !delayed) { @@ -1636,7 +1677,7 @@ namespace Sass { if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); } - return SASS_MEMORY_NEW(String_Constant, pstate ? *pstate : lhs.pstate(), lstr + sep + rstr); + return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); } Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate) @@ -1687,6 +1728,7 @@ namespace Sass { case SASS_WARNING: { error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, backtrace); } break; + default: break; } return e; } @@ -1727,30 +1769,64 @@ namespace Sass { Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) { bool implicit_parent = !exp.old_at_root_without_rule; - return s->resolve_parent_refs(ctx, exp.selector_stack, implicit_parent); + if (is_in_selector_schema) exp.selector_stack.push_back(0); + Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, implicit_parent); + if (is_in_selector_schema) exp.selector_stack.pop_back(); + for (size_t i = 0; i < resolved->length(); i++) { + Complex_Selector_Ptr is = resolved->at(i)->first(); + while (is) { + if (is->head()) { + is->head(operator()(is->head())); + } + is = is->tail(); + } + } + return resolved.detach(); } - // XXX: this is never hit via spec tests - Attribute_Selector_Ptr Eval::operator()(Attribute_Selector_Ptr s) + Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) { - String_Obj attr = s->value(); - if (attr) { attr = static_cast(attr->perform(this)); } - Attribute_Selector_Ptr ss = SASS_MEMORY_COPY(s); - ss->value(attr); - return ss; + for (size_t i = 0; i < s->length(); i++) { + Simple_Selector_Ptr ss = s->at(i); + // skip parents here (called via resolve_parent_refs) + if (ss == NULL || Cast(ss)) continue; + s->at(i) = Cast(ss->perform(this)); + } + return s; } Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) { + LOCAL_FLAG(is_in_selector_schema, true); // the parser will look for a brace to end the selector + ctx.c_options.in_selector = true; // do not compress colors Expression_Obj sel = s->contents()->perform(this); std::string result_str(sel->to_string(ctx.c_options)); + ctx.c_options.in_selector = false; // flag temporary only result_str = unquote(Util::rtrim(result_str)); - Parser p = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()); + char* temp_cstr = sass_copy_c_string(result_str.c_str()); + ctx.strings.push_back(temp_cstr); // attach to context + Parser p = Parser::from_c_str(temp_cstr, ctx, s->pstate()); p.last_media_block = s->media_block(); // a selector schema may or may not connect to parent? bool chroot = s->connect_parent() == false; Selector_List_Obj sl = p.parse_selector_list(chroot); + auto vec_str_rend = ctx.strings.rend(); + auto vec_str_rbegin = ctx.strings.rbegin(); + // remove the first item searching from the back + // we cannot assume our item is still the last one + // order is not important, so we can optimize this + auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); + // undefined behavior if not found! + if (it != vec_str_rend) { + // overwrite with last item + *it = ctx.strings.back(); + // remove last one from vector + ctx.strings.pop_back(); + // free temporary copy + free(temp_cstr); + } + flag_is_in_selector_schema.reset(); return operator()(sl); } @@ -1766,4 +1842,43 @@ namespace Sass { } } + Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) + { + return s; + } + + // hotfix to avoid invalid nested `:not` selectors + // probably the wrong place, but this should ultimately + // be fixed by implement superselector correctly for `:not` + // first use of "find" (ATM only implemented for selectors) + bool hasNotSelector(AST_Node_Obj obj) { + if (Wrapped_Selector_Ptr w = Cast(obj)) { + return w->name() == ":not"; + } + return false; + } + + Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) + { + + if (s->name() == ":not") { + if (exp.selector_stack.back()) { + if (s->selector()->find(hasNotSelector)) { + s->selector()->clear(); + s->name(" "); + } else if (s->selector()->length() == 1) { + Complex_Selector_Ptr cs = s->selector()->at(0); + if (cs->tail()) { + s->selector()->clear(); + s->name(" "); + } + } else if (s->selector()->length() > 1) { + s->selector()->clear(); + s->name(" "); + } + } + } + return s; + }; + } diff --git a/src/libsass/src/eval.hpp b/src/libsass/src/eval.hpp old mode 100755 new mode 100644 index ca89d7fc2..16a1b9210 --- a/src/libsass/src/eval.hpp +++ b/src/libsass/src/eval.hpp @@ -25,6 +25,10 @@ namespace Sass { bool force; bool is_in_comment; + bool is_in_selector_schema; + + Boolean_Obj bool_true; + Boolean_Obj bool_false; Env* environment(); Backtrace* backtrace(); @@ -49,7 +53,6 @@ namespace Sass { Expression_Ptr operator()(Function_Call_Ptr); Expression_Ptr operator()(Function_Call_Schema_Ptr); Expression_Ptr operator()(Variable_Ptr); - Expression_Ptr operator()(Textual_Ptr); Expression_Ptr operator()(Number_Ptr); Expression_Ptr operator()(Color_Ptr); Expression_Ptr operator()(Boolean_Ptr); @@ -57,7 +60,7 @@ namespace Sass { Expression_Ptr operator()(String_Quoted_Ptr); Expression_Ptr operator()(String_Constant_Ptr); // Expression_Ptr operator()(Selector_List_Ptr); - Expression_Ptr operator()(Media_Query_Ptr); + Media_Query_Ptr operator()(Media_Query_Ptr); Expression_Ptr operator()(Media_Query_Expression_Ptr); Expression_Ptr operator()(At_Root_Query_Ptr); Expression_Ptr operator()(Supports_Operator_Ptr); @@ -72,14 +75,15 @@ namespace Sass { // these will return selectors Selector_List_Ptr operator()(Selector_List_Ptr); Selector_List_Ptr operator()(Complex_Selector_Ptr); - Attribute_Selector_Ptr operator()(Attribute_Selector_Ptr); - // they don't have any specific implementatio (yet) - Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; }; - Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; }; - Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s) { return s; }; - Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; }; - Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; }; - Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; }; + Compound_Selector_Ptr operator()(Compound_Selector_Ptr); + Simple_Selector_Ptr operator()(Simple_Selector_Ptr s); + Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s); + // they don't have any specific implementation (yet) + // Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; }; + // Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; }; + // Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; }; + // Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; }; + // Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; }; // actual evaluated selectors Selector_List_Ptr operator()(Selector_Schema_Ptr); Expression_Ptr operator()(Parent_Selector_Ptr); @@ -91,11 +95,11 @@ namespace Sass { static bool eq(Expression_Obj, Expression_Obj); static bool lt(Expression_Obj, Expression_Obj, std::string op); // -- arithmetic on the combinations that matter - static Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); - static Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); - static Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); - static Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, ParserState* pstate = 0); - static Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, ParserState* pstate = 0, bool interpolant = false); + static Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate); + static Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate); + static Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate); + static Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate); + static Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool interpolant = false); private: void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp old mode 100755 new mode 100644 index 1057d980f..8c85024ae --- a/src/libsass/src/expand.cpp +++ b/src/libsass/src/expand.cpp @@ -117,7 +117,7 @@ namespace Sass { if (sel) sel = sel->eval(eval); // check for parent selectors in base level rules - if (r->is_root()) { + if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { if (Selector_List_Ptr selector_list = Cast(r->selector())) { for (Complex_Selector_Obj complex_selector : selector_list->elements()) { Complex_Selector_Ptr tail = complex_selector; @@ -141,6 +141,8 @@ namespace Sass { } } + // do not connect parent again + sel->remove_parent_selectors(); selector_stack.push_back(sel); Env env(environment()); if (block_stack.back()->is_root()) { @@ -176,18 +178,25 @@ namespace Sass { Statement_Ptr Expand::operator()(Media_Block_Ptr m) { - media_block_stack.push_back(m); - Expression_Obj mq = m->media_queries()->perform(&eval); + Media_Block_Obj cpy = SASS_MEMORY_COPY(m); + // Media_Blocks are prone to have circular references + // Copy could leak memory if it does not get picked up + // Looks like we are able to reset block reference for copy + // Good as it will ensure a low memory overhead for this fix + // So this is a cheap solution with a minimal price + ctx.ast_gc.push_back(cpy); cpy->block(0); + Expression_Obj mq = eval(m->media_queries()); std::string str_mq(mq->to_string(ctx.c_options)); char* str = sass_copy_c_string(str_mq.c_str()); ctx.strings.push_back(str); Parser p(Parser::from_c_str(str, ctx, mq->pstate())); mq = p.parse_media_queries(); // re-assign now - List_Obj ls = Cast(mq->perform(&eval)); + cpy->media_queries(mq); + media_block_stack.push_back(cpy); Block_Obj blk = operator()(m->block()); Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, m->pstate(), - ls, + mq, blk); media_block_stack.pop_back(); mm->tabs(m->tabs()); @@ -256,6 +265,7 @@ namespace Sass { new_p, value, d->is_important(), + d->is_custom_property(), bb); decl->tabs(d->tabs()); return decl; @@ -264,7 +274,7 @@ namespace Sass { Statement_Ptr Expand::operator()(Assignment_Ptr a) { Env* env = environment(); - std::string var(a->variable()); + const std::string& var(a->variable()); if (a->is_global()) { if (a->is_default()) { if (env->has_global(var)) { @@ -381,6 +391,11 @@ namespace Sass { Statement_Ptr Expand::operator()(Comment_Ptr c) { + if (ctx.output_style() == COMPRESSED) { + // comments should not be evaluated in compact + // https://github.com/sass/libsass/issues/2359 + if (!c->is_important()) return NULL; + } eval.is_in_comment = true; Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); eval.is_in_comment = false; @@ -434,16 +449,13 @@ namespace Sass { Env env(environment(), true); env_stack.push_back(&env); call_stack.push_back(f); - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit()); - env.set_local(variable, it); Block_Ptr body = f->block(); if (start < end) { if (f->is_inclusive()) ++end; for (double i = start; i < end; ++i) { - it = SASS_MEMORY_COPY(it); - it->value(i); + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); env.set_local(variable, it); append_block(body); } @@ -452,8 +464,7 @@ namespace Sass { for (double i = start; i > end; --i) { - it = SASS_MEMORY_COPY(it); - it->value(i); + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); env.set_local(variable, it); append_block(body); } @@ -515,11 +526,11 @@ namespace Sass { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Obj e = list->at(i); + Expression_Obj item = list->at(i); // unwrap value if the expression is an argument - if (Argument_Obj arg = Cast(e)) e = arg->value(); + if (Argument_Obj arg = Cast(item)) item = arg->value(); // check if we got passed a list of args (investigate) - if (List_Obj scalars = Cast(e)) { + if (List_Obj scalars = Cast(item)) { if (variables.size() == 1) { List_Obj var = scalars; // if (arglist) var = (*scalars)[0]; @@ -534,7 +545,7 @@ namespace Sass { } } else { if (variables.size() > 0) { - env.set_local(variables.at(0), e); + env.set_local(variables.at(0), item); for (size_t j = 1, K = variables.size(); j < K; ++j) { Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); env.set_local(variables[j], res); @@ -598,8 +609,8 @@ namespace Sass { std::string sel_str(contextualized->to_string(ctx.c_options)); error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace()); } - Compound_Selector_Obj placeholder = c->head(); - if (contextualized->is_optional()) placeholder->is_optional(true); + Compound_Selector_Obj target = c->head(); + if (contextualized->is_optional()) target->is_optional(true); for (size_t i = 0, L = extender->length(); i < L; ++i) { Complex_Selector_Obj sel = (*extender)[i]; if (!(sel->head() && sel->head()->length() > 0 && @@ -618,7 +629,7 @@ namespace Sass { sel = ssel; } // if (c->has_line_feed()) sel->has_line_feed(true); - ctx.subset_map.put(placeholder, std::make_pair(sel, placeholder)); + ctx.subset_map.put(target, std::make_pair(sel, target)); } } @@ -669,9 +680,9 @@ namespace Sass { d->name() == "url" )) { deprecated( - "Naming a function \"" + d->name() + "\" is disallowed", + "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", "This name conflicts with an existing CSS function with special parse rules.", - d->pstate() + false, d->pstate() ); } @@ -682,7 +693,6 @@ namespace Sass { Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) { - if (recursions > maxRecursion) { throw Exception::StackError(*c); } @@ -733,13 +743,20 @@ namespace Sass { Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); - + env->set_global("is_in_mixin", bool_true); + if (Block_Ptr pr = block_stack.back()) { + trace_block->is_root(pr->is_root()); + } block_stack.push_back(trace_block); for (auto bb : body->elements()) { + if (Ruleset_Ptr r = Cast(bb)) { + r->is_root(trace_block->is_root()); + } Statement_Obj ith = bb->perform(this); if (ith) trace->block()->append(ith); } block_stack.pop_back(); + env->del_global("is_in_mixin"); env_stack.pop_back(); backtrace_stack.pop_back(); diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp old mode 100755 new mode 100644 index 2d0d5ecea..348503b2b --- a/src/libsass/src/expand.hpp +++ b/src/libsass/src/expand.hpp @@ -37,6 +37,8 @@ namespace Sass { std::vector media_block_stack; std::vector backtrace_stack; + Boolean_Obj bool_true; + Statement_Ptr fallback_impl(AST_Node_Ptr n); private: diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp old mode 100755 new mode 100644 index 618d979db..5348e5dcf --- a/src/libsass/src/extend.cpp +++ b/src/libsass/src/extend.cpp @@ -4,6 +4,7 @@ #include "backtrace.hpp" #include "paths.hpp" #include "parser.hpp" +#include "expand.hpp" #include "node.hpp" #include "sass_util.hpp" #include "remove_placeholders.hpp" @@ -206,7 +207,7 @@ namespace Sass { } // Print a string representation of a ComplexSelectorSet - static void printSourcesSet(ComplexSelectorSet& sources, Context& ctx, const char* message=NULL, bool newline=true) { + static void printSourcesSet(ComplexSelectorSet& sources, const char* message=NULL, bool newline=true) { if (message) { std::cerr << message; @@ -219,7 +220,7 @@ namespace Sass { for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { Complex_Selector_Ptr pSource = *iterator; std::stringstream sstream; - sstream << complexSelectorToNode(pSource, ctx); + sstream << complexSelectorToNode(pSource); sourceStrings.push_back(sstream.str()); } @@ -279,10 +280,9 @@ namespace Sass { } #endif - static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo, Context& ctx) { + static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo) { // TODO: figure out a better way to create a Complex_Selector from scratch // TODO: There's got to be a better way. This got ugly quick... - Position noPosition(-1, -1, -1); Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); fakeHead->elements().push_back(fakeParent); @@ -299,19 +299,19 @@ namespace Sass { return isSuperselector; } - void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out, Context& ctx) { + void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out) { for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { Node& child = *iter; - out.push_back(nodeToComplexSelector(child, ctx)); + out.push_back(nodeToComplexSelector(child)); } } - Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque, Context& ctx) { + Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque) { Node result = Node::createCollection(); for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { Complex_Selector_Obj pChild = *iter; - result.collection()->push_back(complexSelectorToNode(pChild, ctx)); + result.collection()->push_back(complexSelectorToNode(pChild)); } return result; @@ -319,9 +319,7 @@ namespace Sass { class LcsCollectionComparator { public: - LcsCollectionComparator(Context& ctx) : mCtx(ctx) {} - - Context& mCtx; + LcsCollectionComparator() {} bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const { /* @@ -343,12 +341,12 @@ namespace Sass { return false; } - if (parentSuperselector(pOne, pTwo, mCtx)) { + if (parentSuperselector(pOne, pTwo)) { pOut = pTwo; return true; } - if (parentSuperselector(pTwo, pOne, mCtx)) { + if (parentSuperselector(pTwo, pOne)) { pOut = pOne; return true; } @@ -438,7 +436,7 @@ namespace Sass { http://en.wikipedia.org/wiki/Longest_common_subsequence_problem */ - void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, Context& ctx, ComplexSelectorDeque& out) { + void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output @@ -518,7 +516,7 @@ namespace Sass { /* - IMPROVEMENT: We could probably work directly in the output trimmed deque. */ - static Node trim(Node& seqses, Context& ctx, bool isReplace) { + Node Extend::trim(Node& seqses, bool isReplace) { // See the comments in the above ruby code before embarking on understanding this function. // Avoid poor performance in extreme cases. @@ -551,7 +549,7 @@ namespace Sass { for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) { Node& seq1 = *seqs1Iter; - Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1, ctx); + Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1); // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code // for a good description of sources. @@ -565,7 +563,7 @@ namespace Sass { ComplexSelectorSet sources = pSeq1->sources(); DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) - DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIM SOURCES: ")) + DEBUG_EXEC(TRIM, printSourcesSet(sources, "TRIM SOURCES: ")) for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; @@ -598,7 +596,7 @@ namespace Sass { for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) { Node& seq2 = *seqs2Iter; - Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2, ctx); + Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2); DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity()) DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false")) @@ -642,18 +640,17 @@ namespace Sass { - static bool parentSuperselector(const Node& one, const Node& two, Context& ctx) { + static bool parentSuperselector(const Node& one, const Node& two) { // TODO: figure out a better way to create a Complex_Selector from scratch // TODO: There's got to be a better way. This got ugly quick... - Position noPosition(-1, -1, -1); Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); fakeHead->elements().push_back(fakeParent); Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); - Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one, ctx); + Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one); pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two, ctx); + Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two); pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); @@ -662,14 +659,13 @@ namespace Sass { class ParentSuperselectorChunker { public: - ParentSuperselectorChunker(Node& lcs, Context& ctx) : mLcs(lcs), mCtx(ctx) {} + ParentSuperselectorChunker(Node& lcs) : mLcs(lcs) {} Node& mLcs; - Context& mCtx; bool operator()(const Node& seq) const { // {|s| parent_superselector?(s.first, lcs.first)} if (seq.collection()->size() == 0) return false; - return parentSuperselector(seq.collection()->front(), mLcs.collection()->front(), mCtx); + return parentSuperselector(seq.collection()->front(), mLcs.collection()->front()); } }; @@ -723,7 +719,7 @@ namespace Sass { } Node chunk2 = Node::createCollection(); - while (!chunker(seq2)) { + while (!seq2.collection()->empty() && !chunker(seq2)) { chunk2.collection()->push_back(seq2.collection()->front()); seq2.collection()->pop_front(); } @@ -765,7 +761,7 @@ namespace Sass { } - static Node groupSelectors(Node& seq, Context& ctx) { + static Node groupSelectors(Node& seq) { Node newSeq = Node::createCollection(); Node tail = Node::createCollection(); @@ -825,7 +821,7 @@ namespace Sass { return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) end */ - static Node mergeInitialOps(Node& seq1, Node& seq2, Context& ctx) { + static Node mergeInitialOps(Node& seq1, Node& seq2) { Node ops1 = Node::createCollection(); Node ops2 = Node::createCollection(); @@ -839,7 +835,7 @@ namespace Sass { // If neither sequence is a subsequence of the other, they cannot be merged successfully DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx); + Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); if (!(opsLcs == ops1 || opsLcs == ops2)) { return Node::createNil(); @@ -916,7 +912,7 @@ namespace Sass { end end */ - static Node mergeFinalOps(Node& seq1, Node& seq2, Context& ctx, Node& res) { + static Node mergeFinalOps(Node& seq1, Node& seq2, Node& res) { Node ops1 = Node::createCollection(); Node ops2 = Node::createCollection(); @@ -934,7 +930,7 @@ namespace Sass { if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) { DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx); + Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. @@ -981,7 +977,7 @@ namespace Sass { Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx); + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); pMergedWrapper->head(pMerged); DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) @@ -1004,7 +1000,7 @@ namespace Sass { if (pMerged) { Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx)); + mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); newRes.collection()->push_back(mergedPerm); } @@ -1018,12 +1014,10 @@ namespace Sass { } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) { Node tildeSel = sel1; - Node tildeOp = op1; Node plusSel = sel2; Node plusOp = op2; if (op1.combinator() != Complex_Selector::PRECEDES) { tildeSel = sel2; - tildeOp = op2; plusSel = sel1; plusOp = op1; } @@ -1040,7 +1034,7 @@ namespace Sass { Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head(), ctx); + Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head()); pMergedWrapper->head(pMerged); DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) @@ -1056,7 +1050,7 @@ namespace Sass { if (pMerged) { Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx)); + mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); newRes.collection()->push_back(mergedPerm); } @@ -1089,7 +1083,7 @@ namespace Sass { Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx); + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); pMergedWrapper->head(pMerged); DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) @@ -1099,7 +1093,7 @@ namespace Sass { } res.collection()->push_front(op1); - res.collection()->push_front(Node::createSelector(pMergedWrapper, ctx)); + res.collection()->push_front(Node::createSelector(pMergedWrapper)); DEBUG_PRINTLN(ALL, "RESULT: " << res) @@ -1107,7 +1101,7 @@ namespace Sass { return Node::createNil(); } - return mergeFinalOps(seq1, seq2, ctx, res); + return mergeFinalOps(seq1, seq2, res); } else if (!ops1.collection()->empty()) { @@ -1122,7 +1116,7 @@ namespace Sass { res.collection()->push_front(seq1.collection()->back()); seq1.collection()->pop_back(); - return mergeFinalOps(seq1, seq2, ctx, res); + return mergeFinalOps(seq1, seq2, res); } else { // !ops2.collection()->empty() @@ -1136,7 +1130,7 @@ namespace Sass { res.collection()->push_front(seq2.collection()->back()); seq2.collection()->pop_back(); - return mergeFinalOps(seq1, seq2, ctx, res); + return mergeFinalOps(seq1, seq2, res); } @@ -1179,7 +1173,7 @@ namespace Sass { result end */ - Node Extend::subweave(Node& one, Node& two, Context& ctx) { + Node subweave(Node& one, Node& two) { // Check for the simple cases if (one.collection()->size() == 0) { Node out = Node::createCollection(); @@ -1192,8 +1186,6 @@ namespace Sass { return out; } - - Node seq1 = Node::createCollection(); seq1.plus(one); Node seq2 = Node::createCollection(); @@ -1202,7 +1194,7 @@ namespace Sass { DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1) DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) - Node init = mergeInitialOps(seq1, seq2, ctx); + Node init = mergeInitialOps(seq1, seq2); if (init.isNil()) { return Node::createNil(); } @@ -1210,7 +1202,7 @@ namespace Sass { DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init) Node res = Node::createCollection(); - Node fin = mergeFinalOps(seq1, seq2, ctx, res); + Node fin = mergeFinalOps(seq1, seq2, res); if (fin.isNil()) { return Node::createNil(); } @@ -1238,23 +1230,23 @@ namespace Sass { - Node groupSeq1 = groupSelectors(seq1, ctx); + Node groupSeq1 = groupSelectors(seq1); DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) - Node groupSeq2 = groupSelectors(seq2, ctx); + Node groupSeq2 = groupSelectors(seq2); DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2) ComplexSelectorDeque groupSeq1Converted; - nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted, ctx); + nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted); ComplexSelectorDeque groupSeq2Converted; - nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted, ctx); + nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted); ComplexSelectorDeque out; - LcsCollectionComparator collectionComparator(ctx); - lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, ctx, out); - Node seqLcs = complexSelectorDequeToNode(out, ctx); + LcsCollectionComparator collectionComparator; + lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, out); + Node seqLcs = complexSelectorDequeToNode(out); DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) @@ -1268,7 +1260,7 @@ namespace Sass { while (!seqLcs.collection()->empty()) { - ParentSuperselectorChunker superselectorChunker(seqLcs, ctx); + ParentSuperselectorChunker superselectorChunker(seqLcs); Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker); diff.collection()->push_back(chunksResult); @@ -1313,7 +1305,7 @@ namespace Sass { DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff) - Node pathsResult = paths(diff, ctx); + Node pathsResult = paths(diff); DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) @@ -1323,7 +1315,7 @@ namespace Sass { pathsIter != pathsEndIter; ++pathsIter) { Node& child = *pathsIter; - child = flatten(child, ctx); + child = flatten(child); } DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult) @@ -1341,25 +1333,25 @@ namespace Sass { } /* // disabled to avoid clang warning [-Wunused-function] - static Node subweaveNaive(const Node& one, const Node& two, Context& ctx) { + static Node subweaveNaive(const Node& one, const Node& two) { Node out = Node::createCollection(); // Check for the simple cases if (one.isNil()) { - out.collection()->push_back(two.klone(ctx)); + out.collection()->push_back(two.klone()); } else if (two.isNil()) { - out.collection()->push_back(one.klone(ctx)); + out.collection()->push_back(one.klone()); } else { // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B // See https://gist.github.com/nex3/7609394 for details. - Node firstPerm = one.klone(ctx); - Node twoCloned = two.klone(ctx); + Node firstPerm = one.klone(); + Node twoCloned = two.klone(); firstPerm.plus(twoCloned); out.collection()->push_back(firstPerm); - Node secondPerm = two.klone(ctx); - Node oneCloned = one.klone(ctx); + Node secondPerm = two.klone(); + Node oneCloned = one.klone(); secondPerm.plus(oneCloned ); out.collection()->push_back(secondPerm); } @@ -1442,7 +1434,7 @@ namespace Sass { return befores end */ - static Node weave(Node& path, Context& ctx) { + Node Extend::weave(Node& path) { DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) @@ -1453,7 +1445,7 @@ namespace Sass { afters.plus(path); while (!afters.collection()->empty()) { - Node current = afters.collection()->front().klone(ctx); + Node current = afters.collection()->front().klone(); afters.collection()->pop_front(); DEBUG_PRINTLN(WEAVE, "CURRENT: " << current) if (current.collection()->size() == 0) continue; @@ -1469,7 +1461,7 @@ namespace Sass { for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { Node& before = *beforesIter; - Node sub = Extend::subweave(before, current, ctx); + Node sub = subweave(before, current); DEBUG_PRINTLN(WEAVE, "SUB: " << sub) @@ -1484,6 +1476,13 @@ namespace Sass { toPush.plus(seqs); toPush.plus(last_current); + // move line feed from inner to outer selector (very hacky indeed) + if (last_current.collection() && last_current.collection()->front().selector()) { + toPush.got_line_feed = last_current.collection()->front().got_line_feed; + last_current.collection()->front().selector()->has_line_feed(false); + last_current.collection()->front().got_line_feed = false; + } + tempResult.collection()->push_back(toPush); } @@ -1498,16 +1497,6 @@ namespace Sass { - // This forward declaration is needed since extendComplexSelector calls extendCompoundSelector, which may recursively - // call extendComplexSelector again. - static Node extendComplexSelector( - Complex_Selector_Ptr pComplexSelector, - Context& ctx, - Subset_Map& subset_map, - std::set seen, bool isReplace, bool isOriginal); - - - /* This is the equivalent of ruby's SimpleSequence.do_extend. @@ -1528,16 +1517,23 @@ namespace Sass { return pSelector; } }; - static Node extendCompoundSelector( - Compound_Selector_Ptr pSelector, - Context& ctx, - Subset_Map& subset_map, - std::set seen, bool isReplace) { + Node Extend::extendCompoundSelector(Compound_Selector_Ptr pSelector, CompoundSelectorSet& seen, bool isReplace) { + + /* this turned out to be too much overhead + probably due to holding a "Node" object + // check if we already extended this selector + // we can do this since subset_map is "static" + auto memoized = memoizeCompound.find(pSelector); + if (memoized != memoizeCompound.end()) { + return memoized->second.klone(); + } + */ DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: ")) // TODO: Ruby has another loop here to skip certain members? - Node extendedSelectors = Node::createCollection(); + // let RESULTS be an empty list of complex selectors + Node results = Node::createCollection(); // extendedSelectors.got_line_feed = true; SubSetMapPairs entries = subset_map.get_v(pSelector); @@ -1548,34 +1544,28 @@ namespace Sass { SubSetMapLookups holder; - - for (SubSetMapResults::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) { - SubSetMapResult& groupedPair = *groupedIter; + // for each (EXTENDER, TARGET) in MAP.get(COMPOUND): + for (SubSetMapResult& groupedPair : arr) { Complex_Selector_Obj seq = groupedPair.first; SubSetMapPairs& group = groupedPair.second; DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) -// changing this makes aua Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - for (SubSetMapPairs::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) { - SubSetMapPair& pair = *groupIter; - Compound_Selector_Obj pCompound = pair.second; - for (size_t index = 0; index < pCompound->length(); index++) { - Simple_Selector_Obj pSimpleSelector = (*pCompound)[index]; - pSels->append(pSimpleSelector); - pCompound->extended(true); - } + for (SubSetMapPair& pair : group) { + pair.second->extended(true); + pSels->concat(pair.second); } DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) - Complex_Selector_Ptr pExtComplexSelector = seq; // The selector up to where the @extend is (ie, the thing to merge) + // The selector up to where the @extend is (ie, the thing to merge) + Complex_Selector_Ptr pExtComplexSelector = seq; // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) - Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels, ctx); + Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels); DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) @@ -1585,7 +1575,7 @@ namespace Sass { if (!pInnermostCompoundSelector) { pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); } - Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors, ctx); + Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors); DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) @@ -1625,22 +1615,22 @@ namespace Sass { // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true); // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. - DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector, ctx)) + DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector)) - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: ")) + DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, "SOURCES NEW SEQ BEGIN: ")) // I actually want to create a copy here (performance!) ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES THIS EXTEND: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES THIS EXTEND: ")) newSourcesSet.insert(pExtComplexSelector); - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES WITH NEW SOURCE: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES WITH NEW SOURCE: ")) // RUBY: new_seq.add_sources!(sources + [seq]) - pNewSelector->addSources(newSourcesSet, ctx); + pNewSelector->addSources(newSourcesSet); - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, ctx, "SOURCES ON NEW SELECTOR AFTER ADD: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), ctx, "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) + DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, "SOURCES ON NEW SELECTOR AFTER ADD: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) if (pSels->has_line_feed()) pNewSelector->has_line_feed(true); @@ -1649,25 +1639,24 @@ namespace Sass { } - for (SubSetMapLookups::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) { - SubSetMapLookup& pair = *holderIter; + for (SubSetMapLookup& pair : holder) { Compound_Selector_Obj pSels = pair.first; Complex_Selector_Obj pNewSelector = pair.second; // RUBY??: next [] if seen.include?(sels) - if (seen.find(*pSels) != seen.end()) { + if (seen.find(pSels) != seen.end()) { continue; } - std::set recurseSeen(seen); - recurseSeen.insert(*pSels); + CompoundSelectorSet recurseSeen(seen); + recurseSeen.insert(pSels); - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector, ctx)) - Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, ctx, subset_map, recurseSeen, isReplace, false); // !:isOriginal + DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector)) + Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, recurseSeen, isReplace, false); // !:isOriginal DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) @@ -1675,31 +1664,32 @@ namespace Sass { iterator != endIterator; ++iterator) { Node newSelector = *iterator; -// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << extendedSelectors) -// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)); +// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << results) +// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << results.contains(newSelector, false /*simpleSelectorOrderDependent*/)); - if (!extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)) { + if (!results.contains(newSelector)) { // DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR") - extendedSelectors.collection()->push_back(newSelector); + results.collection()->push_back(newSelector); } } } DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) - return extendedSelectors; + // this turned out to be too much overhead + // memory results in a map table - since extending is very expensive + // memoizeCompound.insert(std::pair(pSelector, results)); + + return results; } - static bool complexSelectorHasExtension( - Complex_Selector_Ptr pComplexSelector, - Context& ctx, - Subset_Map& subset_map, - std::set& seen) { + // check if selector has something to be extended by subset_map + bool Extend::complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen) { bool hasExtension = false; - Complex_Selector_Obj pIter = pComplexSelector; + Complex_Selector_Obj pIter = selector; while (!hasExtension && pIter) { Compound_Selector_Obj pHead = pIter->head(); @@ -1714,8 +1704,8 @@ namespace Sass { ext.second->media_block()->media_queries() && pHead->media_block()->media_queries() ) { - std::string query_left(ext.second->media_block()->media_queries()->to_string(ctx.c_options)); - std::string query_right(pHead->media_block()->media_queries()->to_string(ctx.c_options)); + std::string query_left(ext.second->media_block()->media_queries()->to_string()); + std::string query_right(pHead->media_block()->media_queries()->to_string()); if (query_left == query_right) continue; } @@ -1726,9 +1716,9 @@ namespace Sass { std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); err << "You may not @extend an outer selector from within @media.\n"; err << "You may only @extend selectors within the same directive.\n"; - err << "From \"@extend " << ext.second->to_string(ctx.c_options) << "\""; + err << "From \"@extend " << ext.second->to_string() << "\""; err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; - error(err.str(), pComplexSelector->pstate()); + error(err.str(), selector->pstate()); } if (entries.size() > 0) hasExtension = true; } @@ -1751,23 +1741,25 @@ namespace Sass { the combinator and compound selector are one unit next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) */ - static Node extendComplexSelector( - Complex_Selector_Ptr pComplexSelector, - Context& ctx, - Subset_Map& subset_map, - std::set seen, bool isReplace, bool isOriginal) { + Node Extend::extendComplexSelector(Complex_Selector_Ptr selector, CompoundSelectorSet& seen, bool isReplace, bool isOriginal) { - Node complexSelector = complexSelectorToNode(pComplexSelector, ctx); - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) + // check if we already extended this selector + // we can do this since subset_map is "static" + auto memoized = memoizeComplex.find(selector); + if (memoized != memoizeComplex.end()) { + return memoized->second; + } - Node extendedNotExpanded = Node::createCollection(); + // convert the input selector to extend node format + Node complexSelector = complexSelectorToNode(selector); + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) - for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(), - complexSelIterEnd = complexSelector.collection()->end(); - complexSelIter != complexSelIterEnd; ++complexSelIter) - { + // let CHOICES be an empty list of selector-lists + // create new collection to hold the results + Node choices = Node::createCollection(); - Node& sseqOrOp = *complexSelIter; + // for each compound selector COMPOUND in COMPLEX: + for (Node& sseqOrOp : *complexSelector.collection()) { DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) @@ -1780,94 +1772,93 @@ namespace Sass { Node inner = Node::createCollection(); outer.collection()->push_back(inner); inner.collection()->push_back(sseqOrOp); - extendedNotExpanded.collection()->push_back(outer); + choices.collection()->push_back(outer); continue; } - Compound_Selector_Obj pCompoundSelector = sseqOrOp.selector()->head(); + // verified now that node is a valid selector + Complex_Selector_Obj sseqSel = sseqOrOp.selector(); + Compound_Selector_Obj sseqHead = sseqSel->head(); + // let EXTENDED be extend_compound(COMPOUND, SEEN) + // extend the compound selector against the given subset_map // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) - Node extended = extendCompoundSelector(pCompoundSelector, ctx, subset_map, seen, isReplace); + Node extended = extendCompoundSelector(sseqHead, seen, isReplace); // slow(17%)! if (sseqOrOp.got_line_feed) extended.got_line_feed = true; DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) - - // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with an ruby Array instead of a Sequence - // due to the member mapping: choices = extended.map {|seq| seq.members} - Complex_Selector_Obj pJustCurrentCompoundSelector = sseqOrOp.selector(); - + // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with a ruby + // Array instead of a Sequence due to the member mapping: choices = extended.map {|seq| seq.members} // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? - if (isOriginal && !pComplexSelector->has_placeholder()) { + if (isOriginal && !selector->has_placeholder()) { ComplexSelectorSet srcset; - srcset.insert(pComplexSelector); - pJustCurrentCompoundSelector->addSources(srcset, ctx); - DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) + srcset.insert(selector); + sseqSel->addSources(srcset); + // DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) } bool isSuperselector = false; - for (NodeDeque::iterator iterator = extended.collection()->begin(), endIterator = extended.collection()->end(); - iterator != endIterator; ++iterator) { - Node& childNode = *iterator; - Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode, ctx); - if (pExtensionSelector->is_superselector_of(pJustCurrentCompoundSelector)) { + // if no complex selector in EXTENDED is a superselector of COMPOUND: + for (Node& childNode : *extended.collection()) { + Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode); + if (pExtensionSelector->is_superselector_of(sseqSel)) { isSuperselector = true; break; } } if (!isSuperselector) { - if (sseqOrOp.got_line_feed) pJustCurrentCompoundSelector->has_line_feed(sseqOrOp.got_line_feed); - extended.collection()->push_front(complexSelectorToNode(pJustCurrentCompoundSelector, ctx)); + // add a complex selector composed only of COMPOUND to EXTENDED + if (sseqOrOp.got_line_feed) sseqSel->has_line_feed(sseqOrOp.got_line_feed); + extended.collection()->push_front(complexSelectorToNode(sseqSel)); } DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) + // add EXTENDED to CHOICES // Aggregate our current extensions - extendedNotExpanded.collection()->push_back(extended); + choices.collection()->push_back(extended); } - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << extendedNotExpanded) + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << choices) // Ruby Equivalent: paths - Node paths = Sass::paths(extendedNotExpanded, ctx); + Node paths = Sass::paths(choices); DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) - - - // Ruby Equivalent: weave + // let WEAVES be an empty list of selector lists Node weaves = Node::createCollection(); - for (NodeDeque::iterator pathsIter = paths.collection()->begin(), pathsEndIter = paths.collection()->end(); pathsIter != pathsEndIter; ++pathsIter) { - Node& path = *pathsIter; - Node weaved = weave(path, ctx); + // for each list of complex selectors PATH in paths(CHOICES): + for (Node& path : *paths.collection()) { + // add weave(PATH) to WEAVES + Node weaved = weave(path); // slow(12%)! weaved.got_line_feed = path.got_line_feed; weaves.collection()->push_back(weaved); } DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) - - // Ruby Equivalent: trim - Node trimmed = trim(weaves, ctx, isReplace); + Node trimmed(trim(weaves, isReplace)); // slow(19%)! DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) - // Ruby Equivalent: flatten - Node extendedSelectors = flatten(trimmed, ctx, 1); + Node flattened(flatten(trimmed, 1)); DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors) - - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) + // memory results in a map table - since extending is very expensive + memoizeComplex.insert(std::pair(selector, flattened)); - return extendedSelectors; + // return trim(WEAVES) + return flattened; } @@ -1875,20 +1866,23 @@ namespace Sass { /* This is the equivalent of ruby's CommaSequence.do_extend. */ - Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething) { - std::set seen; - return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen); - } - - /* - This is the equivalent of ruby's CommaSequence.do_extend. - */ - Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set& seen) { + // We get a selector list with has something to extend and a subset_map with + // all extenders. Pick the ones that match our selectors in the list. + Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen) { Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); - extendedSomething = false; + // check if we already extended this selector + // we can do this since subset_map is "static" + auto memoized = memoizeList.find(pSelectorList); + if (memoized != memoizeList.end()) { + extendedSomething = true; + return memoized->second; + } + extendedSomething = false; + // process each comlplex selector in the selector list. + // Find the ones that can be extended by given subset_map. for (size_t index = 0, length = pSelectorList->length(); index < length; index++) { Complex_Selector_Obj pSelector = (*pSelectorList)[index]; @@ -1897,27 +1891,33 @@ namespace Sass { // run through the extend code (which does a data model transformation), check if there is anything to extend before doing // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps // when debugging). - if (!complexSelectorHasExtension(pSelector, ctx, subset_map, seen)) { + if (!complexSelectorHasExtension(pSelector, seen)) { pNewSelectors->append(pSelector); continue; } + // complexSelectorHasExtension was true! extendedSomething = true; - Node extendedSelectors = extendComplexSelector(pSelector, ctx, subset_map, seen, isReplace, true); + // now do the actual extension of the complex selector + Node extendedSelectors = extendComplexSelector(pSelector, seen, isReplace, true); + if (!pSelector->has_placeholder()) { - if (!extendedSelectors.contains(complexSelectorToNode(pSelector, ctx), true /*simpleSelectorOrderDependent*/)) { + Node nSelector(complexSelectorToNode(pSelector)); + if (!extendedSelectors.contains(nSelector)) { pNewSelectors->append(pSelector); continue; } } - for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorBegin = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) { + bool doReplace = isReplace; + for (Node& childNode : *extendedSelectors.collection()) { // When it is a replace, skip the first one, unless there is only one - if(isReplace && iterator == iteratorBegin && extendedSelectors.collection()->size() > 1 ) continue; - - Node& childNode = *iterator; - pNewSelectors->append(nodeToComplexSelector(childNode, ctx)); + if(doReplace && extendedSelectors.collection()->size() > 1 ) { + doReplace = false; + continue; + } + pNewSelectors->append(nodeToComplexSelector(childNode)); } } @@ -1931,9 +1931,9 @@ namespace Sass { // process tails while (cur) { // process header - if (cur->head() && seen.find(*cur->head()) == seen.end()) { - std::set recseen(seen); - recseen.insert(*cur->head()); + if (cur->head() && seen.find(cur->head()) == seen.end()) { + CompoundSelectorSet recseen(seen); + recseen.insert(cur->head()); // create a copy since we add multiple items if stuff get unwrapped Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); for (Simple_Selector_Obj hs : *cur->head()) { @@ -1945,27 +1945,20 @@ namespace Sass { // this seems inconsistent but it is how ruby sass seems to remove parentheses cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name())); } - // has wrapped selectors - else { + // has wrapped not selectors + else if (ws->name() == ":not") { // extend the inner list of wrapped selector - Selector_List_Obj ext_sl = extendSelectorList(sl, ctx, subset_map, recseen); + bool extended = false; + Selector_List_Obj ext_sl = extendSelectorList(sl, false, extended, recseen); for (size_t i = 0; i < ext_sl->length(); i += 1) { if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { // create clones for wrapped selector and the inner list Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); // remove parent selectors from inner selector - if (ext_cs->first() && ext_cs->first()->head()->length() > 0) { - Wrapped_Selector_Ptr ext_ws = Cast(ext_cs->first()->head()->first()); - if (ext_ws/* && ext_cs->length() == 1*/) { - Selector_List_Obj ws_cs = Cast(ext_ws->selector()); - Compound_Selector_Obj ws_ss = ws_cs->first()->head(); - if (!( - Cast(ws_ss->first()) || - Cast(ws_ss->first()) || - Cast(ws_ss->first()) - )) continue; - } + Compound_Selector_Obj ext_head = NULL; + if (ext_cs->first()) ext_head = ext_cs->first()->head(); + if (ext_head && ext_head && ext_head->length() > 0) { cpy_ws_sl->append(ext_cs->first()); } // assign list to clone @@ -1974,6 +1967,18 @@ namespace Sass { cpy_head->append(cpy_ws); } } + if (eval && extended) { + eval->exp.selector_stack.push_back(pNewSelectors); + cpy_head->perform(eval); + eval->exp.selector_stack.pop_back(); + } + } + // has wrapped selectors + else { + Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); + Selector_List_Obj ext_sl = extendSelectorList(sl, recseen); + cpy_ws->selector(ext_sl); + cpy_head->append(cpy_ws); } } else { cpy_head->append(hs); @@ -1989,6 +1994,10 @@ namespace Sass { cur = cur->tail(); } } + + // memory results in a map table - since extending is very expensive + memoizeList.insert(std::pair(pSelectorList, pNewSelectors)); + return pNewSelectors.detach(); } @@ -2027,24 +2036,30 @@ namespace Sass { // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. - template - static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, Subset_Map& subset_map) { + // Every Ruleset in the whole tree is calling this function. We decide if there + // was is @extend that matches our selector. If we find one, we will go further + // and call the extend magic for our selector. The subset_map contains all blocks + // where @extend was found. Pick the ones that match our selector! + void Extend::extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject) { - DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string(ctx.c_options)) + DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string()) - // Ruby sass seems to filter nodes that don't have any content well before we get here. I'm not sure the repercussions - // of doing so, so for now, let's just not extend things that won't be output later. + // Ruby sass seems to filter nodes that don't have any content well before we get here. + // I'm not sure the repercussions of doing so, so for now, let's just not extend things + // that won't be output later. Profiling shows this may us 0.2% or so. if (!shouldExtendBlock(pObject->block())) { DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT") return; } bool extendedSomething = false; - Selector_List_Obj pNewSelectorList = Extend::extendSelectorList(Cast(pObject->selector()), ctx, subset_map, false, extendedSomething); + + CompoundSelectorSet seen; + Selector_List_Obj pNewSelectorList = extendSelectorList(pObject->selector(), false, extendedSomething, seen); if (extendedSomething && pNewSelectorList) { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << Cast(pObject->selector())->to_string(ctx.c_options)) - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string(ctx.c_options)) + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << pObject->selector()->to_string()) + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string()) pNewSelectorList->remove_parent_selectors(); pObject->selector(pNewSelectorList); } else { @@ -2052,12 +2067,14 @@ namespace Sass { } } - - - Extend::Extend(Context& ctx, Subset_Map& ssm) - : ctx(ctx), subset_map(ssm) + Extend::Extend(Subset_Map& ssm) + : subset_map(ssm), eval(NULL) { } + void Extend::setEval(Eval& e) { + eval = &e; + } + void Extend::operator()(Block_Ptr b) { for (size_t i = 0, L = b->length(); i < L; ++i) { @@ -2074,14 +2091,14 @@ namespace Sass { if (it.first) sel = it.first->first(); if (it.second) ext = it.second; if (ext && (ext->extended() || ext->is_optional())) continue; - std::string str_sel(sel->to_string({ NESTED, 5 })); - std::string str_ext(ext->to_string({ NESTED, 5 })); + std::string str_sel(sel ? sel->to_string({ NESTED, 5 }) : "NULL"); + std::string str_ext(ext ? ext->to_string({ NESTED, 5 }) : "NULL"); // debug_ast(sel, "sel: "); // debug_ast(ext, "ext: "); error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" "The selector \"" + str_ext + "\" was not found.\n" "Use \"@extend " + str_ext + " !optional\" if the" - " extend should be able to fail.", ext->pstate()); + " extend should be able to fail.", (ext ? ext->pstate() : NULL)); } } @@ -2089,7 +2106,7 @@ namespace Sass { void Extend::operator()(Ruleset_Ptr pRuleset) { - extendObjectWithSelectorAndBlock( pRuleset, ctx, subset_map); + extendObjectWithSelectorAndBlock( pRuleset ); pRuleset->block()->perform(this); } diff --git a/src/libsass/src/extend.hpp b/src/libsass/src/extend.hpp old mode 100755 new mode 100644 index a785d1fb7..03042f3e2 --- a/src/libsass/src/extend.hpp +++ b/src/libsass/src/extend.hpp @@ -5,35 +5,70 @@ #include #include "ast.hpp" +#include "node.hpp" +#include "eval.hpp" #include "operation.hpp" #include "subset_map.hpp" +#include "ast_fwd_decl.hpp" namespace Sass { - class Context; - class Node; + Node subweave(Node& one, Node& two); class Extend : public Operation_CRTP { - Context& ctx; Subset_Map& subset_map; + Eval* eval; void fallback_impl(AST_Node_Ptr n) { } + private: + + std::unordered_map< + Selector_List_Obj, // key + Selector_List_Obj, // value + HashNodes, // hasher + CompareNodes // compare + > memoizeList; + + std::unordered_map< + Complex_Selector_Obj, // key + Node, // value + HashNodes, // hasher + CompareNodes // compare + > memoizeComplex; + + /* this turned out to be too much overhead + re-evaluate once we store an ast selector + std::unordered_map< + Compound_Selector_Obj, // key + Node, // value + HashNodes, // hasher + CompareNodes // compare + > memoizeCompound; + */ + + void extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject); + Node extendComplexSelector(Complex_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace, bool isOriginal); + Node extendCompoundSelector(Compound_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace); + bool complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen); + Node trim(Node& seqses, bool isReplace); + Node weave(Node& path); + public: - static Node subweave(Node& one, Node& two, Context& ctx); - static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set& seen); - static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething); - static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace = false) { + void setEval(Eval& eval); + Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen); + Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace = false) { bool extendedSomething = false; - return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething); + CompoundSelectorSet seen; + return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); } - static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, std::set& seen) { + Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, CompoundSelectorSet& seen) { bool isReplace = false; bool extendedSomething = false; - return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen); + return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); } - Extend(Context&, Subset_Map&); + Extend(Subset_Map&); ~Extend() { } void operator()(Block_Ptr); diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp old mode 100755 new mode 100644 index cac5fb61d..aa3c55ec5 --- a/src/libsass/src/file.cpp +++ b/src/libsass/src/file.cpp @@ -1,3 +1,4 @@ +#include "sass.hpp" #ifdef _WIN32 # ifdef __MINGW32__ # ifndef off64_t @@ -9,7 +10,6 @@ #else # include #endif -#include "sass.hpp" #include #include #include @@ -49,15 +49,20 @@ namespace Sass { // return the current directory // always with forward slashes + // always with trailing slash std::string get_cwd() { - const size_t wd_len = 1024; + const size_t wd_len = 4096; #ifndef _WIN32 char wd[wd_len]; - std::string cwd = getcwd(wd, wd_len); + char* pwd = getcwd(wd, wd_len); + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = pwd; #else wchar_t wd[wd_len]; - std::string cwd = wstring_to_string(_wgetcwd(wd, wd_len)); + wchar_t* pwd = _wgetcwd(wd, wd_len); + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = wstring_to_string(pwd); //convert backslashes to forward slashes replace(cwd.begin(), cwd.end(), '\\', '/'); #endif @@ -69,7 +74,10 @@ namespace Sass { bool file_exists(const std::string& path) { #ifdef _WIN32 - std::wstring wpath = UTF_8::convert_to_utf16(path); + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); DWORD dwAttrib = GetFileAttributesW(wpath.c_str()); return (dwAttrib != INVALID_FILE_ATTRIBUTES && (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); @@ -100,7 +108,7 @@ namespace Sass { // helper function to find the last directory seperator inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) { - size_t pos = std::string::npos; + size_t pos; size_t pos_p = path.find_last_of('/', limit); #ifdef _WIN32 size_t pos_w = path.find_last_of('\\', limit); @@ -151,7 +159,7 @@ namespace Sass { pos = 0; // remove all self references inside the path string while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); - pos = 0; // remove all leading and trailing self references + // remove all leading and trailing self references while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); @@ -388,15 +396,19 @@ namespace Sass { BYTE* pBuffer; DWORD dwBytes; // windows unicode filepaths are encoded in utf16 - std::wstring wpath = UTF_8::convert_to_utf16(path); + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return 0; DWORD dwFileLength = GetFileSize(hFile, NULL); if (dwFileLength == INVALID_FILE_SIZE) return 0; // allocate an extra byte for the null char - pBuffer = (BYTE*)malloc((dwFileLength+1)*sizeof(BYTE)); + // and another one for edge-cases in lexer + pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); - pBuffer[dwFileLength] = '\0'; + pBuffer[dwFileLength+0] = '\0'; + pBuffer[dwFileLength+1] = '\0'; CloseHandle(hFile); // just convert from unsigned char* char* contents = (char*) pBuffer; @@ -408,10 +420,12 @@ namespace Sass { if (file.is_open()) { size_t size = file.tellg(); // allocate an extra byte for the null char - contents = (char*) malloc((size+1)*sizeof(char)); + // and another one for edge-cases in lexer + contents = (char*) malloc((size+2)*sizeof(char)); file.seekg(0, std::ios::beg); file.read(contents, size); - contents[size] = '\0'; + contents[size+0] = '\0'; + contents[size+1] = '\0'; file.close(); } #endif diff --git a/src/libsass/src/file.hpp b/src/libsass/src/file.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp old mode 100755 new mode 100644 index d1c648207..8461c9096 --- a/src/libsass/src/functions.cpp +++ b/src/libsass/src/functions.cpp @@ -31,8 +31,26 @@ #endif #define ARG(argname, argtype) get_arg(argname, env, sig, pstate, backtrace) -#define ARGR(argname, argtype, lo, hi) get_arg_r(argname, env, sig, pstate, lo, hi, backtrace) #define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, backtrace, ctx) +#define ARGNR(argname) get_arg_nr(argname, env, sig, pstate, backtrace) + +// return a number object (copied since we want to have reduced units) +#define ARGN(argname) get_arg_n(argname, env, sig, pstate, backtrace) // Number copy + +// special function for weird hsla percent (10px == 10% == 10 != 0.1) +#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, backtrace) // double + +// macros for common ranges (u mean unsigned or upper, r for full range) +#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 1.0) // double +#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 1.0, 1.0) // double +#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 255.0) // double +#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 255.0, 255.0) // double +#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 100.0) // double +#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 100.0, 100.0) // double + +// macros for color related inputs (rbg and alpha/opacity values) +#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, backtrace) // double +#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, backtrace) // double namespace Sass { using std::stringstream; @@ -84,6 +102,8 @@ namespace Sass { namespace Functions { + static Number tmpnr(ParserState("[FN]"), 0); + inline void handle_utf8_error (const ParserState& pstate, Backtrace* backtrace) { try { @@ -135,20 +155,88 @@ namespace Sass { return val; } - Number_Ptr get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, double lo, double hi, Backtrace* backtrace) + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, double lo, double hi) { // Minimal error handling -- the expectation is that built-ins will be written correctly! Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - double v = val->value(); + tmpnr = val; + tmpnr.reduce(); + double v = tmpnr.value(); if (!(lo <= v && v <= hi)) { std::stringstream msg; msg << "argument `" << argname << "` of `" << sig << "` must be between "; msg << lo << " and " << hi; error(msg.str(), pstate, backtrace); } + return v; + } + + const Number& get_arg_nr(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + tmpnr = val; + tmpnr.reduce(); + return tmpnr; + } + + Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + val = SASS_MEMORY_COPY(val); + val->reduce(); return val; } + double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + tmpnr = val; + tmpnr.reduce(); + /* + if (tmpnr.unit() == "%") { + tmpnr.value(tmpnr.value() / 100); + tmpnr.numerators.clear(); + } else { + if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); + } + */ + return tmpnr.value(); + } + + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + tmpnr = val; + tmpnr.reduce(); + return tmpnr.value(); + } + + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + { + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + tmpnr = val; tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 255.0); + } + } + + + inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) { + Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + tmpnr = val; tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value(), 0.0), 100.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 1.0); + } + } + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, backtrace, ctx) template @@ -184,7 +272,9 @@ namespace Sass { std::string exp_src = exp->to_string(ctx.c_options); Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx); if (sel_list->length() == 0) return NULL; - return sel_list->first()->tail()->head(); + Complex_Selector_Obj first = sel_list->first(); + if (!first->tail()) return first->head(); + return first->tail()->head(); } #ifdef __MINGW32__ @@ -220,56 +310,110 @@ namespace Sass { "global-variable-shadowing", "extend-selector-pseudoclass", "at-error", - "units-level-3" + "units-level-3", + "custom-property" }; //////////////// // RGB FUNCTIONS //////////////// - inline double color_num(Number_Ptr n) { - if (n->unit() == "%") { - return std::min(std::max(n->value() * 255 / 100.0, 0.0), 255.0); - } else { - return std::min(std::max(n->value(), 0.0), 255.0); - } - } - - inline double alpha_num(Number_Ptr n) { - if (n->unit() == "%") { - return std::min(std::max(n->value(), 0.0), 100.0); - } else { - return std::min(std::max(n->value(), 0.0), 1.0); + inline bool special_number(String_Constant_Ptr s) { + if (s) { + std::string calc("calc("); + std::string var("var("); + std::string ss(s->value()); + return std::equal(calc.begin(), calc.end(), ss.begin()) || + std::equal(var.begin(), var.end(), ss.begin()); } + return false; } Signature rgb_sig = "rgb($red, $green, $blue)"; BUILT_IN(rgb) { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ")" + ); + } + return SASS_MEMORY_NEW(Color, pstate, - color_num(ARG("$red", Number)), - color_num(ARG("$green", Number)), - color_num(ARG("$blue", Number))); + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue")); } Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; BUILT_IN(rgba_4) { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + return SASS_MEMORY_NEW(Color, pstate, - color_num(ARG("$red", Number)), - color_num(ARG("$green", Number)), - color_num(ARG("$blue", Number)), - alpha_num(ARG("$alpha", Number))); + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue"), + ALPHA_NUM("$alpha")); } Signature rgba_2_sig = "rgba($color, $alpha)"; BUILT_IN(rgba_2) { + if ( + special_number(Cast(env["$color"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$color"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + Color_Ptr c_arg = ARG("$color", Color); + + if ( + special_number(Cast(env["$alpha"])) + ) { + std::stringstream strm; + strm << "rgba(" + << (int)c_arg->r() << ", " + << (int)c_arg->g() << ", " + << (int)c_arg->b() << ", " + << env["$alpha"]->to_string() + << ")"; + return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); + } + Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); - new_c->a(alpha_num(ARG("$alpha", Number))); + new_c->a(ALPHA_NUM("$alpha")); new_c->disp(""); return new_c; } @@ -286,8 +430,8 @@ namespace Sass { BUILT_IN(blue) { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } - Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, Number* weight) { - double p = weight->value()/100; + Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { + double p = weight/100; double w = 2*p - 1; double a = color1->a() - color2->a(); @@ -307,7 +451,7 @@ namespace Sass { { Color_Obj color1 = ARG("$color-1", Color); Color_Obj color2 = ARG("$color-2", Color); - Number_Obj weight = ARGR("$weight", Number, 0, 100); + double weight = DARG_U_PRCT("$weight"); return colormix(ctx, pstate, color1, color2, weight); } @@ -328,7 +472,9 @@ namespace Sass { double min = std::min(r, std::min(g, b)); double delta = max - min; - double h = 0, s = 0, l = (max + min) / 2.0; + double h = 0; + double s; + double l = (max + min) / 2.0; if (max == min) { h = s = 0; // achromatic @@ -395,9 +541,24 @@ namespace Sass { Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; BUILT_IN(hsl) { - return hsla_impl(ARG("$hue", Number)->value(), - ARG("$saturation", Number)->value(), - ARG("$lightness", Number)->value(), + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), 1.0, ctx, pstate); @@ -406,10 +567,28 @@ namespace Sass { Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; BUILT_IN(hsla) { - return hsla_impl(ARG("$hue", Number)->value(), - ARG("$saturation", Number)->value(), - ARG("$lightness", Number)->value(), - ARG("$alpha", Number)->value(), + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + ARGVAL("$alpha"), ctx, pstate); } @@ -448,11 +627,11 @@ namespace Sass { BUILT_IN(adjust_hue) { Color_Ptr rgb_color = ARG("$color", Color); - Number_Ptr degrees = ARG("$degrees", Number); + double degrees = ARGVAL("$degrees"); HSL hsl_color = rgb_to_hsl(rgb_color->r(), rgb_color->g(), rgb_color->b()); - return hsla_impl(hsl_color.h + degrees->value(), + return hsla_impl(hsl_color.h + degrees, hsl_color.s, hsl_color.l, rgb_color->a(), @@ -464,7 +643,7 @@ namespace Sass { BUILT_IN(lighten) { Color_Ptr rgb_color = ARG("$color", Color); - Number_Ptr amount = ARGR("$amount", Number, 0, 100); + double amount = DARG_U_PRCT("$amount"); HSL hsl_color = rgb_to_hsl(rgb_color->r(), rgb_color->g(), rgb_color->b()); @@ -476,7 +655,7 @@ namespace Sass { return hsla_impl(hsl_color.h, hsl_color.s, - hslcolorL + amount->value(), + hslcolorL + amount, rgb_color->a(), ctx, pstate); @@ -486,7 +665,7 @@ namespace Sass { BUILT_IN(darken) { Color_Ptr rgb_color = ARG("$color", Color); - Number_Ptr amount = ARGR("$amount", Number, 0, 100); + double amount = DARG_U_PRCT("$amount"); HSL hsl_color = rgb_to_hsl(rgb_color->r(), rgb_color->g(), rgb_color->b()); @@ -499,7 +678,7 @@ namespace Sass { return hsla_impl(hsl_color.h, hsl_color.s, - hslcolorL - amount->value(), + hslcolorL - amount, rgb_color->a(), ctx, pstate); @@ -509,18 +688,17 @@ namespace Sass { BUILT_IN(saturate) { // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$amount"]); - if (!amount) { + if (!Cast(env["$amount"])) { return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); } - ARGR("$amount", Number, 0, 100); + double amount = DARG_U_PRCT("$amount"); Color_Ptr rgb_color = ARG("$color", Color); HSL hsl_color = rgb_to_hsl(rgb_color->r(), rgb_color->g(), rgb_color->b()); - double hslcolorS = hsl_color.s + amount->value(); + double hslcolorS = hsl_color.s + amount; // Saturation cannot be below 0 or above 100 if (hslcolorS < 0) { @@ -542,12 +720,12 @@ namespace Sass { BUILT_IN(desaturate) { Color_Ptr rgb_color = ARG("$color", Color); - Number_Ptr amount = ARGR("$amount", Number, 0, 100); + double amount = DARG_U_PRCT("$amount"); HSL hsl_color = rgb_to_hsl(rgb_color->r(), rgb_color->g(), rgb_color->b()); - double hslcolorS = hsl_color.s - amount->value(); + double hslcolorS = hsl_color.s - amount; // Saturation cannot be below 0 or above 100 if (hslcolorS <= 0) { @@ -610,7 +788,7 @@ namespace Sass { return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); } - Number_Obj weight = ARGR("$weight", Number, 0, 100); + double weight = DARG_U_PRCT("$weight"); Color_Ptr rgb_color = ARG("$color", Color); Color_Obj inv = SASS_MEMORY_NEW(Color, pstate, @@ -647,7 +825,7 @@ namespace Sass { BUILT_IN(opacify) { Color_Ptr color = ARG("$color", Color); - double amount = ARGR("$amount", Number, 0, 1)->value(); + double amount = DARG_U_FACT("$amount"); double alpha = std::min(color->a() + amount, 1.0); return SASS_MEMORY_NEW(Color, pstate, @@ -662,7 +840,7 @@ namespace Sass { BUILT_IN(transparentize) { Color_Ptr color = ARG("$color", Color); - double amount = ARGR("$amount", Number, 0, 1)->value(); + double amount = DARG_U_FACT("$amount"); double alpha = std::max(color->a() - amount, 0.0); return SASS_MEMORY_NEW(Color, pstate, @@ -695,10 +873,10 @@ namespace Sass { error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate); } if (rgb) { - double rr = r ? ARGR("$red", Number, -255, 255)->value() : 0; - double gg = g ? ARGR("$green", Number, -255, 255)->value() : 0; - double bb = b ? ARGR("$blue", Number, -255, 255)->value() : 0; - double aa = a ? ARGR("$alpha", Number, -1, 1)->value() : 0; + double rr = r ? DARG_R_BYTE("$red") : 0; + double gg = g ? DARG_R_BYTE("$green") : 0; + double bb = b ? DARG_R_BYTE("$blue") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; return SASS_MEMORY_NEW(Color, pstate, color->r() + rr, @@ -708,9 +886,9 @@ namespace Sass { } if (hsl) { HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - double ss = s ? ARGR("$saturation", Number, -100, 100)->value() : 0; - double ll = l ? ARGR("$lightness", Number, -100, 100)->value() : 0; - double aa = a ? ARGR("$alpha", Number, -1, 1)->value() : 0; + double ss = s ? DARG_R_PRCT("$saturation") : 0; + double ll = l ? DARG_R_PRCT("$lightness") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; return hsla_impl(hsl_struct.h + (h ? h->value() : 0), hsl_struct.s + ss, hsl_struct.l + ll, @@ -750,10 +928,10 @@ namespace Sass { error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate); } if (rgb) { - double rscale = (r ? ARGR("$red", Number, -100.0, 100.0)->value() : 0.0) / 100.0; - double gscale = (g ? ARGR("$green", Number, -100.0, 100.0)->value() : 0.0) / 100.0; - double bscale = (b ? ARGR("$blue", Number, -100.0, 100.0)->value() : 0.0) / 100.0; - double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; + double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; + double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; return SASS_MEMORY_NEW(Color, pstate, color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), @@ -762,10 +940,10 @@ namespace Sass { color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); } if (hsl) { - double hscale = (h ? ARGR("$hue", Number, -100.0, 100.0)->value() : 0.0) / 100.0; - double sscale = (s ? ARGR("$saturation", Number, -100.0, 100.0)->value() : 0.0) / 100.0; - double lscale = (l ? ARGR("$lightness", Number, -100.0, 100.0)->value() : 0.0) / 100.0; - double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; + double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; + double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); @@ -774,7 +952,7 @@ namespace Sass { return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); } if (a) { - double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0; + double ascale = (DARG_R_PRCT("$alpha")) / 100.0; return SASS_MEMORY_NEW(Color, pstate, color->r(), @@ -808,21 +986,21 @@ namespace Sass { if (rgb) { return SASS_MEMORY_NEW(Color, pstate, - r ? ARGR("$red", Number, 0, 255)->value() : color->r(), - g ? ARGR("$green", Number, 0, 255)->value() : color->g(), - b ? ARGR("$blue", Number, 0, 255)->value() : color->b(), - a ? ARGR("$alpha", Number, 0, 255)->value() : color->a()); + r ? DARG_U_BYTE("$red") : color->r(), + g ? DARG_U_BYTE("$green") : color->g(), + b ? DARG_U_BYTE("$blue") : color->b(), + a ? DARG_U_BYTE("$alpha") : color->a()); } if (hsl) { HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); if (h) hsl_struct.h = std::fmod(h->value(), 360.0); - if (s) hsl_struct.s = ARGR("$saturation", Number, 0, 100)->value(); - if (l) hsl_struct.l = ARGR("$lightness", Number, 0, 100)->value(); - double alpha = a ? ARGR("$alpha", Number, 0, 1.0)->value() : color->a(); + if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); + if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); + double alpha = a ? DARG_U_FACT("$alpha") : color->a(); return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); } if (a) { - double alpha = a ? ARGR("$alpha", Number, 0, 1.0)->value() : color->a(); + double alpha = DARG_U_FACT("$alpha"); return SASS_MEMORY_NEW(Color, pstate, color->r(), @@ -939,8 +1117,7 @@ namespace Sass { String_Constant_Ptr i = ARG("$insert", String_Constant); std::string ins = i->value(); ins = unquote(ins); - Number_Ptr ind = ARG("$index", Number); - double index = ind->value(); + double index = ARGVAL("$index"); size_t len = UTF_8::code_point_count(str, 0, str.size()); if (index > 0 && index <= len) { @@ -1005,8 +1182,8 @@ namespace Sass { std::string newstr; try { String_Constant_Ptr s = ARG("$string", String_Constant); - double start_at = ARG("$start-at", Number)->value(); - double end_at = ARG("$end-at", Number)->value(); + double start_at = ARGVAL("$start-at"); + double end_at = ARGVAL("$end-at"); String_Quoted_Ptr ss = Cast(s); std::string str = unquote(s->value()); @@ -1100,7 +1277,7 @@ namespace Sass { Signature percentage_sig = "percentage($number)"; BUILT_IN(percentage) { - Number_Ptr n = ARG("$number", Number); + Number_Obj n = ARGN("$number"); if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate); return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); } @@ -1108,41 +1285,37 @@ namespace Sass { Signature round_sig = "round($number)"; BUILT_IN(round) { - Number_Ptr n = ARG("$number", Number); - Number_Ptr r = SASS_MEMORY_COPY(n); - r->pstate(pstate); + Number_Obj r = ARGN("$number"); r->value(Sass::round(r->value(), ctx.c_options.precision)); - return r; + r->pstate(pstate); + return r.detach(); } Signature ceil_sig = "ceil($number)"; BUILT_IN(ceil) { - Number_Ptr n = ARG("$number", Number); - Number_Ptr r = SASS_MEMORY_COPY(n); - r->pstate(pstate); + Number_Obj r = ARGN("$number"); r->value(std::ceil(r->value())); - return r; + r->pstate(pstate); + return r.detach(); } Signature floor_sig = "floor($number)"; BUILT_IN(floor) { - Number_Ptr n = ARG("$number", Number); - Number_Ptr r = SASS_MEMORY_COPY(n); - r->pstate(pstate); + Number_Obj r = ARGN("$number"); r->value(std::floor(r->value())); - return r; + r->pstate(pstate); + return r.detach(); } Signature abs_sig = "abs($number)"; BUILT_IN(abs) { - Number_Ptr n = ARG("$number", Number); - Number_Ptr r = SASS_MEMORY_COPY(n); - r->pstate(pstate); + Number_Obj r = ARGN("$number"); r->value(std::abs(r->value())); - return r; + r->pstate(pstate); + return r.detach(); } Signature min_sig = "min($numbers...)"; @@ -1189,19 +1362,19 @@ namespace Sass { Number_Ptr l = Cast(arg); Boolean_Ptr b = Cast(arg); if (l) { - double v = l->value(); - if (v < 1) { + double lv = l->value(); + if (lv < 1) { stringstream err; - err << "$limit " << v << " must be greater than or equal to 1 for `random'"; + err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; error(err.str(), pstate); } - bool eq_int = std::fabs(trunc(v) - v) < NUMBER_EPSILON; + bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; if (!eq_int) { stringstream err; - err << "Expected $limit to be an integer but got " << v << " for `random'"; + err << "Expected $limit to be an integer but got " << lv << " for `random'"; error(err.str(), pstate); } - std::uniform_real_distribution<> distributor(1, v + 1); + std::uniform_real_distribution<> distributor(1, lv + 1); uint_fast32_t distributed = static_cast(distributor(rand)); return SASS_MEMORY_NEW(Number, pstate, (double)distributed); } @@ -1214,7 +1387,6 @@ namespace Sass { } else { throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number"); } - return 0; } ///////////////// @@ -1251,20 +1423,20 @@ namespace Sass { Signature nth_sig = "nth($list, $n)"; BUILT_IN(nth) { - Number_Ptr n = ARG("$n", Number); + double nr = ARGVAL("$n"); Map_Ptr m = Cast(env["$list"]); if (Selector_List_Ptr sl = Cast(env["$list"])) { size_t len = m ? m->length() : sl->length(); bool empty = m ? m->empty() : sl->empty(); if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); - double index = std::floor(n->value() < 0 ? len + n->value() : n->value() - 1); + double index = std::floor(nr < 0 ? len + nr : nr - 1); if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); // return (*sl)[static_cast(index)]; Listize listize; return (*sl)[static_cast(index)]->perform(&listize); } List_Obj l = Cast(env["$list"]); - if (n->value() == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate); // if the argument isn't a list, then wrap it in a singleton list if (!m && !l) { l = SASS_MEMORY_NEW(List, pstate, 1); @@ -1273,7 +1445,7 @@ namespace Sass { size_t len = m ? m->length() : l->length(); bool empty = m ? m->empty() : l->empty(); if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); - double index = std::floor(n->value() < 0 ? len + n->value() : n->value() - 1); + double index = std::floor(nr < 0 ? len + nr : nr - 1); if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); if (m) { @@ -1301,7 +1473,7 @@ namespace Sass { l->append(ARG("$list", Expression)); } if (m) { - l = m->to_list(ctx, pstate); + l = m->to_list(pstate); } if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); @@ -1324,7 +1496,7 @@ namespace Sass { l->append(ARG("$list", Expression)); } if (m) { - l = m->to_list(ctx, pstate); + l = m->to_list(pstate); } for (size_t i = 0, L = l->length(); i < L; ++i) { if (Eval::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); @@ -1354,11 +1526,11 @@ namespace Sass { l2->append(ARG("$list2", Expression)); } if (m1) { - l1 = m1->to_list(ctx, pstate); + l1 = m1->to_list(pstate); sep_val = SASS_COMMA; } if (m2) { - l2 = m2->to_list(ctx, pstate); + l2 = m2->to_list(pstate); } size_t len = l1->length() + l2->length(); std::string sep_str = unquote(sep->value()); @@ -1392,7 +1564,7 @@ namespace Sass { l->append(ARG("$list", Expression)); } if (m) { - l = m->to_list(ctx, pstate); + l = m->to_list(pstate); } List_Ptr result = SASS_MEMORY_COPY(l); std::string sep_str(unquote(sep->value())); @@ -1425,7 +1597,7 @@ namespace Sass { Map_Obj mith = Cast(arglist->value_at_index(i)); if (!ith) { if (mith) { - ith = mith->to_list(ctx, pstate); + ith = mith->to_list(pstate); } else { ith = SASS_MEMORY_NEW(List, pstate, 1); ith->append(arglist->value_at_index(i)); @@ -1477,7 +1649,9 @@ namespace Sass { Expression_Obj v = ARG("$key", Expression); try { Expression_Obj val = m->at(v); - return val ? val.detach() : SASS_MEMORY_NEW(Null, pstate); + if (!val) return SASS_MEMORY_NEW(Null, pstate); + val->set_delayed(false); + return val.detach(); } catch (const std::out_of_range&) { return SASS_MEMORY_NEW(Null, pstate); } @@ -1575,23 +1749,33 @@ namespace Sass { Signature unit_sig = "unit($number)"; BUILT_IN(unit) - { return SASS_MEMORY_NEW(String_Quoted, pstate, quote(ARG("$number", Number)->unit(), '"')); } + { + Number_Obj arg = ARGN("$number"); + std::string str(quote(arg->unit(), '"')); + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } Signature unitless_sig = "unitless($number)"; BUILT_IN(unitless) - { return SASS_MEMORY_NEW(Boolean, pstate, ARG("$number", Number)->is_unitless()); } + { + Number_Obj arg = ARGN("$number"); + bool unitless = arg->is_unitless(); + return SASS_MEMORY_NEW(Boolean, pstate, unitless); + } Signature comparable_sig = "comparable($number-1, $number-2)"; BUILT_IN(comparable) { - Number_Ptr n1 = ARG("$number-1", Number); - Number_Ptr n2 = ARG("$number-2", Number); + Number_Obj n1 = ARGN("$number-1"); + Number_Obj n2 = ARGN("$number-2"); if (n1->is_unitless() || n2->is_unitless()) { return SASS_MEMORY_NEW(Boolean, pstate, true); } - Number tmp_n2(n2); // copy - tmp_n2.normalize(n1->find_convertible_unit()); - return SASS_MEMORY_NEW(Boolean, pstate, n1->unit() == tmp_n2.unit()); + // normalize into main units + n1->normalize(); n2->normalize(); + Units &lhs_unit = *n1, &rhs_unit = *n2; + bool is_comparable = (lhs_unit == rhs_unit); + return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); } Signature variable_exists_sig = "variable-exists($name)"; @@ -1623,9 +1807,14 @@ namespace Sass { Signature function_exists_sig = "function-exists($name)"; BUILT_IN(function_exists) { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, backtrace); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); - if(d_env.has_global(s+"[f]")) { + if(d_env.has_global(name+"[f]")) { return SASS_MEMORY_NEW(Boolean, pstate, true); } else { @@ -1662,7 +1851,20 @@ namespace Sass { Signature call_sig = "call($name, $args...)"; BUILT_IN(call) { - std::string name = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + std::string name; + Function_Ptr ff = Cast(env["$name"]); + String_Constant_Ptr ss = Cast(env["$name"]); + + if (ss) { + name = Util::normalize_underscores(unquote(ss->value())); + std::cerr << "DEPRECATION WARNING: "; + std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; + std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl; + std::cerr << std::endl; + } else if (ff) { + name = ff->name(); + } + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); @@ -1693,8 +1895,8 @@ namespace Sass { Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); Expand expand(ctx, &d_env, backtrace, &selector_stack); func->via_call(true); // calc invoke is allowed + if (ff) func->func(ff); return func->perform(&expand.eval); - } //////////////////// @@ -1715,10 +1917,10 @@ namespace Sass { Expand expand(ctx, &d_env, backtrace, &selector_stack); Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); bool is_true = !cond->is_false(); - Expression_Ptr res = ARG(is_true ? "$if-true" : "$if-false", Expression); + Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); res = res->perform(&expand.eval); res->set_delayed(false); // clone? - return res; + return res.detach(); } ////////////////////////// @@ -1793,7 +1995,7 @@ namespace Sass { Selector_List_Obj child = *itr; std::vector exploded; selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(ctx, selector_stack); + Selector_List_Obj rv = child->resolve_parent_refs(selector_stack); selector_stack.pop_back(); for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { exploded.push_back((*rv)[m]); @@ -1905,7 +2107,7 @@ namespace Sass { Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); - Selector_List_Obj result = selector1->unify_with(selector2, ctx); + Selector_List_Obj result = selector1->unify_with(selector2); Listize listize; return result->perform(&listize); } @@ -1935,9 +2137,10 @@ namespace Sass { Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); Subset_Map subset_map; - extender->populate_extends(extendee, ctx, subset_map); + extender->populate_extends(extendee, subset_map); + Extend extend(subset_map); - Selector_List_Obj result = Extend::extendSelectorList(selector, ctx, subset_map, false); + Selector_List_Obj result = extend.extendSelectorList(selector, false); Listize listize; return result->perform(&listize); @@ -1950,9 +2153,10 @@ namespace Sass { Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); Subset_Map subset_map; - replacement->populate_extends(original, ctx, subset_map); + replacement->populate_extends(original, subset_map); + Extend extend(subset_map); - Selector_List_Obj result = Extend::extendSelectorList(selector, ctx, subset_map, true); + Selector_List_Obj result = extend.extendSelectorList(selector, true); Listize listize; return result->perform(&listize); @@ -1993,5 +2197,45 @@ namespace Sass { List_Obj list = Cast(value); return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); } + + Signature content_exists_sig = "content-exists()"; + BUILT_IN(content_exists) + { + if (!d_env.has_global("is_in_mixin")) { + error("Cannot call content-exists() except within a mixin.", pstate, backtrace); + } + return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); + } + + Signature get_function_sig = "get-function($name, $css: false)"; + BUILT_IN(get_function) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, backtrace); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + std::string full_name = name + "[f]"; + + Boolean_Obj css = ARG("$css", Boolean); + if (!css->is_false()) { + Definition_Ptr def = SASS_MEMORY_NEW(Definition, + pstate, + name, + SASS_MEMORY_NEW(Parameters, pstate), + SASS_MEMORY_NEW(Block, pstate, 0, false), + Definition::FUNCTION); + return SASS_MEMORY_NEW(Function, pstate, def, true); + } + + + if (!d_env.has_global(full_name)) { + error("Function not found: " + name, pstate, backtrace); + } + + Definition_Ptr def = Cast(d_env[full_name]); + return SASS_MEMORY_NEW(Function, pstate, def, false); + } } } diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp old mode 100755 new mode 100644 index f2cc0af50..131160d4c --- a/src/libsass/src/functions.hpp +++ b/src/libsass/src/functions.hpp @@ -106,6 +106,8 @@ namespace Sass { extern Signature simple_selectors_sig; extern Signature selector_parse_sig; extern Signature is_bracketed_sig; + extern Signature content_exists_sig; + extern Signature get_function_sig; BUILT_IN(rgb); BUILT_IN(rgba_4); @@ -188,6 +190,8 @@ namespace Sass { BUILT_IN(simple_selectors); BUILT_IN(selector_parse); BUILT_IN(is_bracketed); + BUILT_IN(content_exists); + BUILT_IN(get_function); } } diff --git a/src/libsass/src/inspect.cpp b/src/libsass/src/inspect.cpp old mode 100755 new mode 100644 index 4ace72484..273ff8fb1 --- a/src/libsass/src/inspect.cpp +++ b/src/libsass/src/inspect.cpp @@ -15,7 +15,7 @@ namespace Sass { - Inspect::Inspect(Emitter emi) + Inspect::Inspect(const Emitter& emi) : Emitter(emi) { } Inspect::~Inspect() { } @@ -42,7 +42,9 @@ namespace Sass { void Inspect::operator()(Ruleset_Ptr ruleset) { if (ruleset->selector()) { + opt.in_selector = true; ruleset->selector()->perform(this); + opt.in_selector = false; } if (ruleset->block()) { ruleset->block()->perform(this); @@ -90,7 +92,7 @@ namespace Sass { append_token("@at-root ", at_root_block); append_mandatory_space(); if(at_root_block->expression()) at_root_block->expression()->perform(this); - at_root_block->block()->perform(this); + if(at_root_block->block()) at_root_block->block()->perform(this); } void Inspect::operator()(Directive_Ptr at_rule) @@ -121,6 +123,8 @@ namespace Sass { if (dec->value()->concrete_type() == Expression::NULL_VAL) return; bool was_decl = in_declaration; in_declaration = true; + LOCAL_FLAG(in_custom_property, dec->is_custom_property()); + if (output_style() == NESTED) indentation += dec->tabs(); append_indentation(); @@ -354,6 +358,8 @@ namespace Sass { if (items_output) append_comma_separator(); key->perform(this); append_colon_separator(); + LOCAL_FLAG(in_space_array, true); + LOCAL_FLAG(in_comma_array, true); map->at(key)->perform(this); items_output = true; } @@ -495,8 +501,9 @@ namespace Sass { void Inspect::operator()(Unary_Expression_Ptr expr) { - if (expr->optype() == Unary_Expression::PLUS) append_string("+"); - else append_string("-"); + if (expr->optype() == Unary_Expression::PLUS) append_string("+"); + else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); + else append_string("-"); expr->operand()->perform(this); } @@ -517,16 +524,14 @@ namespace Sass { append_token(var->name(), var); } - void Inspect::operator()(Textual_Ptr txt) - { - append_token(txt->value(), txt); - } - void Inspect::operator()(Number_Ptr n) { std::string res; + // reduce units + n->reduce(); + // check if the fractional part of the value equals to zero // neat trick from http://stackoverflow.com/a/1521682/1550314 // double int_part; bool is_int = modf(value, &int_part) == 0.0; @@ -624,6 +629,11 @@ namespace Sass { // maybe an unknown token std::string name = c->disp(); + if (opt.in_selector && name != "") { + append_token(name, c); + return; + } + // resolved color std::string res_name = name; @@ -672,7 +682,7 @@ namespace Sass { ss << name; } else if (r == 0 && g == 0 && b == 0 && a == 0) { - ss << "transparent"; + ss << "transparent"; } else if (a >= 1) { if (res_name != "") { @@ -822,12 +832,22 @@ namespace Sass { void Inspect::operator()(At_Root_Query_Ptr ae) { - append_string("("); - ae->feature()->perform(this); - if (ae->value()) { - append_colon_separator(); - ae->value()->perform(this); + if (ae->feature()) { + append_string("("); + ae->feature()->perform(this); + if (ae->value()) { + append_colon_separator(); + ae->value()->perform(this); + } + append_string(")"); } + } + + void Inspect::operator()(Function_Ptr f) + { + append_token("get-function", f); + append_string("("); + append_string(quote(f->name())); append_string(")"); } @@ -901,7 +921,9 @@ namespace Sass { void Inspect::operator()(Selector_Schema_Ptr s) { + opt.in_selector = true; s->contents()->perform(this); + opt.in_selector = false; } void Inspect::operator()(Parent_Selector_Ptr p) @@ -948,6 +970,10 @@ namespace Sass { } } add_close_mapping(s); + if (s->modifier() != 0) { + append_mandatory_space(); + append_char(s->modifier()); + } append_string("]"); } @@ -963,16 +989,20 @@ namespace Sass { void Inspect::operator()(Wrapped_Selector_Ptr s) { - bool was = in_wrapped; - in_wrapped = true; - append_token(s->name(), s); - append_string("("); - bool was_comma_array = in_comma_array; - in_comma_array = false; - s->selector()->perform(this); - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; + if (s->name() == " ") { + append_string(""); + } else { + bool was = in_wrapped; + in_wrapped = true; + append_token(s->name(), s); + append_string("("); + bool was_comma_array = in_comma_array; + in_comma_array = false; + s->selector()->perform(this); + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; + } } void Inspect::operator()(Compound_Selector_Ptr s) @@ -1038,6 +1068,7 @@ namespace Sass { if (tail) append_mandatory_space(); else append_optional_space(); break; + default: break; } if (tail && comb != Complex_Selector::ANCESTOR_OF) { if (c->has_line_break()) append_optional_linefeed(); diff --git a/src/libsass/src/inspect.hpp b/src/libsass/src/inspect.hpp old mode 100755 new mode 100644 index 59805a156..c36790b80 --- a/src/libsass/src/inspect.hpp +++ b/src/libsass/src/inspect.hpp @@ -17,7 +17,7 @@ namespace Sass { public: - Inspect(Emitter emi); + Inspect(const Emitter& emi); virtual ~Inspect(); // statements @@ -48,6 +48,7 @@ namespace Sass { virtual void operator()(Content_Ptr); // expressions virtual void operator()(Map_Ptr); + virtual void operator()(Function_Ptr); virtual void operator()(List_Ptr); virtual void operator()(Binary_Expression_Ptr); virtual void operator()(Unary_Expression_Ptr); @@ -56,7 +57,6 @@ namespace Sass { // virtual void operator()(Custom_Warning_Ptr); // virtual void operator()(Custom_Error_Ptr); virtual void operator()(Variable_Ptr); - virtual void operator()(Textual_Ptr); virtual void operator()(Number_Ptr); virtual void operator()(Color_Ptr); virtual void operator()(Boolean_Ptr); diff --git a/src/libsass/src/json.cpp b/src/libsass/src/json.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/json.hpp b/src/libsass/src/json.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/kwd_arg_macros.hpp b/src/libsass/src/kwd_arg_macros.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/lexer.cpp b/src/libsass/src/lexer.cpp old mode 100755 new mode 100644 index 781257e2e..be7f67713 --- a/src/libsass/src/lexer.cpp +++ b/src/libsass/src/lexer.cpp @@ -49,6 +49,12 @@ namespace Sass { return unsigned(chr - '0') <= '9' - '0'; } + bool is_number(const char& chr) + { + // adapted the technique from is_alpha + return is_digit(chr) || chr == '-' || chr == '+'; + } + bool is_xdigit(const char& chr) { // adapted the technique from is_alpha @@ -79,10 +85,11 @@ namespace Sass { // but with specific ranges (copied from Ruby Sass) bool is_nonascii(const char& chr) { + unsigned int cmp = unsigned(chr); return ( - (unsigned(chr) >= 128 && unsigned(chr) <= 15572911) || - (unsigned(chr) >= 15630464 && unsigned(chr) <= 15712189) || - (unsigned(chr) >= 4036001920) + (cmp >= 128 && cmp <= 15572911) || + (cmp >= 15630464 && cmp <= 15712189) || + (cmp >= 4036001920) ); } @@ -90,15 +97,17 @@ namespace Sass { // valid in a uri (copied from Ruby Sass) bool is_uri_character(const char& chr) { - return (unsigned(chr) > 41 && unsigned(chr) < 127) || - unsigned(chr) == ':' || unsigned(chr) == '/'; + unsigned int cmp = unsigned(chr); + return (cmp > 41 && cmp < 127) || + cmp == ':' || cmp == '/'; } // check if char is within a reduced ascii range // valid for escaping (copied from Ruby Sass) bool is_escapable_character(const char& chr) { - return unsigned(chr) > 31 && unsigned(chr) < 127; + unsigned int cmp = unsigned(chr); + return cmp > 31 && cmp < 127; } // Match word character (look ahead) diff --git a/src/libsass/src/lexer.hpp b/src/libsass/src/lexer.hpp old mode 100755 new mode 100644 index 5356c4f9e..5838c291c --- a/src/libsass/src/lexer.hpp +++ b/src/libsass/src/lexer.hpp @@ -29,6 +29,7 @@ namespace Sass { bool is_alpha(const char& src); bool is_punct(const char& src); bool is_digit(const char& src); + bool is_number(const char& src); bool is_alnum(const char& src); bool is_xdigit(const char& src); bool is_unicode(const char& src); @@ -96,9 +97,9 @@ namespace Sass { // Regex equivalent: /(?:literal)/ template const char* exactly(const char* src) { - if (str == 0) return 0; + if (str == NULL) return 0; const char* pre = str; - if (src == 0) return 0; + if (src == NULL) return 0; // there is a small chance that the search string // is longer than the rest of the string to look at while (*pre && *src == *pre) { @@ -109,14 +110,22 @@ namespace Sass { } + // Match a single character literal. + // Regex equivalent: /(?:x)/i + // only define lower case alpha chars + template + const char* insensitive(const char* src) { + return *src == chr || *src+32 == chr ? src + 1 : 0; + } + // Match the full string literal. // Regex equivalent: /(?:literal)/i // only define lower case alpha chars template const char* insensitive(const char* src) { - if (str == 0) return 0; + if (str == NULL) return 0; const char* pre = str; - if (src == 0) return 0; + if (src == NULL) return 0; // there is a small chance that the search string // is longer than the rest of the string to look at while (*pre && (*src == *pre || *src+32 == *pre)) { diff --git a/src/libsass/src/listize.cpp b/src/libsass/src/listize.cpp old mode 100755 new mode 100644 index 88329ba1b..cb921ae67 --- a/src/libsass/src/listize.cpp +++ b/src/libsass/src/listize.cpp @@ -64,6 +64,7 @@ namespace Sass { break; case Complex_Selector::ANCESTOR_OF: break; + default: break; } Complex_Selector_Obj tail = sel->tail(); diff --git a/src/libsass/src/listize.hpp b/src/libsass/src/listize.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/mapping.hpp b/src/libsass/src/mapping.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/memory/SharedPtr.cpp b/src/libsass/src/memory/SharedPtr.cpp old mode 100755 new mode 100644 index e6d44dab1..2530360a5 --- a/src/libsass/src/memory/SharedPtr.cpp +++ b/src/libsass/src/memory/SharedPtr.cpp @@ -17,8 +17,8 @@ namespace Sass { std::cerr << "###################################\n"; std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; std::cerr << "###################################\n"; - for (auto var : all) { - if (AST_Node_Ptr ast = Cast(var)) { + for (SharedObj* var : all) { + if (AST_Node_Ptr ast = dynamic_cast(var)) { debug_ast(ast); } else { std::cerr << "LEAKED " << var << "\n"; @@ -37,22 +37,20 @@ namespace Sass { , dbg(false) #endif { - refcounter = 0; - #ifdef DEBUG_SHARED_PTR - if (taint) all.push_back(this); - #endif - }; - - SharedObj::~SharedObj() { - #ifdef DEBUG_SHARED_PTR - if (dbg) std::cerr << "Destruct " << this << "\n"; - if(!all.empty()) { // check needed for MSVC (no clue why?) - all.erase(std::remove(all.begin(), all.end(), this), all.end()); - } - #endif - }; - + refcounter = 0; + #ifdef DEBUG_SHARED_PTR + if (taint) all.push_back(this); + #endif + }; + SharedObj::~SharedObj() { + #ifdef DEBUG_SHARED_PTR + if (dbg) std::cerr << "Destruct " << this << "\n"; + if(!all.empty()) { // check needed for MSVC (no clue why?) + all.erase(std::remove(all.begin(), all.end(), this), all.end()); + } + #endif + }; void SharedPtr::decRefCount() { if (node) { @@ -62,7 +60,7 @@ namespace Sass { #endif if (node->refcounter == 0) { #ifdef DEBUG_SHARED_PTR - AST_Node_Ptr ptr = Cast(node); + // AST_Node_Ptr ast = dynamic_cast(node); if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; #endif if (!node->detached) { diff --git a/src/libsass/src/memory/SharedPtr.hpp b/src/libsass/src/memory/SharedPtr.hpp old mode 100755 new mode 100644 index 2df55bc24..f20dfa39b --- a/src/libsass/src/memory/SharedPtr.hpp +++ b/src/libsass/src/memory/SharedPtr.hpp @@ -17,7 +17,7 @@ namespace Sass { #ifdef DEBUG_SHARED_PTR #define SASS_MEMORY_NEW(Class, ...) \ - ((new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ #define SASS_MEMORY_COPY(obj) \ ((obj)->copy(__FILE__, __LINE__)) \ @@ -86,9 +86,9 @@ namespace Sass { class SharedPtr { - private: + protected: SharedObj* node; - private: + protected: void decRefCount(); void incRefCount(); public: @@ -97,16 +97,16 @@ namespace Sass { : node(NULL) {}; // the create constructor SharedPtr(SharedObj* ptr); - // copy assignment operator - SharedPtr& operator=(const SharedPtr& rhs); - // move assignment operator - /* SharedPtr& operator=(SharedPtr&& rhs); */ // the copy constructor SharedPtr(const SharedPtr& obj); // the move constructor - /* SharedPtr(SharedPtr&& obj); */ - // destructor - ~SharedPtr(); + SharedPtr(SharedPtr&& obj); + // copy assignment operator + SharedPtr& operator=(const SharedPtr& obj); + // move assignment operator + SharedPtr& operator=(SharedPtr&& obj); + // pure virtual destructor + virtual ~SharedPtr() = 0; public: SharedObj* obj () const { return node; @@ -146,6 +146,29 @@ namespace Sass { : SharedPtr(node) {}; SharedImpl(const T& node) : SharedPtr(node) {}; + // the copy constructor + SharedImpl(const SharedImpl& impl) + : SharedPtr(impl.node) {}; + // the move constructor + SharedImpl(SharedImpl&& impl) + : SharedPtr(impl.node) {}; + // copy assignment operator + SharedImpl& operator=(const SharedImpl& rhs) { + if (node) decRefCount(); + node = rhs.node; + incRefCount(); + return *this; + } + // move assignment operator + SharedImpl& operator=(SharedImpl&& rhs) { + // don't move our self + if (this != &rhs) { + if (node) decRefCount(); + node = std::move(rhs.node); + rhs.node = NULL; + } + return *this; + } ~SharedImpl() {}; public: operator T*() const { diff --git a/src/libsass/src/node.cpp b/src/libsass/src/node.cpp old mode 100755 new mode 100644 index 1cea923a8..08eada733 --- a/src/libsass/src/node.cpp +++ b/src/libsass/src/node.cpp @@ -14,15 +14,15 @@ namespace Sass { } - Node Node::createSelector(Complex_Selector_Ptr pSelector, Context& ctx) { + Node Node::createSelector(const Complex_Selector& pSelector) { NodeDequePtr null; - Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(pSelector); + Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(&pSelector); pStripped->tail(NULL); pStripped->combinator(Complex_Selector::ANCESTOR_OF); Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/); - if (pSelector) n.got_line_feed = pSelector->has_line_feed(); + n.got_line_feed = pSelector.has_line_feed(); return n; } @@ -50,12 +50,12 @@ namespace Sass { { if (pSelector) got_line_feed = pSelector->has_line_feed(); } - Node Node::klone(Context& ctx) const { + Node Node::klone() const { NodeDequePtr pNewCollection = std::make_shared(); if (mpCollection) { for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { Node& toClone = *iter; - pNewCollection->push_back(toClone.klone(ctx)); + pNewCollection->push_back(toClone.klone()); } } @@ -65,7 +65,7 @@ namespace Sass { } - bool Node::contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const { + bool Node::contains(const Node& potentialChild) const { bool found = false; for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { @@ -172,7 +172,7 @@ namespace Sass { #endif - Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx) { + Node complexSelectorToNode(Complex_Selector_Ptr pToConvert) { if (pToConvert == NULL) { return Node::createNil(); } @@ -191,13 +191,15 @@ namespace Sass { bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); - if (pToConvert->head() || empty_parent_ref) { - } - // the first Complex_Selector may contain a dummy head pointer, skip it. if (pToConvert->head() && !empty_parent_ref) { - node.collection()->push_back(Node::createSelector(pToConvert, ctx)); + node.collection()->push_back(Node::createSelector(*pToConvert)); if (has_lf) node.collection()->back().got_line_feed = has_lf; + if (pToConvert->head() || empty_parent_ref) { + if (pToConvert->tail()) { + pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); + } + } has_lf = false; } @@ -218,7 +220,7 @@ namespace Sass { } - Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx) { + Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert) { if (toConvert.isNil()) { return NULL; } @@ -232,7 +234,6 @@ namespace Sass { NodeDeque& childNodes = *toConvert.collection(); std::string noPath(""); - Position noPosition(-1, -1, -1); Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL); Complex_Selector_Obj pCurrent = pFirst; @@ -280,7 +281,7 @@ namespace Sass { // A very naive trim function, which removes duplicates in a node // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs - Node Node::naiveTrim(Node& seqses, Context& ctx) { + Node Node::naiveTrim(Node& seqses) { std::vector res; std::vector known; diff --git a/src/libsass/src/node.hpp b/src/libsass/src/node.hpp old mode 100755 new mode 100644 index 969d5dfee..23ba360c3 --- a/src/libsass/src/node.hpp +++ b/src/libsass/src/node.hpp @@ -61,15 +61,15 @@ namespace Sass { static Node createCombinator(const Complex_Selector::Combinator& combinator); // This method will klone the selector, stripping off the tail and combinator - static Node createSelector(Complex_Selector_Ptr pSelector, Context& ctx); + static Node createSelector(const Complex_Selector& pSelector); static Node createCollection(); static Node createCollection(const NodeDeque& values); static Node createNil(); - static Node naiveTrim(Node& seqses, Context& ctx); + static Node naiveTrim(Node& seqses); - Node klone(Context& ctx) const; + Node klone() const; bool operator==(const Node& rhs) const; inline bool operator!=(const Node& rhs) const { return !(*this == rhs); } @@ -91,7 +91,7 @@ namespace Sass { // potentialChild must be a node collection of selectors/combinators. this must be a collection // of collections of nodes/combinators. This method checks if potentialChild is a child of this // Node. - bool contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const; + bool contains(const Node& potentialChild) const; private: // Private constructor; Use the static methods (like createCombinator and createSelector) @@ -110,8 +110,8 @@ namespace Sass { #ifdef DEBUG std::ostream& operator<<(std::ostream& os, const Node& node); #endif - Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx); - Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx); + Node complexSelectorToNode(Complex_Selector_Ptr pToConvert); + Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert); } diff --git a/src/libsass/src/operation.hpp b/src/libsass/src/operation.hpp old mode 100755 new mode 100644 index fbb98bf57..2d4fbec9d --- a/src/libsass/src/operation.hpp +++ b/src/libsass/src/operation.hpp @@ -40,6 +40,7 @@ namespace Sass { // expressions virtual T operator()(List_Ptr x) = 0; virtual T operator()(Map_Ptr x) = 0; + virtual T operator()(Function_Ptr x) = 0; virtual T operator()(Binary_Expression_Ptr x) = 0; virtual T operator()(Unary_Expression_Ptr x) = 0; virtual T operator()(Function_Call_Ptr x) = 0; @@ -47,7 +48,6 @@ namespace Sass { virtual T operator()(Custom_Warning_Ptr x) = 0; virtual T operator()(Custom_Error_Ptr x) = 0; virtual T operator()(Variable_Ptr x) = 0; - virtual T operator()(Textual_Ptr x) = 0; virtual T operator()(Number_Ptr x) = 0; virtual T operator()(Color_Ptr x) = 0; virtual T operator()(Boolean_Ptr x) = 0; @@ -122,6 +122,7 @@ namespace Sass { // expressions T operator()(List_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Map_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Function_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Binary_Expression_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Unary_Expression_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Function_Call_Ptr x) { return static_cast(this)->fallback(x); } @@ -129,7 +130,6 @@ namespace Sass { T operator()(Custom_Warning_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Custom_Error_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Variable_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Textual_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Number_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Color_Ptr x) { return static_cast(this)->fallback(x); } T operator()(Boolean_Ptr x) { return static_cast(this)->fallback(x); } diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp old mode 100755 new mode 100644 index 0ba43332e..cc847ee08 --- a/src/libsass/src/output.cpp +++ b/src/libsass/src/output.cpp @@ -134,6 +134,7 @@ namespace Sass { append_string(ss.str()); append_optional_linefeed(); } + scheduled_crutch = s; if (s) s->perform(this); append_scope_opener(b); for (size_t i = 0, L = b->length(); i < L; ++i) { @@ -324,7 +325,7 @@ namespace Sass { if (s->can_compress_whitespace() && output_style() == COMPRESSED) { value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); } - if (!in_comment) { + if (!in_comment && !in_custom_property) { append_token(string_to_output(value), s); } else { append_token(value, s); diff --git a/src/libsass/src/output.hpp b/src/libsass/src/output.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp old mode 100755 new mode 100644 index d266484ce..71858de10 --- a/src/libsass/src/parser.cpp +++ b/src/libsass/src/parser.cpp @@ -99,6 +99,15 @@ namespace Sass { // consume unicode BOM read_bom(); + // scan the input to find invalid utf8 sequences + const char* it = utf8::find_invalid(position, end); + + // report invalid utf8 + if (it != end) { + pstate += Offset::init(position, it); + throw Exception::InvalidSass(pstate, "Invalid UTF-8 sequence"); + } + // create a block AST node to hold children Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); @@ -261,8 +270,13 @@ namespace Sass { } // selector may contain interpolations which need delayed evaluation - else if (!(lookahead_result = lookahead_for_selector(position)).error) - { block->append(parse_ruleset(lookahead_result)); } + else if ( + !(lookahead_result = lookahead_for_selector(position)).error && + !lookahead_result.is_custom_property + ) + { + block->append(parse_ruleset(lookahead_result)); + } // parse multiple specific keyword directives else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } @@ -281,7 +295,7 @@ namespace Sass { else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } else if (lex< at_keyword >(true)) { block->append(parse_directive()); } - else if (is_root /* && block->is_root() */) { + else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { lex< css_whitespace >(); if (position >= end) return true; css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); @@ -325,15 +339,15 @@ namespace Sass { Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); if (lex< quoted_string >()) { - Expression_Obj the_url = parse_string(); - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), the_url)); + Expression_Obj quoted_url = parse_string(); + args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); } - else if (String_Obj the_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), the_url)); + else if (String_Obj string_url = parse_url_function_argument()) { + args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); } else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { - Expression_Obj the_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), the_url)); + Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); + args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); } else { error("malformed URL", pstate); @@ -385,22 +399,27 @@ namespace Sass { Parameters_Obj Parser::parse_parameters() { - std::string name(lexed); - Position position = after_token; Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); if (lex_css< exactly<'('> >()) { // if there's anything there at all if (!peek_css< exactly<')'> >()) { - do params->append(parse_parameter()); - while (lex_css< exactly<','> >()); + do { + if (peek< exactly<')'> >()) break; + params->append(parse_parameter()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); } - if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); } return params; } Parameter_Obj Parser::parse_parameter() { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); + } while (lex< alternatives < spaces, block_comment > >()); lex < variable >(); std::string name(Util::normalize_underscores(lexed)); @@ -420,22 +439,27 @@ namespace Sass { Arguments_Obj Parser::parse_arguments() { - std::string name(lexed); - Position position = after_token; Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); if (lex_css< exactly<'('> >()) { // if there's anything there at all if (!peek_css< exactly<')'> >()) { - do args->append(parse_argument()); - while (lex_css< exactly<','> >()); + do { + if (peek< exactly<')'> >()) break; + args->append(parse_argument()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); } return args; } Argument_Obj Parser::parse_argument() { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { position += 2; css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); @@ -493,6 +517,7 @@ namespace Sass { // a ruleset connects a selector and a block Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) { + NESTING_GUARD(nestings); // inherit is_root from parent block Block_Obj parent = block_stack.back(); bool is_root = parent && parent->is_root(); @@ -504,7 +529,7 @@ namespace Sass { if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); else { Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); - list->schema(parse_selector_schema(lookahead.found, false)); + list->schema(parse_selector_schema(lookahead.position, false)); ruleset->selector(list); } // then parse the inner block @@ -525,13 +550,14 @@ namespace Sass { // in the eval stage we will be re-parse it into an actual selector Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) { + NESTING_GUARD(nestings); // move up to the start lex< optional_spaces >(); const char* i = position; // selector schema re-uses string schema implementation String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); // the selector schema is pretty much just a wrapper for the string schema - Selector_Schema_Ptr selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); selector_schema->connect_parent(chroot == false); selector_schema->media_block(last_media_block); @@ -548,12 +574,13 @@ namespace Sass { schema->append(str); } - // check if the interpolation only contains white-space (error out) - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } // skip over all nested inner interpolations up to our own delimiter const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); + // check if the interpolation never ends of only contains white-space (error out) + if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { + position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } // pass inner expression to the parser to resolve nested interpolations pstate.add(p, p+2); Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list(); @@ -594,7 +621,7 @@ namespace Sass { after_token = before_token = pstate; // return parsed result - return selector_schema; + return selector_schema.detach(); } // EO parse_selector_schema @@ -633,13 +660,14 @@ namespace Sass { // this is the main entry point for most Selector_List_Obj Parser::parse_selector_list(bool chroot) { - bool reloop = true; + bool reloop; bool had_linefeed = false; + NESTING_GUARD(nestings); Complex_Selector_Obj sel; Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); group->media_block(last_media_block); - if (peek_css< alternatives < end_of_file, exactly <'{'> > >()) { + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { css_error("Invalid CSS", " after ", ": expected selector, was "); } @@ -689,7 +717,8 @@ namespace Sass { Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) { - String_Ptr reference = 0; + NESTING_GUARD(nestings); + String_Obj reference = 0; lex < block_comment >(); advanceToNextToken(); Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); @@ -841,9 +870,6 @@ namespace Sass { else if (lex< id_name >()) { return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); } - else if (lex< quoted_string >()) { - return SASS_MEMORY_NEW(Element_Selector, pstate, unquote(lexed)); - } else if (lex< alternatives < variable, number, static_reference_combinator > >()) { return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); } @@ -864,6 +890,9 @@ namespace Sass { sel->media_block(last_media_block); return sel; } + else { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } // failed return 0; } @@ -911,8 +940,8 @@ namespace Sass { >() ) { lex_css< alternatives < static_value, binomial > >(); - String_Constant_Ptr expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (expr && lex_css< exactly<')'> >()) { + String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (lex_css< exactly<')'> >()) { expr->can_compress_whitespace(true); return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); } @@ -939,12 +968,28 @@ namespace Sass { return 0; } + const char* Parser::re_attr_sensitive_close(const char* src) + { + return alternatives < exactly<']'>, exactly<'/'> >(src); + } + + const char* Parser::re_attr_insensitive_close(const char* src) + { + return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + } + Attribute_Selector_Obj Parser::parse_attribute_selector() { ParserState p = pstate; if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector", pstate); std::string name(lexed); - if (lex_css< alternatives < exactly<']'>, exactly<'/'> > >()) return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); + } if (!lex_css< alternatives< exact_match, class_match, dash_match, prefix_match, suffix_match, substring_match > >()) { error("invalid operator in attribute selector for " + name, pstate); @@ -962,8 +1007,15 @@ namespace Sass { error("expected a string constant or identifier in attribute selector for " + name, pstate); } - if (!lex_css< alternatives < exactly<']'>, exactly<'/'> > >()) error("unterminated attribute selector for " + name, pstate); - return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); + } + error("unterminated attribute selector for " + name, pstate); + return NULL; // to satisfy compilers (error must not return) } /* parse block comment and add to block */ @@ -981,10 +1033,15 @@ namespace Sass { Declaration_Obj Parser::parse_declaration() { String_Obj prop; + bool is_custom_property = false; if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; prop = parse_identifier_schema(); } else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); } else { @@ -993,9 +1050,12 @@ namespace Sass { bool is_indented = true; const std::string property(lexed); if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + property + "\" must be followed by a ':'", pstate); + if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value", pstate); + if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty + if (is_custom_property) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); + } lex < css_comments >(false); - if (peek_css< exactly<';'> >()) error("style declaration must contain a value", pstate); - if (peek_css< exactly<'{'> >()) is_indented = false; // don't indent if value is empty if (peek_css< static_value >()) { return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); } @@ -1044,6 +1104,7 @@ namespace Sass { Expression_Obj Parser::parse_map() { + NESTING_GUARD(nestings); Expression_Obj key = parse_list(); List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); @@ -1051,6 +1112,11 @@ namespace Sass { if (!lex_css< exactly<':'> >()) { return key; } + List_Obj l = Cast(key); + if (l && l->separator() == SASS_COMMA) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + Expression_Obj value = parse_space_list(); map->append(key); @@ -1062,12 +1128,12 @@ namespace Sass { if (peek_css< exactly<')'> >(position)) { break; } - Expression_Obj key = parse_space_list(); + key = parse_space_list(); if (!(lex< exactly<':'> >())) { css_error("Invalid CSS", " after ", ": expected \":\", was "); } - Expression_Obj value = parse_space_list(); + value = parse_space_list(); map->append(key); map->append(value); @@ -1082,6 +1148,7 @@ namespace Sass { Expression_Obj Parser::parse_bracket_list() { + NESTING_GUARD(nestings); // check if we have an empty list // return the empty list as such if (peek_css< list_terminator >(position)) @@ -1128,12 +1195,14 @@ namespace Sass { // so to speak: we unwrap items from lists if possible here! Expression_Obj Parser::parse_list(bool delayed) { + NESTING_GUARD(nestings); return parse_comma_list(delayed); } // will return singletons unwrapped Expression_Obj Parser::parse_comma_list(bool delayed) { + NESTING_GUARD(nestings); // check if we have an empty list // return the empty list as such if (peek_css< list_terminator >(position)) @@ -1173,6 +1242,7 @@ namespace Sass { // will return singletons unwrapped Expression_Obj Parser::parse_space_list() { + NESTING_GUARD(nestings); Expression_Obj disj1 = parse_disjunction(); // if it's a singleton, return it (don't wrap it) if (peek_css< space_list_terminator >(position) @@ -1197,6 +1267,7 @@ namespace Sass { // parse logical OR operation Expression_Obj Parser::parse_disjunction() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side conjunction @@ -1218,6 +1289,7 @@ namespace Sass { // parse logical AND operation Expression_Obj Parser::parse_conjunction() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side relation @@ -1240,6 +1312,7 @@ namespace Sass { // parse comparison operations Expression_Obj Parser::parse_relation() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side expression @@ -1272,7 +1345,6 @@ namespace Sass { bool right_ws = peek < css_comments >() != NULL; operators.push_back({ op, left_ws, right_ws }); operands.push_back(parse_expression()); - left_ws = peek < css_comments >() != NULL; } // we are called recursively for list, so we first // fold inner binary expression which has delayed @@ -1293,6 +1365,7 @@ namespace Sass { // parse addition and subtraction operations Expression_Obj Parser::parse_expression() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parses multiple add and subtract operations @@ -1336,6 +1409,7 @@ namespace Sass { // parse addition and subtraction operations Expression_Obj Parser::parse_operators() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); Expression_Obj factor = parse_factor(); @@ -1350,7 +1424,7 @@ namespace Sass { case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; - default: throw std::runtime_error("unknown static op parsed"); break; + default: throw std::runtime_error("unknown static op parsed"); } operands.push_back(parse_factor()); left_ws = peek < css_comments >(); @@ -1368,6 +1442,7 @@ namespace Sass { // called from parse_value_schema Expression_Obj Parser::parse_factor() { + NESTING_GUARD(nestings); lex < css_comments >(false); if (lex_css< exactly<'('> >()) { // parse_map may return a list @@ -1426,6 +1501,11 @@ namespace Sass { if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } + else if (lex< exactly<'/'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } else if (lex< sequence< kwd_not > >()) { Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); @@ -1443,12 +1523,119 @@ namespace Sass { } } + bool number_has_zero(const std::string& parsed) + { + size_t L = parsed.length(); + return !( (L > 0 && parsed.substr(0, 1) == ".") || + (L > 1 && parsed.substr(0, 2) == "0.") || + (L > 1 && parsed.substr(0, 2) == "-.") || + (L > 2 && parsed.substr(0, 3) == "-0.") ); + } + + Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "", + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "%", + true); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) + { + size_t L = parsed.length(); + size_t num_pos = parsed.find_first_not_of(" \n\r\t"); + if (num_pos == std::string::npos) num_pos = L; + size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); + if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { + unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); + } + if (unit_pos == std::string::npos) unit_pos = L; + const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(num.c_str()), + Token(number(parsed.c_str())), + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Expression_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + { + Color_Ptr color = NULL; + if (parsed[0] != '#') { + return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); + } + // chop off the '#' + std::string hext(parsed.substr(1)); + if (parsed.length() == 4) { + std::string r(2, parsed[1]); + std::string g(2, parsed[2]); + std::string b(2, parsed[3]); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 7) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 9) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + std::string a(parsed.substr(7,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + static_cast(strtol(a.c_str(), NULL, 16)) / 255, + parsed); + } + color->is_interpolant(false); + color->is_delayed(false); + return color; + } + // parse one value for a list Expression_Obj Parser::parse_value() { lex< css_comments >(false); if (lex< ampersand >()) { + if (match< ampersand >()) { + warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); + } return SASS_MEMORY_NEW(Parent_Selector, pstate); } if (lex< kwd_important >()) @@ -1456,10 +1643,10 @@ namespace Sass { // parse `10%4px` into separated items and not a schema if (lex< sequence < percentage, lookahead < number > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); } + { return lexed_percentage(lexed); } if (lex< sequence < number, lookahead< sequence < op, number > > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); } + { return lexed_number(lexed); } // string may be interpolated if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) @@ -1486,11 +1673,24 @@ namespace Sass { } if (lex< percentage >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); } + { return lexed_percentage(lexed); } // match hex number first because 0x000 looks like a number followed by an identifier if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, lexed); } + { return lexed_hex_color(lexed); } + + if (lex< hexa >()) + { + std::string s = lexed.to_string(); + + deprecated( + "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in", + "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.", + true, pstate + ); + + return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); + } if (lex< sequence < exactly <'#'>, identifier > >()) { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } @@ -1498,13 +1698,13 @@ namespace Sass { // also handle the 10em- foo special case // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed); } + { return lexed_dimension(lexed); } if (lex< sequence< static_component, one_plus< strict_identifier > > >()) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } if (lex< number >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); } + { return lexed_number(lexed); } if (lex< variable >()) { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } @@ -1534,7 +1734,7 @@ namespace Sass { return str_quoted; } - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); schema->is_interpolant(true); while (i < chunk.end) { p = constant ? find_first_in_interval< exactly >(i, chunk.end) : @@ -1570,7 +1770,74 @@ namespace Sass { ++ i; } - return schema; + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj tok; + if (!(tok = parse_css_variable_value_token(top_level))) { + return NULL; + } + + schema->concat(tok); + while ((tok = parse_css_variable_value_token(top_level))) { + schema->concat(tok); + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if ( + (top_level && lex< css_variable_top_level_value >(false)) || + (!top_level && lex< css_variable_value >(false)) + ) { + Token str(lexed); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); + } + else if (Expression_Obj tok = lex_interpolation()) { + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else if (lex< quoted_string >()) { + Expression_Obj tok = parse_string(); + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else { + if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { + if (lex< exactly<'('> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); + } + else if (lex< exactly<'['> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); + } + else if (lex< exactly<'{'> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); + } + } + } + + return schema->length() > 0 ? schema.detach() : NULL; } String_Constant_Obj Parser::parse_static_value() @@ -1579,7 +1846,8 @@ namespace Sass { Token str(lexed); // static values always have trailing white- // space and end delimiter (\s*[;]$) included - -- pstate.offset.column; + --pstate.offset.column; + --after_token.column; --str.end; --position; @@ -1648,7 +1916,11 @@ namespace Sass { lex< exactly<'='> >(); kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (peek< variable >()) kwd_arg->append(parse_list()); - else if (lex< number >()) kwd_arg->append(SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, Util::normalize_decimals(lexed))); + else if (lex< number >()) { + std::string parsed(lexed); + Util::normalize_decimals(parsed); + kwd_arg->append(lexed_number(parsed)); + } else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } return kwd_arg; } @@ -1662,7 +1934,7 @@ namespace Sass { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - const char* e = 0; + const char* e; const char* ee = end; end = stop; size_t num_items = 0; @@ -1685,7 +1957,7 @@ namespace Sass { if (peek< exactly< rbrace > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - Expression_Obj ex = 0; + Expression_Obj ex; if (lex< re_static_expression >()) { ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); } else { @@ -1726,19 +1998,19 @@ namespace Sass { } // lex percentage value else if (lex< percentage >()) { - schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed)); + schema->append(lexed_percentage(lexed)); } // lex dimension value else if (lex< dimension >()) { - schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed)); + schema->append(lexed_dimension(lexed)); } // lex number value else if (lex< number >()) { - schema->append( SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed)); + schema->append(lexed_number(lexed)); } // lex hex color value else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { - schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, lexed)); + schema->append(lexed_hex_color(lexed)); } else if (lex< sequence < exactly <'#'>, identifier > >()) { schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); @@ -1898,6 +2170,9 @@ namespace Sass { lex< identifier >(); std::string name(lexed); + if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) + { error("Cannot call content-exists() except within a mixin.", pstate); } + ParserState call_pos = pstate; Arguments_Obj args = parse_arguments(); return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); @@ -2012,6 +2287,10 @@ namespace Sass { While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); // parse mandatory predicate Expression_Obj predicate = parse_list(); + List_Obj l = Cast(predicate); + if (!predicate || (l && !l->length())) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); + } call->predicate(predicate); // parse mandatory block call->block(parse_block(root)); @@ -2083,7 +2362,7 @@ namespace Sass { if (!lex_css< exactly<'('> >()) { error("media query expression must begin with '('", pstate); } - Expression_Obj feature = 0; + Expression_Obj feature; if (peek_css< exactly<')'> >()) { error("media feature required in media query expression", pstate); } @@ -2103,6 +2382,9 @@ namespace Sass { Supports_Block_Obj Parser::parse_supports_directive() { Supports_Condition_Obj cond = parse_supports_condition(); + if (!cond) { + css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); + } // create the ast node object for the support queries Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); // additional block is mandatory @@ -2117,7 +2399,7 @@ namespace Sass { Supports_Condition_Obj Parser::parse_supports_condition() { lex < css_whitespace >(); - Supports_Condition_Obj cond = 0; + Supports_Condition_Obj cond; if ((cond = parse_supports_negation())) return cond; if ((cond = parse_supports_operator())) return cond; if ((cond = parse_supports_interpolation())) return cond; @@ -2164,14 +2446,18 @@ namespace Sass { // look like declarations their semantics differ significantly Supports_Condition_Obj Parser::parse_supports_declaration() { - Supports_Condition_Ptr cond = 0; + Supports_Condition_Ptr cond; // parse something declaration like - Declaration_Obj declaration = parse_declaration(); - if (!declaration) error("@supports condition expected declaration", pstate); + Expression_Obj feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!feature || !expression) error("@supports condition expected declaration", pstate); cond = SASS_MEMORY_NEW(Supports_Declaration, - declaration->pstate(), - declaration->property(), - declaration->value()); + feature->pstate(), + feature, + expression); // ToDo: maybe we need an additional error condition? return cond; } @@ -2197,6 +2483,7 @@ namespace Sass { At_Root_Block_Obj Parser::parse_at_root_block() { + stack.push_back(Scope::AtRoot); ParserState at_source_position = pstate; Block_Obj body = 0; At_Root_Query_Obj expr; @@ -2215,6 +2502,7 @@ namespace Sass { } At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); if (!expr.isNull()) at_root->expression(expr); + stack.pop_back(); return at_root; } @@ -2334,7 +2622,7 @@ namespace Sass { Expression_Obj Parser::lex_interp_string() { - Expression_Obj rv = 0; + Expression_Obj rv; if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; return rv; @@ -2394,7 +2682,7 @@ namespace Sass { Expression_Obj Parser::lex_almost_any_value_token() { - Expression_Obj rv = 0; + Expression_Obj rv; if (*position == 0) return 0; if ((rv = lex_almost_any_value_chars())) return rv; // if ((rv = lex_block_comment())) return rv; @@ -2487,12 +2775,18 @@ namespace Sass { re_selector_list >(p) ) { + bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; while (p < q) { // did we have interpolations? if (*p == '#' && *(p+1) == '{') { rv.has_interpolants = true; p = q; break; } + // A property that's ambiguous with a nested selector is interpreted as a + // custom property. + if (*p == ':') { + rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); + } ++ p; } // store anyway } @@ -2659,6 +2953,7 @@ namespace Sass { skip = check_bom_chars(source, end, gb_18030_bom, 4); encoding = "GB-18030"; break; + default: break; } if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding, pstate); position += skip; @@ -2744,19 +3039,20 @@ namespace Sass { } // print a css parsing error with actual context information from parsed source - void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle) + void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) { int max_len = 18; const char* end = this->end; while (*end != 0) ++ end; const char* pos = peek < optional_spaces >(); + if (!pos) pos = position; const char* last_pos(pos); if (last_pos > source) { utf8::prior(last_pos, source); } // backup position to last significant char - while (last_pos > source && last_pos < end) { + while (trim && last_pos > source && last_pos < end) { if (!Prelexer::is_space(*last_pos)) break; utf8::prior(last_pos, source); } @@ -2807,6 +3103,8 @@ namespace Sass { size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; + // Hotfix when source is null, probably due to interpolation parsing!? + if (source == NULL || *source == 0) source = pstate.src; // now pass new message to the more generic error function error(msg + prefix + quote(left) + middle + quote(right), pstate); } diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp old mode 100755 new mode 100644 index 263b4e1e2..71b00a854 --- a/src/libsass/src/parser.hpp +++ b/src/libsass/src/parser.hpp @@ -10,12 +10,22 @@ #include "position.hpp" #include "prelexer.hpp" +#ifndef MAX_NESTING +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +#define MAX_NESTING 512 +#endif + struct Lookahead { const char* found; const char* error; const char* position; bool parsable; bool has_interpolants; + bool is_custom_property; }; namespace Sass { @@ -23,7 +33,7 @@ namespace Sass { class Parser : public ParserState { public: - enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules }; + enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; Context& ctx; std::vector block_stack; @@ -35,14 +45,14 @@ namespace Sass { Position before_token; Position after_token; ParserState pstate; - int indentation; - + size_t indentation; + size_t nestings; Token lexed; Parser(Context& ctx, const ParserState& pstate) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0) + source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0), nestings(0) { stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); @@ -99,7 +109,7 @@ namespace Sass { } - // peek will only skip over space, tabs and line comment + // match will not skip over space, tabs and line comment // return the position where the lexer match will occur template const char* match(const char* start = 0) @@ -227,7 +237,8 @@ namespace Sass { // text before and in the middle are configurable void css_error(const std::string& msg, const std::string& prefix = " after ", - const std::string& middle = ", was: "); + const std::string& middle = ", was: ", + const bool trim = true); void read_bom(); Block_Obj parse(); @@ -275,6 +286,8 @@ namespace Sass { String_Obj parse_interpolated_chunk(Token, bool constant = false); String_Obj parse_string(); String_Constant_Obj parse_static_value(); + String_Schema_Obj parse_css_variable_value(bool top_level = true); + String_Schema_Obj parse_css_variable_value_token(bool top_level = true); String_Obj parse_ie_property(); String_Obj parse_ie_keyword_arg(); String_Schema_Obj parse_value_schema(const char* stop); @@ -357,6 +370,21 @@ namespace Sass { } return 0; } + + public: + static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); + static Expression_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); + private: + Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; + Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; + Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; + Expression_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; + + static const char* re_attr_sensitive_close(const char* src); + static const char* re_attr_insensitive_close(const char* src); + }; size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); diff --git a/src/libsass/src/paths.hpp b/src/libsass/src/paths.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/plugins.cpp b/src/libsass/src/plugins.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/plugins.hpp b/src/libsass/src/plugins.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/position.cpp b/src/libsass/src/position.cpp old mode 100755 new mode 100644 index a8afe67d4..312e04ca6 --- a/src/libsass/src/position.cpp +++ b/src/libsass/src/position.cpp @@ -4,6 +4,11 @@ namespace Sass { + Offset::Offset(const char chr) + : line(chr == '\n' ? 1 : 0), + column(chr == '\n' ? 0 : 1) + {} + Offset::Offset(const char* string) : line(0), column(0) { @@ -32,7 +37,6 @@ namespace Sass { // increase offset by given string (mostly called by lexer) // increase line counter and count columns on the last line - // ToDo: make the col count utf8 aware Offset Offset::add(const char* begin, const char* end) { if (end == 0) return *this; @@ -42,9 +46,23 @@ namespace Sass { // start new line column = 0; } else { - ++ column; + // do not count any utf8 continuation bytes + // https://stackoverflow.com/a/9356203/1550314 + // https://en.wikipedia.org/wiki/UTF-8#Description + unsigned char chr = *begin; + // skip over 10xxxxxx + // is 1st bit not set + if ((chr & 128) == 0) { + // regular ascii char + column += 1; + } + // is 2nd bit not set + else if ((chr & 64) == 0) { + // first utf8 byte + column += 1; + } } - ++begin; + ++ begin; } return *this; } diff --git a/src/libsass/src/position.hpp b/src/libsass/src/position.hpp old mode 100755 new mode 100644 index 1aeb5d15a..923be3c59 --- a/src/libsass/src/position.hpp +++ b/src/libsass/src/position.hpp @@ -11,6 +11,7 @@ namespace Sass { class Offset { public: // c-tor + Offset(const char chr); Offset(const char* string); Offset(const std::string& text); Offset(const size_t line, const size_t column); diff --git a/src/libsass/src/prelexer.cpp b/src/libsass/src/prelexer.cpp old mode 100755 new mode 100644 index f702513aa..d0edbc9cc --- a/src/libsass/src/prelexer.cpp +++ b/src/libsass/src/prelexer.cpp @@ -437,6 +437,10 @@ namespace Sass { optional < sequence < exactly <'/'>, + negate < sequence < + exactly < calc_fn_kwd >, + exactly < '(' > + > >, multiple_units > > >(src); @@ -576,7 +580,7 @@ namespace Sass { const char* value_combinations(const char* src) { // `2px-2px` is invalid combo bool was_number = false; - const char* pos = src; + const char* pos; while (src) { if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { was_number = false; @@ -642,10 +646,7 @@ namespace Sass { >, sequence < negate < - sequence < - exactly < url_kwd >, - exactly <'('> - > + uri_prefix >, neg_class_char < almost_any_value_class @@ -997,7 +998,17 @@ namespace Sass { digits>(src); } const char* number(const char* src) { - return sequence< optional, unsigned_number>(src); + return sequence< + optional, + unsigned_number, + optional< + sequence< + exactly<'e'>, + optional, + unsigned_number + > + > + >(src); } const char* coefficient(const char* src) { return alternatives< sequence< optional, digits >, @@ -1036,7 +1047,7 @@ namespace Sass { const char* hexa(const char* src) { const char* p = sequence< exactly<'#'>, one_plus >(src); ptrdiff_t len = p - src; - return (len != 4 && len != 7 && len != 9) ? 0 : p; + return (len != 5 && len != 9) ? 0 : p; } const char* hex0(const char* src) { const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); @@ -1046,7 +1057,7 @@ namespace Sass { /* no longer used - remove? const char* rgb_prefix(const char* src) { - return word(src); + return word(src); }*/ // Match CSS uri specifiers. @@ -1160,7 +1171,7 @@ namespace Sass { } // Match the CSS negation pseudo-class. const char* pseudo_not(const char* src) { - return word< pseudo_not_kwd >(src); + return word< pseudo_not_fn_kwd >(src); } // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. const char* even(const char* src) { @@ -1268,7 +1279,7 @@ namespace Sass { optional_css_whitespace, exactly<'='>, optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, zero_plus< sequence< optional_css_whitespace, exactly<','>, @@ -1278,7 +1289,7 @@ namespace Sass { optional_css_whitespace, exactly<'='>, optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa > + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > > > > > >, @@ -1313,6 +1324,7 @@ namespace Sass { identifier, quoted_string, number, + hex, hexa, sequence < exactly < '(' >, @@ -1449,6 +1461,16 @@ namespace Sass { // >(src); // } + const char* real_uri(const char* src) { + return sequence< + exactly< url_kwd >, + exactly< '(' >, + W, + real_uri_value, + exactly< ')' > + >(src); + } + const char* real_uri_suffix(const char* src) { return sequence< W, exactly< ')' > >(src); } @@ -1499,6 +1521,7 @@ namespace Sass { static_string, percentage, hex, + hexa, exactly<'|'>, // exactly<'+'>, sequence < number, unit_identifier >, @@ -1566,6 +1589,40 @@ namespace Sass { >(src); } + extern const char css_variable_url_negates[] = "()[]{}\"'#/"; + const char* css_variable_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;!"; + const char* css_variable_top_level_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_top_level_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + const char* parenthese_scope(const char* src) { return sequence < exactly < '(' >, @@ -1599,7 +1656,7 @@ namespace Sass { class_char < selector_lookahead_ops >, // match selector combinators /[>+~]/ class_char < selector_combinator_ops >, - // match attribute compare operators + // match pseudo selectors sequence < exactly <'('>, optional_spaces, @@ -1607,6 +1664,7 @@ namespace Sass { optional_spaces, exactly <')'> >, + // match attribute compare operators alternatives < exact_match, class_match, dash_match, prefix_match, suffix_match, substring_match @@ -1625,12 +1683,21 @@ namespace Sass { // class match exactly <'.'>, // single or double colon - optional < pseudo_prefix > + sequence < + optional < pseudo_prefix >, + // fix libsass issue 2376 + negate < uri_prefix > + > >, // accept hypens in token one_plus < sequence < // can start with hyphens - zero_plus < exactly<'-'> >, + zero_plus < + sequence < + exactly <'-'>, + optional_spaces + > + >, // now the main token alternatives < kwd_optional, @@ -1657,10 +1724,7 @@ namespace Sass { return sequence< optional, identifier>(src); } const char* re_type_selector(const char* src) { - return alternatives< type_selector, universal, quoted_string, dimension, percentage, number, identifier_alnums >(src); - } - const char* re_type_selector2(const char* src) { - return alternatives< type_selector, universal, quoted_string, dimension, percentage, number, identifier_alnums >(src); + return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); } const char* re_static_expression(const char* src) { return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); diff --git a/src/libsass/src/prelexer.hpp b/src/libsass/src/prelexer.hpp old mode 100755 new mode 100644 index 7cac454a1..2d8f83164 --- a/src/libsass/src/prelexer.hpp +++ b/src/libsass/src/prelexer.hpp @@ -46,7 +46,7 @@ namespace Sass { src = exactly(src); if (!src) return 0; const char* stop; - while (1) { + while (true) { if (!*src) return 0; stop = exactly(src); if (stop && (!esc || *(src - 1) != '\\')) return stop; @@ -139,7 +139,7 @@ namespace Sass { src = exactly(src); if (!src) return 0; const char* stop; - while (1) { + while (true) { if (!*src) return 0; stop = exactly(src); if (stop && (!esc || *(src - 1) != '\\')) return stop; @@ -264,7 +264,6 @@ namespace Sass { const char* kwd_while_directive(const char* src); const char* re_nothing(const char* src); - const char* re_type_selector2(const char* src); const char* re_special_fun(const char* src); @@ -366,6 +365,7 @@ namespace Sass { const char* UUNICODE(const char* src); const char* NONASCII(const char* src); const char* ESCAPE(const char* src); + const char* real_uri(const char* src); const char* real_uri_suffix(const char* src); // const char* real_uri_prefix(const char* src); const char* real_uri_value(const char* src); @@ -380,6 +380,9 @@ namespace Sass { const char* static_property(const char* src); const char* static_value(const char* src); + const char* css_variable_value(const char* src); + const char* css_variable_top_level_value(const char* src); + // Utility functions for finding and counting characters in a string. template const char* find_first(const char* src) { diff --git a/src/libsass/src/remove_placeholders.cpp b/src/libsass/src/remove_placeholders.cpp old mode 100755 new mode 100644 index 7402a9251..15cddace2 --- a/src/libsass/src/remove_placeholders.cpp +++ b/src/libsass/src/remove_placeholders.cpp @@ -44,8 +44,8 @@ namespace Sass { if (cs->head()) { for (Simple_Selector_Obj& ss : cs->head()->elements()) { if (Wrapped_Selector_Ptr ws = Cast(ss)) { - if (Selector_List_Ptr sl = Cast(ws->selector())) { - Selector_List_Ptr clean = remove_placeholders(sl); + if (Selector_List_Ptr wsl = Cast(ws->selector())) { + Selector_List_Ptr clean = remove_placeholders(wsl); // also clean superflous parent selectors // probably not really the correct place clean->remove_parent_selectors(); diff --git a/src/libsass/src/remove_placeholders.hpp b/src/libsass/src/remove_placeholders.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/sass.cpp b/src/libsass/src/sass.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/sass.hpp b/src/libsass/src/sass.hpp old mode 100755 new mode 100644 index 1f4c88b6e..f0550490c --- a/src/libsass/src/sass.hpp +++ b/src/libsass/src/sass.hpp @@ -90,10 +90,13 @@ struct Sass_Inspect_Options { // Precision for fractional numbers int precision; + // Do not compress colors in selectors + bool in_selector; + // initialization list (constructor with defaults) Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 5) - : output_style(style), precision(precision) + int precision = 5, bool in_selector = false) + : output_style(style), precision(precision), in_selector(in_selector) { } }; diff --git a/src/libsass/src/sass2scss.cpp b/src/libsass/src/sass2scss.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp old mode 100755 new mode 100644 index 82dc2152f..03a2f12fb --- a/src/libsass/src/sass_context.cpp +++ b/src/libsass/src/sass_context.cpp @@ -72,24 +72,35 @@ namespace Sass { // now create the code trace (ToDo: maybe have util functions?) if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { - size_t line = e.pstate.line; + size_t lines = e.pstate.line; const char* line_beg = e.pstate.src; - while (line_beg && *line_beg && line) { - if (*line_beg == '\n') --line; - ++line_beg; + // scan through src until target line + // move line_beg pointer to line start + while (line_beg && *line_beg && lines != 0) { + if (*line_beg == '\n') --lines; + utf8::unchecked::next(line_beg); } const char* line_end = line_beg; + // move line_end before next newline character while (line_end && *line_end && *line_end != '\n') { if (*line_end == '\n') break; if (*line_end == '\r') break; - line_end++; + utf8::unchecked::next(line_end); } - size_t max_left = 42; size_t max_right = 78; - size_t move_in = e.pstate.column > max_left ? e.pstate.column - max_left : 0; - size_t shorten = (line_end - line_beg) - move_in > max_right ? - (line_end - line_beg) - move_in - max_right : 0; - msg_stream << ">> " << std::string(line_beg + move_in, line_end - shorten) << "\n"; - msg_stream << " " << std::string(e.pstate.column - move_in, '-') << "^\n"; + if (line_end && *line_end != 0) ++ line_end; + size_t line_len = line_end - line_beg; + size_t move_in = 0; size_t shorten = 0; + size_t left_chars = 42; size_t max_chars = 76; + // reported excerpt should not exceed `max_chars` chars + if (e.pstate.column > line_len) left_chars = e.pstate.column; + if (e.pstate.column > left_chars) move_in = e.pstate.column - left_chars; + if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; + utf8::advance(line_beg, move_in, line_end); + utf8::retreat(line_end, shorten, line_beg); + std::string sanitized; std::string marker(e.pstate.column - move_in, '-'); + utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); + msg_stream << ">> " << sanitized << "\n"; + msg_stream << " " << marker << "^\n"; } JsonNode* json_err = json_mkobject(); @@ -199,7 +210,6 @@ namespace Sass { static int handle_errors(Sass_Context* c_ctx) { try { return handle_error(c_ctx); } catch (...) { return handle_error(c_ctx); } - return c_ctx->error_status; } static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() @@ -331,7 +341,9 @@ extern "C" { c_ctx->error_column = std::string::npos; // allocate a new compiler instance - Sass_Compiler* compiler = (struct Sass_Compiler*) calloc(1, sizeof(struct Sass_Compiler)); + void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); + if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } + Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; compiler->state = SASS_COMPILER_CREATED; // store in sass compiler @@ -522,30 +534,10 @@ extern "C" { static void sass_clear_options (struct Sass_Options* options) { if (options == 0) return; - // Deallocate custom functions - if (options->c_functions) { - Sass_Function_List this_func_data = options->c_functions; - while (this_func_data && *this_func_data) { - free(*this_func_data); - ++this_func_data; - } - } - // Deallocate custom headers - if (options->c_headers) { - Sass_Importer_List this_head_data = options->c_headers; - while (this_head_data && *this_head_data) { - free(*this_head_data); - ++this_head_data; - } - } - // Deallocate custom importers - if (options->c_importers) { - Sass_Importer_List this_imp_data = options->c_importers; - while (this_imp_data && *this_imp_data) { - free(*this_imp_data); - ++this_imp_data; - } - } + // Deallocate custom functions, headers and importes + sass_delete_function_list(options->c_functions); + sass_delete_importer_list(options->c_importers); + sass_delete_importer_list(options->c_headers); // Deallocate inc paths if (options->plugin_paths) { struct string_list* cur; @@ -577,11 +569,6 @@ extern "C" { free(options->include_path); free(options->source_map_file); free(options->source_map_root); - // Free custom functions - free(options->c_functions); - // Free custom importers - free(options->c_importers); - free(options->c_headers); // Reset our pointers options->input_path = 0; options->output_path = 0; diff --git a/src/libsass/src/sass_context.hpp b/src/libsass/src/sass_context.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/sass_functions.cpp b/src/libsass/src/sass_functions.cpp old mode 100755 new mode 100644 index f7beda35f..bfbf25838 --- a/src/libsass/src/sass_functions.cpp +++ b/src/libsass/src/sass_functions.cpp @@ -18,7 +18,7 @@ extern "C" { { Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); if (cb == 0) return 0; - cb->signature = strdup(signature); + cb->signature = sass_copy_c_string(signature); cb->function = function; cb->cookie = cookie; return cb; diff --git a/src/libsass/src/sass_functions.hpp b/src/libsass/src/sass_functions.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/sass_util.cpp b/src/libsass/src/sass_util.cpp old mode 100755 new mode 100644 index f3dd81f2b..3aef2bc72 --- a/src/libsass/src/sass_util.cpp +++ b/src/libsass/src/sass_util.cpp @@ -37,7 +37,7 @@ namespace Sass { end end */ - Node paths(const Node& arrs, Context& ctx) { + Node paths(const Node& arrs) { Node loopStart = Node::createCollection(); loopStart.collection()->push_back(Node::createCollection()); @@ -108,7 +108,7 @@ namespace Sass { return flattened end */ - Node flatten(Node& arr, Context& ctx, int n) { + Node flatten(Node& arr, int n) { if (n != -1 && n == 0) { return arr; } @@ -124,7 +124,7 @@ namespace Sass { if (e.isCollection()) { // e.collection().got_line_feed = e.got_line_feed; - Node recurseFlattened = flatten(e, ctx, n - 1); + Node recurseFlattened = flatten(e, n - 1); if(e.got_line_feed) { flattened.got_line_feed = e.got_line_feed; diff --git a/src/libsass/src/sass_util.hpp b/src/libsass/src/sass_util.hpp old mode 100755 new mode 100644 index ef72dff27..816da5fd8 --- a/src/libsass/src/sass_util.hpp +++ b/src/libsass/src/sass_util.hpp @@ -28,7 +28,7 @@ namespace Sass { # # [1, 4, 5], # # [2, 4, 5]] */ - Node paths(const Node& arrs, Context& ctx); + Node paths(const Node& arrs); /* @@ -139,7 +139,7 @@ namespace Sass { http://en.wikipedia.org/wiki/Longest_common_subsequence_problem */ template - Node lcs(Node& x, Node& y, const ComparatorType& comparator, Context& ctx) { + Node lcs(Node& x, Node& y, const ComparatorType& comparator) { DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) Node newX = Node::createCollection(); @@ -169,7 +169,7 @@ namespace Sass { # @param n [int] The number of levels to flatten # @return [NodeCollection] The flattened array */ - Node flatten(Node& arr, Context& ctx, int n = -1); + Node flatten(Node& arr, int n = -1); /* diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp old mode 100755 new mode 100644 index adf41d573..25abd49ba --- a/src/libsass/src/sass_values.cpp +++ b/src/libsass/src/sass_values.cpp @@ -221,6 +221,7 @@ extern "C" { case SASS_WARNING: { free(val->error.message); } break; + default: break; } free(val); @@ -236,26 +237,26 @@ extern "C" { switch(val->unknown.tag) { case SASS_NULL: { return sass_make_null(); - } break; + } case SASS_BOOLEAN: { return sass_make_boolean(val->boolean.value); - } break; + } case SASS_NUMBER: { return sass_make_number(val->number.value, val->number.unit); - } break; + } case SASS_COLOR: { return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a); - } break; + } case SASS_STRING: { return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); - } break; + } case SASS_LIST: { union Sass_Value* list = sass_make_list(val->list.length, val->list.separator, val->list.is_bracketed); for (i = 0; i < list->list.length; i++) { list->list.values[i] = sass_clone_value(val->list.values[i]); } return list; - } break; + } case SASS_MAP: { union Sass_Value* map = sass_make_map(val->map.length); for (i = 0; i < val->map.length; i++) { @@ -263,13 +264,14 @@ extern "C" { map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value); } return map; - } break; + } case SASS_ERROR: { return sass_make_error(val->error.message); - } break; + } case SASS_WARNING: { return sass_make_warning(val->warning.message); - } break; + } + default: break; } return 0; @@ -287,7 +289,7 @@ extern "C" { union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) { - Sass::Value_Ptr rv = 0; + Sass::Value_Ptr rv; try { @@ -311,27 +313,27 @@ extern "C" { if (sass_value_is_number(a) && sass_value_is_number(b)) { Number_Ptr_Const l_n = Cast(lhs); Number_Ptr_Const r_n = Cast(rhs); - rv = Eval::op_numbers(op, *l_n, *r_n, options); + rv = Eval::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); } else if (sass_value_is_number(a) && sass_value_is_color(a)) { Number_Ptr_Const l_n = Cast(lhs); Color_Ptr_Const r_c = Cast(rhs); - rv = Eval::op_number_color(op, *l_n, *r_c, options); + rv = Eval::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); } else if (sass_value_is_color(a) && sass_value_is_number(b)) { Color_Ptr_Const l_c = Cast(lhs); Number_Ptr_Const r_n = Cast(rhs); - rv = Eval::op_color_number(op, *l_c, *r_n, options); + rv = Eval::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); } else if (sass_value_is_color(a) && sass_value_is_color(b)) { Color_Ptr_Const l_c = Cast(lhs); Color_Ptr_Const r_c = Cast(rhs); - rv = Eval::op_colors(op, *l_c, *r_c, options); + rv = Eval::op_colors(op, *l_c, *r_c, options, l_c->pstate()); } else /* convert other stuff to string and apply operation */ { Value_Ptr l_v = Cast(lhs); Value_Ptr r_v = Cast(rhs); - rv = Eval::op_strings(op, *l_v, *r_v, options); + rv = Eval::op_strings(op, *l_v, *r_v, options, l_v->pstate()); } // ToDo: maybe we should should return null value? @@ -349,9 +351,6 @@ extern "C" { catch (std::string& e) { return sass_make_error(e.c_str()); } catch (const char* e) { return sass_make_error(e); } catch (...) { return sass_make_error("unknown"); } - - return 0; - } } diff --git a/src/libsass/src/sass_values.hpp b/src/libsass/src/sass_values.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/source_map.cpp b/src/libsass/src/source_map.cpp old mode 100755 new mode 100644 index 0e408b65d..c171a3f68 --- a/src/libsass/src/source_map.cpp +++ b/src/libsass/src/source_map.cpp @@ -24,9 +24,9 @@ namespace Sass { json_append_member(json_srcmap, "version", json_mknumber(3)); - const char *include = file.c_str(); - JsonNode *json_include = json_mkstring(include); - json_append_member(json_srcmap, "file", json_include); + const char *file_name = file.c_str(); + JsonNode *json_file_name = json_mkstring(file_name); + json_append_member(json_srcmap, "file", json_file_name); // pass-through sourceRoot option if (!ctx.source_map_root.empty()) { @@ -34,35 +34,34 @@ namespace Sass { json_append_member(json_srcmap, "sourceRoot", root); } - JsonNode *json_includes = json_mkarray(); + JsonNode *json_sources = json_mkarray(); for (size_t i = 0; i < source_index.size(); ++i) { - std::string include(links[source_index[i]]); + std::string source(links[source_index[i]]); if (ctx.c_options.source_map_file_urls) { - include = File::rel2abs(include); + source = File::rel2abs(source); // check for windows abs path - if (include[0] == '/') { + if (source[0] == '/') { // ends up with three slashes - include = "file://" + include; + source = "file://" + source; } else { // needs an additional slash - include = "file:///" + include; + source = "file:///" + source; } } - const char* inc = include.c_str(); - JsonNode *json_include = json_mkstring(inc); - json_append_element(json_includes, json_include); + const char* source_name = source.c_str(); + JsonNode *json_source_name = json_mkstring(source_name); + json_append_element(json_sources, json_source_name); } - json_append_member(json_srcmap, "sources", json_includes); + json_append_member(json_srcmap, "sources", json_sources); - if (include_sources) { + if (include_sources && source_index.size()) { JsonNode *json_contents = json_mkarray(); for (size_t i = 0; i < source_index.size(); ++i) { const Resource& resource(sources[source_index[i]]); JsonNode *json_content = json_mkstring(resource.contents); json_append_element(json_contents, json_content); } - if (json_contents->children.head) - json_append_member(json_srcmap, "sourcesContent", json_contents); + json_append_member(json_srcmap, "sourcesContent", json_contents); } JsonNode *json_names = json_mkarray(); @@ -137,7 +136,7 @@ namespace Sass { } } } - // will adjust the offset + // adjust the buffer offset prepend(Offset(out.buffer)); // now add the new mappings VECTOR_UNSHIFT(mappings, out.smap.mappings); diff --git a/src/libsass/src/source_map.hpp b/src/libsass/src/source_map.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/subset_map.cpp b/src/libsass/src/subset_map.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/subset_map.hpp b/src/libsass/src/subset_map.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/support/libsass.pc.in b/src/libsass/src/support/libsass.pc.in old mode 100755 new mode 100644 diff --git a/src/libsass/src/to_c.cpp b/src/libsass/src/to_c.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/to_c.hpp b/src/libsass/src/to_c.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/to_value.cpp b/src/libsass/src/to_value.cpp old mode 100755 new mode 100644 index b29e97044..3912c5510 --- a/src/libsass/src/to_value.cpp +++ b/src/libsass/src/to_value.cpp @@ -9,8 +9,6 @@ namespace Sass { // throw a runtime error if this happens // we want a well defined set of possible nodes throw std::runtime_error("invalid node for to_value"); - // mute warning - return 0; } // Custom_Error is a valid value @@ -62,7 +60,8 @@ namespace Sass { l->pstate(), l->length(), l->separator(), - l->is_arglist()); + l->is_arglist(), + l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { ll->append((*l)[i]->perform(this)); } @@ -81,6 +80,12 @@ namespace Sass { return n; } + // Function is a valid value + Value_Ptr To_Value::operator()(Function_Ptr n) + { + return n; + } + // Argument returns its value Value_Ptr To_Value::operator()(Argument_Ptr arg) { diff --git a/src/libsass/src/to_value.hpp b/src/libsass/src/to_value.hpp old mode 100755 new mode 100644 index e6c88dd69..8f64128c4 --- a/src/libsass/src/to_value.hpp +++ b/src/libsass/src/to_value.hpp @@ -34,6 +34,7 @@ namespace Sass { Value_Ptr operator()(List_Ptr); Value_Ptr operator()(Map_Ptr); Value_Ptr operator()(Null_Ptr); + Value_Ptr operator()(Function_Ptr); // convert to string via `To_String` Value_Ptr operator()(Selector_List_Ptr); diff --git a/src/libsass/src/units.cpp b/src/libsass/src/units.cpp old mode 100755 new mode 100644 index b3512dba6..779f1d2b4 --- a/src/libsass/src/units.cpp +++ b/src/libsass/src/units.cpp @@ -1,6 +1,7 @@ #include "sass.hpp" #include #include "units.hpp" +#include "error_handling.hpp" namespace Sass { @@ -53,12 +54,12 @@ namespace Sass { { switch (unit & 0xFF00) { - case UnitClass::LENGTH: return UnitClass::LENGTH; break; - case UnitClass::ANGLE: return UnitClass::ANGLE; break; - case UnitClass::TIME: return UnitClass::TIME; break; - case UnitClass::FREQUENCY: return UnitClass::FREQUENCY; break; - case UnitClass::RESOLUTION: return UnitClass::RESOLUTION; break; - default: return UnitClass::INCOMMENSURABLE; break; + case UnitClass::LENGTH: return UnitClass::LENGTH; + case UnitClass::ANGLE: return UnitClass::ANGLE; + case UnitClass::TIME: return UnitClass::TIME; + case UnitClass::FREQUENCY: return UnitClass::FREQUENCY; + case UnitClass::RESOLUTION: return UnitClass::RESOLUTION; + default: return UnitClass::INCOMMENSURABLE; } }; @@ -66,12 +67,25 @@ namespace Sass { { switch (unit & 0xFF00) { - case UnitClass::LENGTH: return "LENGTH"; break; - case UnitClass::ANGLE: return "ANGLE"; break; - case UnitClass::TIME: return "TIME"; break; - case UnitClass::FREQUENCY: return "FREQUENCY"; break; - case UnitClass::RESOLUTION: return "RESOLUTION"; break; - default: return "INCOMMENSURABLE"; break; + case UnitClass::LENGTH: return "LENGTH"; + case UnitClass::ANGLE: return "ANGLE"; + case UnitClass::TIME: return "TIME"; + case UnitClass::FREQUENCY: return "FREQUENCY"; + case UnitClass::RESOLUTION: return "RESOLUTION"; + default: return "INCOMMENSURABLE"; + } + }; + + UnitType get_main_unit(const UnitClass unit) + { + switch (unit) + { + case UnitClass::LENGTH: return UnitType::PX; + case UnitClass::ANGLE: return UnitType::DEG; + case UnitClass::TIME: return UnitType::SEC; + case UnitClass::FREQUENCY: return UnitType::HERTZ; + case UnitClass::RESOLUTION: return UnitType::DPI; + default: return UnitType::UNKNOWN; } }; @@ -107,29 +121,29 @@ namespace Sass { { switch (unit) { // size units - case UnitType::PX: return "px"; break; - case UnitType::PT: return "pt"; break; - case UnitType::PC: return "pc"; break; - case UnitType::MM: return "mm"; break; - case UnitType::CM: return "cm"; break; - case UnitType::IN: return "in"; break; + case UnitType::PX: return "px"; + case UnitType::PT: return "pt"; + case UnitType::PC: return "pc"; + case UnitType::MM: return "mm"; + case UnitType::CM: return "cm"; + case UnitType::IN: return "in"; // angle units - case UnitType::DEG: return "deg"; break; - case UnitType::GRAD: return "grad"; break; - case UnitType::RAD: return "rad"; break; - case UnitType::TURN: return "turn"; break; + case UnitType::DEG: return "deg"; + case UnitType::GRAD: return "grad"; + case UnitType::RAD: return "rad"; + case UnitType::TURN: return "turn"; // time units - case UnitType::SEC: return "s"; break; - case UnitType::MSEC: return "ms"; break; + case UnitType::SEC: return "s"; + case UnitType::MSEC: return "ms"; // frequency units - case UnitType::HERTZ: return "Hz"; break; - case UnitType::KHERTZ: return "kHz"; break; + case UnitType::HERTZ: return "Hz"; + case UnitType::KHERTZ: return "kHz"; // resolutions units - case UnitType::DPI: return "dpi"; break; - case UnitType::DPCM: return "dpcm"; break; - case UnitType::DPPX: return "dppx"; break; + case UnitType::DPI: return "dpi"; + case UnitType::DPCM: return "dpcm"; + case UnitType::DPPX: return "dppx"; // for unknown units - default: return ""; break; + default: return ""; } } @@ -161,7 +175,7 @@ namespace Sass { } // throws incompatibleUnits exceptions - double conversion_factor(const std::string& s1, const std::string& s2, bool strict) + double conversion_factor(const std::string& s1, const std::string& s2) { // assert for same units if (s1 == s2) return 1; @@ -171,27 +185,317 @@ namespace Sass { // query unit group types UnitClass t1 = get_unit_type(u1); UnitClass t2 = get_unit_type(u2); + // return the conversion factor + return conversion_factor(u1, u2, t1, t2); + } + + // throws incompatibleUnits exceptions + double conversion_factor(UnitType u1, UnitType u2, UnitClass t1, UnitClass t2) + { + // can't convert between groups + if (t1 != t2) return 0; // get absolute offset // used for array acces size_t i1 = u1 - t1; size_t i2 = u2 - t2; - // error if units are not of the same group - // don't error for multiplication and division - if (strict && t1 != t2) throw incompatibleUnits(u1, u2); - // only process known units - if (u1 != UNKNOWN && u2 != UNKNOWN) { - switch (t1) { - case UnitClass::LENGTH: return size_conversion_factors[i1][i2]; break; - case UnitClass::ANGLE: return angle_conversion_factors[i1][i2]; break; - case UnitClass::TIME: return time_conversion_factors[i1][i2]; break; - case UnitClass::FREQUENCY: return frequency_conversion_factors[i1][i2]; break; - case UnitClass::RESOLUTION: return resolution_conversion_factors[i1][i2]; break; - // ToDo: should we throw error here? - case UnitClass::INCOMMENSURABLE: return 0; break; - } + // process known units + switch (t1) { + case LENGTH: + return size_conversion_factors[i1][i2]; + case ANGLE: + return angle_conversion_factors[i1][i2]; + case TIME: + return time_conversion_factors[i1][i2]; + case FREQUENCY: + return frequency_conversion_factors[i1][i2]; + case RESOLUTION: + return resolution_conversion_factors[i1][i2]; + case INCOMMENSURABLE: + return 0; } // fallback return 0; } + double convert_units(const std::string& lhs, const std::string& rhs, int& lhsexp, int& rhsexp) + { + double f = 0; + // do not convert same ones + if (lhs == rhs) return 0; + // skip already canceled out unit + if (lhsexp == 0) return 0; + if (rhsexp == 0) return 0; + // check if it can be converted + UnitType ulhs = string_to_unit(lhs); + UnitType urhs = string_to_unit(rhs); + // skip units we cannot convert + if (ulhs == UNKNOWN) return 0; + if (urhs == UNKNOWN) return 0; + // query unit group types + UnitClass clhs = get_unit_type(ulhs); + UnitClass crhs = get_unit_type(urhs); + // skip units we cannot convert + if (clhs != crhs) return 0; + // if right denominator is bigger than lhs, we want to keep it in rhs unit + if (rhsexp < 0 && lhsexp > 0 && - rhsexp > lhsexp) { + // get the conversion factor for units + f = conversion_factor(urhs, ulhs, clhs, crhs); + // left hand side has been consumned + f = std::pow(f, lhsexp); + rhsexp += lhsexp; + lhsexp = 0; + } + else { + // get the conversion factor for units + f = conversion_factor(ulhs, urhs, clhs, crhs); + // right hand side has been consumned + f = std::pow(f, rhsexp); + lhsexp += rhsexp; + rhsexp = 0; + } + return f; + } + + bool Units::operator< (const Units& rhs) const + { + return (numerators < rhs.numerators) && + (denominators < rhs.denominators); + } + bool Units::operator== (const Units& rhs) const + { + return (numerators == rhs.numerators) && + (denominators == rhs.denominators); + } + + double Units::normalize() + { + + size_t iL = numerators.size(); + size_t nL = denominators.size(); + + // the final conversion factor + double factor = 1; + + for (size_t i = 0; i < iL; i++) { + std::string &lhs = numerators[i]; + UnitType ulhs = string_to_unit(lhs); + if (ulhs == UNKNOWN) continue; + UnitClass clhs = get_unit_type(ulhs); + UnitType umain = get_main_unit(clhs); + if (ulhs == umain) continue; + double f(conversion_factor(umain, ulhs, clhs, clhs)); + if (f == 0) throw std::runtime_error("INVALID"); + numerators[i] = unit_to_string(umain); + factor /= f; + } + + for (size_t n = 0; n < nL; n++) { + std::string &rhs = denominators[n]; + UnitType urhs = string_to_unit(rhs); + if (urhs == UNKNOWN) continue; + UnitClass crhs = get_unit_type(urhs); + UnitType umain = get_main_unit(crhs); + if (urhs == umain) continue; + double f(conversion_factor(umain, urhs, crhs, crhs)); + if (f == 0) throw std::runtime_error("INVALID"); + denominators[n] = unit_to_string(umain); + factor /= f; + } + + std::sort (numerators.begin(), numerators.end()); + std::sort (denominators.begin(), denominators.end()); + + // return for conversion + return factor; + } + + double Units::reduce() + { + + size_t iL = numerators.size(); + size_t nL = denominators.size(); + + // have less than two units? + if (iL + nL < 2) return 1; + + // first make sure same units cancel each other out + // it seems that a map table will fit nicely to do this + // we basically construct exponents for each unit + // has the advantage that they will be pre-sorted + std::map exponents; + + // initialize by summing up occurences in unit vectors + // this will already cancel out equivalent units (e.q. px/px) + for (size_t i = 0; i < iL; i ++) exponents[numerators[i]] += 1; + for (size_t n = 0; n < nL; n ++) exponents[denominators[n]] -= 1; + + // the final conversion factor + double factor = 1; + + // convert between compatible units + for (size_t i = 0; i < iL; i++) { + for (size_t n = 0; n < nL; n++) { + std::string &lhs = numerators[i], &rhs = denominators[n]; + int &lhsexp = exponents[lhs], &rhsexp = exponents[rhs]; + double f(convert_units(lhs, rhs, lhsexp, rhsexp)); + if (f == 0) continue; + factor /= f; + } + } + + // now we can build up the new unit arrays + numerators.clear(); + denominators.clear(); + + // recreate sorted units vectors + for (auto exp : exponents) { + int &exponent = exp.second; + while (exponent > 0 && exponent --) + numerators.push_back(exp.first); + while (exponent < 0 && exponent ++) + denominators.push_back(exp.first); + } + + // return for conversion + return factor; + + } + + std::string Units::unit() const + { + std::string u; + size_t iL = numerators.size(); + size_t nL = denominators.size(); + for (size_t i = 0; i < iL; i += 1) { + if (i) u += '*'; + u += numerators[i]; + } + if (nL != 0) u += '/'; + for (size_t n = 0; n < nL; n += 1) { + if (n) u += '*'; + u += denominators[n]; + } + return u; + } + + bool Units::is_unitless() const + { + return numerators.empty() && + denominators.empty(); + } + + bool Units::is_valid_css_unit() const + { + return numerators.size() <= 1 && + denominators.size() == 0; + } + + // this does not cover all cases (multiple prefered units) + double Units::convert_factor(const Units& r) const + { + + std::vector miss_nums(0); + std::vector miss_dens(0); + // create copy since we need these for state keeping + std::vector r_nums(r.numerators); + std::vector r_dens(r.denominators); + + auto l_num_it = numerators.begin(); + auto l_num_end = numerators.end(); + + bool l_unitless = is_unitless(); + auto r_unitless = r.is_unitless(); + + // overall conversion + double factor = 1; + + // process all left numerators + while (l_num_it != l_num_end) + { + // get and increment afterwards + const std::string l_num = *(l_num_it ++); + + auto r_num_it = r_nums.begin(), r_num_end = r_nums.end(); + + bool found = false; + // search for compatible numerator + while (r_num_it != r_num_end) + { + // get and increment afterwards + const std::string r_num = *(r_num_it); + // get possible conversion factor for units + double conversion = conversion_factor(l_num, r_num); + // skip incompatible numerator + if (conversion == 0) { + ++ r_num_it; + continue; + } + // apply to global factor + factor *= conversion; + // remove item from vector + r_nums.erase(r_num_it); + // found numerator + found = true; + break; + } + // maybe we did not find any + // left numerator is leftover + if (!found) miss_nums.push_back(l_num); + } + + auto l_den_it = denominators.begin(); + auto l_den_end = denominators.end(); + + // process all left denominators + while (l_den_it != l_den_end) + { + // get and increment afterwards + const std::string l_den = *(l_den_it ++); + + auto r_den_it = r_dens.begin(); + auto r_den_end = r_dens.end(); + + bool found = false; + // search for compatible denominator + while (r_den_it != r_den_end) + { + // get and increment afterwards + const std::string r_den = *(r_den_it); + // get possible converstion factor for units + double conversion = conversion_factor(l_den, r_den); + // skip incompatible denominator + if (conversion == 0) { + ++ r_den_it; + continue; + } + // apply to global factor + factor /= conversion; + // remove item from vector + r_dens.erase(r_den_it); + // found denominator + found = true; + break; + } + // maybe we did not find any + // left denominator is leftover + if (!found) miss_dens.push_back(l_den); + } + + // check left-overs (ToDo: might cancel out?) + if (miss_nums.size() > 0 && !r_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + else if (miss_dens.size() > 0 && !r_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + else if (r_nums.size() > 0 && !l_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + else if (r_dens.size() > 0 && !l_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + + return factor; + } + } diff --git a/src/libsass/src/units.hpp b/src/libsass/src/units.hpp old mode 100755 new mode 100644 index eb29c693d..306f5349b --- a/src/libsass/src/units.hpp +++ b/src/libsass/src/units.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Sass { @@ -52,40 +53,56 @@ namespace Sass { }; + class Units { + public: + std::vector numerators; + std::vector denominators; + public: + // default constructor + Units() : + numerators(), + denominators() + { } + // copy constructor + Units(const Units* ptr) : + numerators(ptr->numerators), + denominators(ptr->denominators) + { } + // convert to string + std::string unit() const; + // get if units are empty + bool is_unitless() const; + // return if valid for css + bool is_valid_css_unit() const; + // reduce units for output + // returns conversion factor + double reduce(); + // normalize units for compare + // returns conversion factor + double normalize(); + // compare operations + bool operator< (const Units& rhs) const; + bool operator== (const Units& rhs) const; + // factor to convert into given units + double convert_factor(const Units&) const; + }; + extern const double size_conversion_factors[6][6]; extern const double angle_conversion_factors[4][4]; extern const double time_conversion_factors[2][2]; extern const double frequency_conversion_factors[2][2]; extern const double resolution_conversion_factors[3][3]; + UnitType get_main_unit(const UnitClass unit); enum Sass::UnitType string_to_unit(const std::string&); const char* unit_to_string(Sass::UnitType unit); enum Sass::UnitClass get_unit_type(Sass::UnitType unit); std::string get_unit_class(Sass::UnitType unit); std::string unit_to_class(const std::string&); // throws incompatibleUnits exceptions - double conversion_factor(const std::string&, const std::string&, bool = true); - - class incompatibleUnits: public std::exception - { - public: - const char* msg; - incompatibleUnits(Sass::UnitType a, Sass::UnitType b) - : exception() - { - std::stringstream ss; - ss << "Incompatible units: "; - ss << "'" << unit_to_string(a) << "' and "; - ss << "'" << unit_to_string(b) << "'"; - // hold on to string on stack! - std::string str(ss.str()); - msg = str.c_str(); - } - virtual const char* what() const throw() - { - return msg; - } - }; + double conversion_factor(const std::string&, const std::string&); + double conversion_factor(UnitType, UnitType, UnitClass, UnitClass); + double convert_units(const std::string&, const std::string&, int&, int&); } diff --git a/src/libsass/src/utf8.h b/src/libsass/src/utf8.h old mode 100755 new mode 100644 diff --git a/src/libsass/src/utf8/checked.h b/src/libsass/src/utf8/checked.h old mode 100755 new mode 100644 index 133115513..693aee964 --- a/src/libsass/src/utf8/checked.h +++ b/src/libsass/src/utf8/checked.h @@ -193,6 +193,13 @@ namespace utf8 utf8::next(it, end); } + template + void retreat (octet_iterator& it, distance_type n, octet_iterator start) + { + for (distance_type i = 0; i < n; ++i) + utf8::prior(it, start); + } + template typename std::iterator_traits::difference_type distance (octet_iterator first, octet_iterator last) diff --git a/src/libsass/src/utf8/core.h b/src/libsass/src/utf8/core.h old mode 100755 new mode 100644 diff --git a/src/libsass/src/utf8/unchecked.h b/src/libsass/src/utf8/unchecked.h old mode 100755 new mode 100644 index 989ccefa7..01bdd076a --- a/src/libsass/src/utf8/unchecked.h +++ b/src/libsass/src/utf8/unchecked.h @@ -116,6 +116,13 @@ namespace utf8 utf8::unchecked::next(it); } + template + void retreat (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::prior(it); + } + template typename std::iterator_traits::difference_type distance (octet_iterator first, octet_iterator last) diff --git a/src/libsass/src/utf8_string.cpp b/src/libsass/src/utf8_string.cpp old mode 100755 new mode 100644 index ba4d7a25f..19425521c --- a/src/libsass/src/utf8_string.cpp +++ b/src/libsass/src/utf8_string.cpp @@ -28,7 +28,7 @@ namespace Sass { size_t offset_at_position(const string& str, size_t position) { string::const_iterator it = str.begin(); utf8::advance(it, position, str.end()); - return distance(str.begin(), it); + return std::distance(str.begin(), it); } // function that returns number of bytes in a character at offset diff --git a/src/libsass/src/utf8_string.hpp b/src/libsass/src/utf8_string.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp old mode 100755 new mode 100644 index 4b1636e1c..49187d95d --- a/src/libsass/src/util.cpp +++ b/src/libsass/src/util.cpp @@ -9,11 +9,22 @@ #include #include +#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) +#include +#endif namespace Sass { double round(double val, size_t precision) { + // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets + // See https://github.com/sass/node-sass/issues/1854 for details + // FIXME: Remove this workaround when we switch to VS2015+ + #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) + static std::once_flag flag; + std::call_once(flag, []() { _set_FMA3_enable(0); }); + #endif + // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 if (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); @@ -24,7 +35,7 @@ namespace Sass { } /* Locale unspecific atof function. */ - double sass_atof(const char *str) + double sass_strtod(const char *str) { char separator = *(localeconv()->decimal_point); if(separator != '.'){ @@ -37,13 +48,13 @@ namespace Sass { // of the string. This is slower but it is thread safe. char *copy = sass_copy_c_string(str); *(copy + (found - str)) = separator; - double res = atof(copy); + double res = strtod(copy, NULL); free(copy); return res; } } - return atof(str); + return strtod(str, NULL); } // helper for safe access to c_ctx @@ -221,6 +232,75 @@ namespace Sass { return quote_mark; } + std::string read_hex_escapes(const std::string& s) + { + + std::string result; + bool skipped = false; + + for (size_t i = 0, L = s.length(); i < L; ++i) { + + // implement the same strange ruby sass behavior + // an escape sequence can also mean a unicode char + if (s[i] == '\\' && !skipped) { + + // remember + skipped = true; + + // escape length + size_t len = 1; + + // parse as many sequence chars as possible + // ToDo: Check if ruby aborts after possible max + while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + + if (len > 1) { + + // convert the extracted hex string to code point value + // ToDo: Maybe we could do this without creating a substring + uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); + + if (s[i + len] == ' ') ++ len; + + // assert invalid code points + if (cp == 0) cp = 0xFFFD; + // replace bell character + // if (cp == '\n') cp = 32; + + // use a very simple approach to convert via utf8 lib + // maybe there is a more elegant way; maybe we shoud + // convert the whole output from string to a stream!? + // allocate memory for utf8 char and convert to utf8 + unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); + for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(u[m]); + + // skip some more chars? + i += len - 1; skipped = false; + + } + + else { + + skipped = false; + + result.push_back(s[i]); + + } + + } + + else { + + result.push_back(s[i]); + + } + + } + + return result; + + } + std::string unquote(const std::string& s, char* qd, bool keep_utf8_sequences, bool strict) { @@ -281,7 +361,7 @@ namespace Sass { // convert the whole output from string to a stream!? // allocate memory for utf8 char and convert to utf8 unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; u[m] && m < 5; m++) unq.push_back(u[m]); + for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(u[m]); // skip some more chars? i += len - 1; skipped = false; @@ -520,8 +600,10 @@ namespace Sass { } else if (Has_Block_Ptr b = Cast(stm)) { Block_Obj pChildBlock = b->block(); - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; + if (!b->is_invisible()) { + if (isPrintable(pChildBlock, style)) { + hasPrintableChildBlocks = true; + } } } @@ -557,8 +639,8 @@ namespace Sass { return true; } } - else if (Media_Block_Ptr m = Cast(stm)) { - if (isPrintable(m, style)) { + else if (Media_Block_Ptr mb = Cast(stm)) { + if (isPrintable(mb, style)) { return true; } } diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp old mode 100755 new mode 100644 index ceed7d725..ee263711b --- a/src/libsass/src/util.hpp +++ b/src/libsass/src/util.hpp @@ -18,7 +18,7 @@ namespace Sass { } while (0) double round(double val, size_t precision = 0); - double sass_atof(const char* str); + double sass_strtod(const char* str); const char* safe_str(const char *, const char* = ""); void free_string_array(char **); char **copy_strings(const std::vector&, char ***, int = 0); @@ -26,6 +26,7 @@ namespace Sass { std::string evacuate_escapes(const std::string& str); std::string string_to_output(const std::string& str); std::string comment_to_string(const std::string& text); + std::string read_hex_escapes(const std::string& str); void newline_to_space(std::string& str); std::string quote(const std::string&, char q = 0); diff --git a/src/libsass/src/values.cpp b/src/libsass/src/values.cpp old mode 100755 new mode 100644 index a8b165f79..0f2fd48d7 --- a/src/libsass/src/values.cpp +++ b/src/libsass/src/values.cpp @@ -73,12 +73,10 @@ namespace Sass { ParserState("[C-VALUE]"), sass_number_get_value(val), sass_number_get_unit(val)); - break; case SASS_BOOLEAN: return SASS_MEMORY_NEW(Boolean, ParserState("[C-VALUE]"), sass_boolean_get_value(val)); - break; case SASS_COLOR: return SASS_MEMORY_NEW(Color, ParserState("[C-VALUE]"), @@ -86,18 +84,15 @@ namespace Sass { sass_color_get_g(val), sass_color_get_b(val), sass_color_get_a(val)); - break; case SASS_STRING: if (sass_string_is_quoted(val)) { return SASS_MEMORY_NEW(String_Quoted, ParserState("[C-VALUE]"), sass_string_get_value(val)); - } else { - return SASS_MEMORY_NEW(String_Constant, + } + return SASS_MEMORY_NEW(String_Constant, ParserState("[C-VALUE]"), sass_string_get_value(val)); - } - break; case SASS_LIST: { List_Ptr l = SASS_MEMORY_NEW(List, ParserState("[C-VALUE]"), @@ -109,7 +104,6 @@ namespace Sass { l->is_bracketed(sass_list_get_is_bracketed(val)); return l; } - break; case SASS_MAP: { Map_Ptr m = SASS_MEMORY_NEW(Map, ParserState("[C-VALUE]")); for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) { @@ -119,20 +113,17 @@ namespace Sass { } return m; } - break; case SASS_NULL: return SASS_MEMORY_NEW(Null, ParserState("[C-VALUE]")); - break; case SASS_ERROR: return SASS_MEMORY_NEW(Custom_Error, ParserState("[C-VALUE]"), sass_error_get_message(val)); - break; case SASS_WARNING: return SASS_MEMORY_NEW(Custom_Warning, ParserState("[C-VALUE]"), sass_warning_get_message(val)); - break; + default: break; } return 0; } diff --git a/src/libsass/src/values.hpp b/src/libsass/src/values.hpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_node.cpp b/src/libsass/test/test_node.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_paths.cpp b/src/libsass/test/test_paths.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_selector_difference.cpp b/src/libsass/test/test_selector_difference.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_specificity.cpp b/src/libsass/test/test_specificity.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_subset_map.cpp b/src/libsass/test/test_subset_map.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_superselector.cpp b/src/libsass/test/test_superselector.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/test/test_unification.cpp b/src/libsass/test/test_unification.cpp old mode 100755 new mode 100644 diff --git a/src/libsass/win/libsass.sln b/src/libsass/win/libsass.sln old mode 100755 new mode 100644 index 9354d85f5..2a55ad87e --- a/src/libsass/win/libsass.sln +++ b/src/libsass/win/libsass.sln @@ -1,10 +1,21 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsass", "libsass.vcxproj", "{E4030474-AFC9-4CC6-BEB6-D846F631502B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".SolutionItems", ".SolutionItems", "{33318C77-2391-4399-8118-C109155A4A75}" + ProjectSection(SolutionItems) = preProject + ..\.editorconfig = ..\.editorconfig + ..\.gitattributes = ..\.gitattributes + ..\.gitignore = ..\.gitignore + ..\.travis.yml = ..\.travis.yml + ..\appveyor.yml = ..\appveyor.yml + ..\Readme.md = ..\Readme.md + ..\res\resource.rc = ..\res\resource.rc + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 diff --git a/src/libsass/win/libsass.sln.DotSettings b/src/libsass/win/libsass.sln.DotSettings new file mode 100644 index 000000000..405024e15 --- /dev/null +++ b/src/libsass/win/libsass.sln.DotSettings @@ -0,0 +1,9 @@ + + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded \ No newline at end of file diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets old mode 100755 new mode 100644 diff --git a/src/libsass/win/libsass.vcxproj b/src/libsass/win/libsass.vcxproj old mode 100755 new mode 100644 diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters old mode 100755 new mode 100644 diff --git a/test/fixtures/source-map-embed/expected.css b/test/fixtures/source-map-embed/expected.css index 343a10548..56f2e59a3 100644 --- a/test/fixtures/source-map-embed/expected.css +++ b/test/fixtures/source-map-embed/expected.css @@ -10,4 +10,4 @@ #navbar li a { font-weight: bold; } -/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUFBLEFBQUEsT0FBTyxDQUFDO0VBQ04sS0FBSyxFQUFFLEdBQUc7RUFDVixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQVEsT0FBRCxDQUFDLEVBQUUsQ0FBQztFQUNULGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQUVELEFBQVEsT0FBRCxDQUFDLEVBQUUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJLEdBS1o7RUFORCxBQUdFLE9BSEssQ0FBQyxFQUFFLENBR1IsQ0FBQyxDQUFDO0lBQ0EsV0FBVyxFQUFFLElBQUksR0FDbEIiCn0= */ +/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUFBLEFBQUEsT0FBTyxDQUFDO0VBQ04sS0FBSyxFQUFFLEdBQUc7RUFDVixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJLEdBS1o7RUFORCxBQUdFLE9BSEssQ0FBQyxFQUFFLENBR1IsQ0FBQyxDQUFDO0lBQ0EsV0FBVyxFQUFFLElBQUksR0FDbEIiCn0= */ diff --git a/test/fixtures/source-map/expected.map b/test/fixtures/source-map/expected.map index f96ab927d..bd437653e 100644 --- a/test/fixtures/source-map/expected.map +++ b/test/fixtures/source-map/expected.map @@ -5,5 +5,5 @@ "index.scss" ], "names": [], - "mappings": "AAAA,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI,GACb;;AAED,AAAQ,OAAD,CAAC,EAAE,CAAC;EACT,eAAe,EAAE,IAAI,GACtB;;AAED,AAAQ,OAAD,CAAC,EAAE,CAAC;EACT,KAAK,EAAE,IAAI,GAKZ;EAND,AAGE,OAHK,CAAC,EAAE,CAGR,CAAC,CAAC;IACA,WAAW,EAAE,IAAI,GAClB" + "mappings": "AAAA,AAAA,OAAO,CAAC;EACN,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI,GACb;;AAED,AAAA,OAAO,CAAC,EAAE,CAAC;EACT,eAAe,EAAE,IAAI,GACtB;;AAED,AAAA,OAAO,CAAC,EAAE,CAAC;EACT,KAAK,EAAE,IAAI,GAKZ;EAND,AAGE,OAHK,CAAC,EAAE,CAGR,CAAC,CAAC;IACA,WAAW,EAAE,IAAI,GAClB" } From 0626988063ec4b56346cb29d41e9fa9e2223a1e2 Mon Sep 17 00:00:00 2001 From: Jimmy King Date: Tue, 6 Mar 2018 20:42:19 -0700 Subject: [PATCH 100/286] docs: updated @10xLaCroixDrinker's username --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a01fed36..67b14120e 100644 --- a/README.md +++ b/README.md @@ -457,7 +457,7 @@ This functionality has been moved to [`node-sass-middleware`](https://github.com ### DocPad Plugin -[@jking90](https://github.com/jking90) wrote a [DocPad](http://docpad.org/) plugin that compiles `.scss` files using node-sass: +[@10xLaCroixDrinker](https://github.com/10xLaCroixDrinker) wrote a [DocPad](http://docpad.org/) plugin that compiles `.scss` files using node-sass: ### Duo.js extension From 863f29f8fe29887a705513a9b74f84ca4b0f3784 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Mar 2018 12:25:35 +1100 Subject: [PATCH 101/286] Bump minimum nan@2.9.2 I ran into [an issue][1] compiling the iojs 3 binary on OSX. This sound affect anyone in reality but just in case. [1]: https://github.com/nodejs/nan/pull/728 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91b28442f..13b02e7c2 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.3.2", + "nan": "^2.9.2", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", "request": "~2.79.0", From a2d2a636339e74577d48a97c36d0900e77c9512f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Mar 2018 17:56:01 +1100 Subject: [PATCH 102/286] Pre-empt support for Node 10 Lets try to avoid the rush of issues when Node 10 is released next month. Node 10 is currently version [`62`][1] but is expected to be [`63`][2] before going stable. We need to pick one because of the ABI breakage betweem 62 and 63. If this bet pays off we save ourselves a bunch of time dealing with installation issues, and avoiding a new release. If we're wrong it's no different to any other major Node release. [1]: https://github.com/nodejs/node/blob/97595739/src/node_version.h#L112 [2]: https://github.com/nodejs/node/pull/19201 --- lib/extensions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/extensions.js b/lib/extensions.js index b4c406d7a..48e2c8ec5 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -75,6 +75,7 @@ function getHumanNodeVersion(abi) { case 53: return 'Electron 1.6.x'; case 57: return 'Node.js 8.x'; case 59: return 'Node.js 9.x'; + case 63: return 'Node.js 10.x'; default: return false; } } From b926705b3b6b96b7d0350da0f7199c0a5f5bfa4e Mon Sep 17 00:00:00 2001 From: William Date: Fri, 8 Dec 2017 09:22:31 +0100 Subject: [PATCH 103/286] Use watcher removed function on deleted Updated the node-sass cli script to use the watcher removed function instead of the 'deleted' function which is non-existent. --- bin/node-sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node-sass b/bin/node-sass index d80efb0fd..62abdb229 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -263,7 +263,7 @@ function watch(options, emitter) { }); gaze.on('deleted', function(file) { - handler(watcher.deleted(file)); + handler(watcher.removed(file)); }); } From 1ed5ce94a2d765034d44468461b9fa05293a9e36 Mon Sep 17 00:00:00 2001 From: Kristaps Austers Date: Sat, 21 Oct 2017 04:55:21 +0300 Subject: [PATCH 104/286] Allow setting of SASS_BINARY_DIR without setting explicit binary name --- lib/extensions.js | 39 ++++++++++++++++++++++++++++++++++++--- test/runtime.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/lib/extensions.js b/lib/extensions.js index 48e2c8ec5..4c410f5d5 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -7,7 +7,7 @@ var eol = require('os').EOL, pkg = require('../package.json'), mkdir = require('mkdirp'), path = require('path'), - defaultBinaryPath = path.join(__dirname, '..', 'vendor'), + defaultBinaryDir = path.join(__dirname, '..', 'vendor'), trueCasePathSync = require('true-case-path'); /** @@ -126,7 +126,7 @@ function getHumanEnvironment(env) { * @api public */ function getInstalledBinaries() { - return fs.readdirSync(defaultBinaryPath); + return fs.readdirSync(getBinaryDir()); } /** @@ -245,6 +245,38 @@ function getBinaryUrl() { return [site, 'v' + pkg.version, getBinaryName()].join('/'); } +/** + * Get binary dir. + * If environment variable SASS_BINARY_DIR, + * .npmrc variable sass_binary_dir or + * process argument --sass-binary-dir is provided, + * select it by appending binary name, otherwise + * use default binary dir. + * Once the primary selection is made, check if + * callers wants to throw if file not exists before + * returning. + * + * @api public + */ + +function getBinaryDir() { + var binaryDir; + + if (getArgument('--sass-binary-dir')) { + binaryDir = getArgument('--sass-binary-dir'); + } else if (process.env.SASS_BINARY_DIR) { + binaryDir = process.env.SASS_BINARY_DIR; + } else if (process.env.npm_config_sass_binary_dir) { + binaryDir = process.env.npm_config_sass_binary_dir; + } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryDir) { + binaryDir = pkg.nodeSassConfig.binaryDir; + } else { + binaryDir = defaultBinaryDir; + } + + return binaryDir; +} + /** * Get binary path. * If environment variable SASS_BINARY_PATH, @@ -271,7 +303,7 @@ function getBinaryPath() { } else if (pkg.nodeSassConfig && pkg.nodeSassConfig.binaryPath) { binaryPath = pkg.nodeSassConfig.binaryPath; } else { - binaryPath = path.join(defaultBinaryPath, getBinaryName().replace(/_(?=binding\.node)/, '/')); + binaryPath = path.join(getBinaryDir(), getBinaryName().replace(/_(?=binding\.node)/, '/')); } if (process.versions.modules < 46) { @@ -415,6 +447,7 @@ function getPlatformVariant() { module.exports.hasBinary = hasBinary; module.exports.getBinaryUrl = getBinaryUrl; module.exports.getBinaryName = getBinaryName; +module.exports.getBinaryDir = getBinaryDir; module.exports.getBinaryPath = getBinaryPath; module.exports.getBinaryCachePath = getBinaryCachePath; module.exports.getCachedBinary = getCachedBinary; diff --git a/test/runtime.js b/test/runtime.js index ab4b0c206..c4ae9d557 100644 --- a/test/runtime.js +++ b/test/runtime.js @@ -83,6 +83,37 @@ describe('runtime parameters', function() { }); }); + describe('SASS_BINARY_DIR', function() { + beforeEach(function() { + process.argv.push('--sass-binary-dir', 'aaa'); + process.env.SASS_BINARY_DIR = 'bbb'; + process.env.npm_config_sass_binary_dir = 'ccc'; + pkg.nodeSassConfig = { binaryDir: 'ddd' }; + }); + + it('command line argument', function() { + assert.equal(sass.getBinaryDir(), 'aaa'); + }); + + it('environment variable', function() { + process.argv = []; + assert.equal(sass.getBinaryDir(), 'bbb'); + }); + + it('npm config variable', function() { + process.argv = []; + process.env.SASS_BINARY_DIR = null; + assert.equal(sass.getBinaryDir(), 'ccc'); + }); + + it('package.json', function() { + process.argv = []; + process.env.SASS_BINARY_DIR = null; + process.env.npm_config_sass_binary_dir = null; + assert.equal(sass.getBinaryDir(), 'ddd'); + }); + }); + describe('SASS_BINARY_PATH', function() { beforeEach(function() { process.argv.push('--sass-binary-path', 'aaa_binding.node'); From 3331891a89fc65b91b2c184f9159ec189a80fd66 Mon Sep 17 00:00:00 2001 From: Kristaps Austers Date: Sun, 22 Oct 2017 03:57:18 +0300 Subject: [PATCH 105/286] Add SASS_BINARY_DIR to Readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 67b14120e..e0d09f59d 100644 --- a/README.md +++ b/README.md @@ -567,6 +567,7 @@ Variable name | .npmrc parameter | Process argument | Value SASS_BINARY_NAME | sass_binary_name | --sass-binary-name | path SASS_BINARY_SITE | sass_binary_site | --sass-binary-site | URL SASS_BINARY_PATH | sass_binary_path | --sass-binary-path | path +SASS_BINARY_DIR | sass_binary_dir | --sass-binary-dir | path These parameters can be used as environment variable: From 389835029f393bb8fdc85d26786c71bcca2845f7 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 22 Feb 2017 21:27:07 +1100 Subject: [PATCH 106/286] Install script should not write partially downloaded binaries Currently the binary download is streamed to disk once a 200 response has been recieved. When an error occurs during the download a partially downloaded binary is left on disk. Subsequent installs see the binary and bail out of re-downloading it. Worse yet those subsequent installs move the binary into the global cache so even removing node_modules will not remove the broken binary. With this patch the binary is only flushed to disk once it has been fully downloaded. Fixes #1882 Fixes #1888 --- scripts/install.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/install.js b/scripts/install.js index 099b2c2b8..35bcf14fe 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -58,6 +58,11 @@ function download(url, dest, cb) { reportError(['HTTP error', response.statusCode, response.statusMessage].join(' ')); } else { console.log('Download complete'); + + if (successful(response)) { + response.pipe(fs.createWriteStream(dest)); + } + cb(); } }) @@ -65,10 +70,6 @@ function download(url, dest, cb) { var length = parseInt(response.headers['content-length'], 10); var progress = log.newItem('', length); - if (successful(response)) { - response.pipe(fs.createWriteStream(dest)); - } - // The `progress` is true by default. However if it has not // been explicitly set it's `undefined` which is considered // as far as npm is concerned. From 7355176858d6ba79b21edb8c5f67dd99ec06e74d Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Mar 2018 20:02:00 +1100 Subject: [PATCH 107/286] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61141267..338cd015e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## v4.8.0 + +https://github.com/sass/node-sass/releases/tag/v4.8.0 + +## v4.7.2 + +https://github.com/sass/node-sass/releases/tag/v4.7.2 + +## v4.7.1 + +https://github.com/sass/node-sass/releases/tag/v4.7.1 + +## v4.7.0 + +https://github.com/sass/node-sass/releases/tag/v4.7.0 + ## v4.6.1 https://github.com/sass/node-sass/releases/tag/v4.6.1 From 0b6baf38271b6d55f8a23b09158228381fd24de3 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 10 Mar 2018 20:02:51 +1100 Subject: [PATCH 108/286] 4.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13b02e7c2..7d69017de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.7.2", + "version": "4.8.0", "libsass": "3.5.0", "description": "Wrapper around libsass", "license": "MIT", From b4f82d806cbc6796a03c3b266cee5463d2a5b0e4 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Mar 2018 17:43:35 +1100 Subject: [PATCH 109/286] Fix wrong binary encoding Due to #1912 we were writing binaries to disk with the encoding. We need to do some additional juggling when dealing with binary data. --- scripts/install.js | 10 ++++++---- scripts/util/downloadoptions.js | 3 ++- test/downloadoptions.js | 9 ++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/install.js b/scripts/install.js index 35bcf14fe..6febbe498 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -51,7 +51,7 @@ function download(url, dest, cb) { console.log('Downloading binary from', url); try { - request(url, downloadOptions(), function(err, response) { + request(url, downloadOptions(), function(err, response, buffer) { if (err) { reportError(err); } else if (!successful(response)) { @@ -60,10 +60,12 @@ function download(url, dest, cb) { console.log('Download complete'); if (successful(response)) { - response.pipe(fs.createWriteStream(dest)); + fs.createWriteStream(dest) + .on('error', cb) + .end(buffer, cb); + } else { + cb(); } - - cb(); } }) .on('response', function(response) { diff --git a/scripts/util/downloadoptions.js b/scripts/util/downloadoptions.js index b100d06e9..23529716f 100644 --- a/scripts/util/downloadoptions.js +++ b/scripts/util/downloadoptions.js @@ -18,7 +18,8 @@ module.exports = function() { timeout: 60000, headers: { 'User-Agent': userAgent(), - } + }, + encoding: null, }; var proxyConfig = proxy(); diff --git a/test/downloadoptions.js b/test/downloadoptions.js index 7a80175b3..18daeb9af 100644 --- a/test/downloadoptions.js +++ b/test/downloadoptions.js @@ -12,7 +12,8 @@ describe('util', function() { timeout: 60000, headers: { 'User-Agent': ua(), - } + }, + encoding: null, }; assert.deepEqual(opts(), expected); @@ -37,7 +38,8 @@ describe('util', function() { timeout: 60000, headers: { 'User-Agent': ua(), - } + }, + encoding: null, }; assert.deepEqual(opts(), expected); @@ -61,7 +63,8 @@ describe('util', function() { timeout: 60000, headers: { 'User-Agent': ua(), - } + }, + encoding: null, }; assert.deepEqual(opts(), expected); From a4564cca023c9c487b4bbb29c463114b12c287c3 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Mar 2018 20:44:27 +1100 Subject: [PATCH 110/286] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 338cd015e..7952408ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v4.8.1 + +https://github.com/sass/node-sass/releases/tag/v4.8.1 + ## v4.8.0 https://github.com/sass/node-sass/releases/tag/v4.8.0 From 96d0d0b1e49b716e5aa418c72dc29cff4454707f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Mar 2018 20:44:35 +1100 Subject: [PATCH 111/286] 4.8.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d69017de..6dedfc427 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.8.0", + "version": "4.8.1", "libsass": "3.5.0", "description": "Wrapper around libsass", "license": "MIT", From 37093575ee9736a513decb16e581f9fc789d56f7 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 13 Mar 2018 08:54:40 +1100 Subject: [PATCH 112/286] Bump LibSass to 3.5.1 Fixes #2280 --- package.json | 4 ++-- src/libsass/docs/implementations.md | 3 +++ src/libsass/src/check_nesting.cpp | 4 +--- src/libsass/src/context.cpp | 7 +++++-- src/libsass/src/eval.cpp | 1 + src/libsass/src/functions.cpp | 16 ++++++++-------- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 6dedfc427..f28eb7cf8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.8.1", - "libsass": "3.5.0", + "libsass": "3.5.1", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "^3.5.0", + "sass-spec": "^3.5.1", "unique-temp-dir": "^1.0.0" } } diff --git a/src/libsass/docs/implementations.md b/src/libsass/docs/implementations.md index 4321558c3..4814cdd8e 100644 --- a/src/libsass/docs/implementations.md +++ b/src/libsass/docs/implementations.md @@ -3,6 +3,9 @@ There are several implementations of `libsass` for a variety of languages. Here ### C * [sassc](https://github.com/hcatlin/sassc) +### Crystal +* [sass.cr](https://github.com/straight-shoota/sass.cr) + ### Elixir * [sass.ex](https://github.com/scottdavis/sass.ex) diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp index 952bb3380..19b43e0d8 100644 --- a/src/libsass/src/check_nesting.cpp +++ b/src/libsass/src/check_nesting.cpp @@ -108,9 +108,7 @@ namespace Sass { this->visit_children(i); if (Block_Ptr b = Cast(i->alternative())) { - for (auto n : i->alternative()->elements()) { - n->perform(this); - } + for (auto n : b->elements()) n->perform(this); } return i; diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp index 2c51db223..b64369f63 100644 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -653,8 +653,11 @@ namespace Sass { Expand expand(*this, &global, &backtrace); Cssize cssize(*this, &backtrace); CheckNesting check_nesting; - // check nesting - check_nesting(root); + // check nesting in all files + for (auto sheet : sheets) { + auto styles = sheet.second; + check_nesting(styles.root); + } // expand and eval the tree root = expand(root); // check nesting diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp index 9ea80c05d..67fa628a3 100644 --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -1562,6 +1562,7 @@ namespace Sass { v->value(ops[op](lv, rn.value() * f)); } + v->reduce(); v->pstate(pstate); return v.detach(); } diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp index 8461c9096..aaa3394fb 100644 --- a/src/libsass/src/functions.cpp +++ b/src/libsass/src/functions.cpp @@ -102,8 +102,6 @@ namespace Sass { namespace Functions { - static Number tmpnr(ParserState("[FN]"), 0); - inline void handle_utf8_error (const ParserState& pstate, Backtrace* backtrace) { try { @@ -159,7 +157,7 @@ namespace Sass { { // Minimal error handling -- the expectation is that built-ins will be written correctly! Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - tmpnr = val; + Number tmpnr(val); tmpnr.reduce(); double v = tmpnr.value(); if (!(lo <= v && v <= hi)) { @@ -175,7 +173,7 @@ namespace Sass { { // Minimal error handling -- the expectation is that built-ins will be written correctly! Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - tmpnr = val; + Number tmpnr(val); tmpnr.reduce(); return tmpnr; } @@ -193,7 +191,7 @@ namespace Sass { { // Minimal error handling -- the expectation is that built-ins will be written correctly! Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - tmpnr = val; + Number tmpnr(val); tmpnr.reduce(); /* if (tmpnr.unit() == "%") { @@ -210,7 +208,7 @@ namespace Sass { { // Minimal error handling -- the expectation is that built-ins will be written correctly! Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - tmpnr = val; + Number tmpnr(val); tmpnr.reduce(); return tmpnr.value(); } @@ -218,7 +216,8 @@ namespace Sass { double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) { Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - tmpnr = val; tmpnr.reduce(); + Number tmpnr(val); + tmpnr.reduce(); if (tmpnr.unit() == "%") { return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); } else { @@ -229,7 +228,8 @@ namespace Sass { inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) { Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - tmpnr = val; tmpnr.reduce(); + Number tmpnr(val); + tmpnr.reduce(); if (tmpnr.unit() == "%") { return std::min(std::max(tmpnr.value(), 0.0), 100.0); } else { From 7648fc461d68e2995278cdb7b2946d488a5e182f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 13 Mar 2018 20:16:46 +1100 Subject: [PATCH 113/286] 4.8.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f28eb7cf8..9d177e3cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.8.1", + "version": "4.8.2", "libsass": "3.5.1", "description": "Wrapper around libsass", "license": "MIT", From 84754d536838776db0856b81b168eec5c195cf28 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 17 Mar 2018 16:29:42 +1100 Subject: [PATCH 114/286] Bump LibSass 3.5.2 Fixes #2287 --- package.json | 2 +- src/libsass.gyp | 3 + src/libsass/Makefile.conf | 2 + src/libsass/src/ast.cpp | 39 +-- src/libsass/src/ast.hpp | 112 ++++---- src/libsass/src/ast_def_macros.hpp | 2 +- src/libsass/src/ast_fwd_decl.hpp | 3 + src/libsass/src/backtrace.cpp | 46 +++ src/libsass/src/backtrace.hpp | 61 +--- src/libsass/src/bind.cpp | 23 +- src/libsass/src/check_nesting.cpp | 129 +++++---- src/libsass/src/check_nesting.hpp | 16 +- src/libsass/src/context.cpp | 42 +-- src/libsass/src/context.hpp | 5 +- src/libsass/src/cssize.cpp | 16 +- src/libsass/src/cssize.hpp | 8 +- src/libsass/src/debugger.hpp | 3 +- src/libsass/src/error_handling.cpp | 127 ++++---- src/libsass/src/error_handling.hpp | 91 +++--- src/libsass/src/eval.cpp | 367 ++++++------------------ src/libsass/src/eval.hpp | 16 +- src/libsass/src/expand.cpp | 62 ++-- src/libsass/src/expand.hpp | 5 +- src/libsass/src/extend.cpp | 4 +- src/libsass/src/file.cpp | 21 +- src/libsass/src/functions.cpp | 185 ++++++------ src/libsass/src/functions.hpp | 4 +- src/libsass/src/operators.cpp | 240 ++++++++++++++++ src/libsass/src/operators.hpp | 30 ++ src/libsass/src/output.cpp | 11 +- src/libsass/src/parser.cpp | 131 +++++---- src/libsass/src/parser.hpp | 21 +- src/libsass/src/sass_context.cpp | 18 +- src/libsass/src/sass_values.cpp | 25 +- src/libsass/src/util.cpp | 20 +- src/libsass/src/util.hpp | 3 +- src/libsass/win/libsass.targets | 2 + src/libsass/win/libsass.vcxproj.filters | 6 + 38 files changed, 1014 insertions(+), 887 deletions(-) create mode 100644 src/libsass/src/backtrace.cpp create mode 100644 src/libsass/src/operators.cpp create mode 100644 src/libsass/src/operators.hpp diff --git a/package.json b/package.json index 9d177e3cc..c0772a83b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.8.2", - "libsass": "3.5.1", + "libsass": "3.5.2", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", diff --git a/src/libsass.gyp b/src/libsass.gyp index 0fd857d81..add96e89a 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -13,6 +13,7 @@ 'sources': [ 'libsass/src/ast.cpp', 'libsass/src/ast_fwd_decl.cpp', + 'libsass/src/backtrace.cpp', 'libsass/src/base64vlq.cpp', 'libsass/src/bind.cpp', 'libsass/src/cencode.c', @@ -35,6 +36,8 @@ 'libsass/src/listize.cpp', 'libsass/src/memory/SharedPtr.cpp', 'libsass/src/node.cpp', + 'libsass/src/operators.cpp', + 'libsass/src/operators.hpp', 'libsass/src/output.cpp', 'libsass/src/parser.cpp', 'libsass/src/plugins.cpp', diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf index a8954edea..5ba968b68 100644 --- a/src/libsass/Makefile.conf +++ b/src/libsass/Makefile.conf @@ -41,6 +41,8 @@ SOURCES = \ sass_context.cpp \ sass_functions.cpp \ sass2scss.cpp \ + backtrace.cpp \ + operators.cpp \ to_c.cpp \ to_value.cpp \ source_map.cpp \ diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp index 269e122b9..c3b38efb9 100644 --- a/src/libsass/src/ast.cpp +++ b/src/libsass/src/ast.cpp @@ -837,7 +837,7 @@ namespace Sass { return lhs_list->is_superselector_of(rhs_list); } } - error("is_superselector expected a Selector_List", sub->pstate()); + coreError("is_superselector expected a Selector_List", sub->pstate()); return false; } @@ -1171,7 +1171,7 @@ namespace Sass { // check if we need to append some headers // then we need to check for the combinator // only then we can safely set the new tail - void Complex_Selector::append(Complex_Selector_Obj ss) + void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces) { Complex_Selector_Obj t = ss->tail(); @@ -1185,7 +1185,8 @@ namespace Sass { // append old headers if (h && h->length()) { if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - error("Invalid parent selector", pstate_); + traces.push_back(Backtrace(pstate())); + throw Exception::InvalidParent(this, traces, ss); } else if (last()->head_ && last()->head_->length()) { Compound_Selector_Obj rh = last()->head(); size_t i; @@ -1258,21 +1259,21 @@ namespace Sass { return list; } - Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, bool implicit_parent) + Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) { if (!this->has_parent_ref()) return this; Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); Selector_List_Ptr ps = pstack.back(); for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { for (size_t si = 0, sL = this->length(); si < sL; ++si) { - Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, implicit_parent); + Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent); ss->concat(rv); } } return ss; } - Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, bool implicit_parent) + Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) { Complex_Selector_Obj tail = this->tail(); Compound_Selector_Obj head = this->head(); @@ -1285,7 +1286,7 @@ namespace Sass { } // first resolve_parent_refs the tail (which may return an expanded list) - Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, implicit_parent) : 0; + Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0; if (head && head->length() > 0) { @@ -1331,7 +1332,7 @@ namespace Sass { // keep old parser state s->pstate(pstate()); // append new tail - s->append(ss); + s->append(ss, traces); retval->append(s); } } @@ -1346,7 +1347,8 @@ namespace Sass { // this is only if valid if the parent has no trailing op // otherwise we cannot append more simple selectors to head if (parent->last()->combinator() != ANCESTOR_OF) { - throw Exception::InvalidParent(parent, ss); + traces.push_back(Backtrace(pstate())); + throw Exception::InvalidParent(parent, traces, ss); } ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); @@ -1369,7 +1371,7 @@ namespace Sass { // keep old parser state s->pstate(pstate()); // append new tail - s->append(ss); + s->append(ss, traces); retval->append(s); } } @@ -1406,7 +1408,7 @@ namespace Sass { for (Simple_Selector_Obj ss : head->elements()) { if (Wrapped_Selector_Ptr ws = Cast(ss)) { if (Selector_List_Ptr sl = Cast(ws->selector())) { - if (parents) ws->selector(sl->resolve_parent_refs(pstack, implicit_parent)); + if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent)); } } } @@ -1690,7 +1692,7 @@ namespace Sass { } if (!pIter->head() || pIter->tail()) { - error("nested selectors may not be extended", c->pstate()); + coreError("nested selectors may not be extended", c->pstate()); } compound_sel->is_optional(extendee->is_optional()); @@ -1766,31 +1768,31 @@ namespace Sass { { if (!a->name().empty()) { if (has_keyword_argument()) { - error("named arguments must precede variable-length argument", a->pstate()); + coreError("named arguments must precede variable-length argument", a->pstate()); } has_named_arguments(true); } else if (a->is_rest_argument()) { if (has_rest_argument()) { - error("functions and mixins may only be called with one variable-length argument", a->pstate()); + coreError("functions and mixins may only be called with one variable-length argument", a->pstate()); } if (has_keyword_argument_) { - error("only keyword arguments may follow variable arguments", a->pstate()); + coreError("only keyword arguments may follow variable arguments", a->pstate()); } has_rest_argument(true); } else if (a->is_keyword_argument()) { if (has_keyword_argument()) { - error("functions and mixins may only be called with one keyword argument", a->pstate()); + coreError("functions and mixins may only be called with one keyword argument", a->pstate()); } has_keyword_argument(true); } else { if (has_rest_argument()) { - error("ordinal arguments must precede variable-length arguments", a->pstate()); + coreError("ordinal arguments must precede variable-length arguments", a->pstate()); } if (has_named_arguments()) { - error("ordinal arguments must precede named arguments", a->pstate()); + coreError("ordinal arguments must precede named arguments", a->pstate()); } } } @@ -1907,6 +1909,7 @@ namespace Sass { l.normalize(); r.normalize(); Units &lhs_unit = l, &rhs_unit = r; if (!(lhs_unit == rhs_unit)) { + /* ToDo: do we always get usefull backtraces? */ throw Exception::IncompatibleUnits(rhs, *this); } return lhs_unit < rhs_unit || diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp index 8bd2e6af3..a2be8685c 100644 --- a/src/libsass/src/ast.hpp +++ b/src/libsass/src/ast.hpp @@ -572,13 +572,15 @@ namespace Sass { // Trace. ///////////////// class Trace : public Has_Block { + ADD_CONSTREF(char, type) ADD_CONSTREF(std::string, name) public: - Trace(ParserState pstate, std::string n, Block_Obj b = 0) - : Has_Block(pstate, b), name_(n) + Trace(ParserState pstate, std::string n, Block_Obj b = 0, char type = 'm') + : Has_Block(pstate, b), type_(type), name_(n) { } Trace(const Trace* ptr) : Has_Block(ptr), + type_(ptr->type_), name_(ptr->name_) { } ATTACH_AST_OPERATIONS(Trace) @@ -943,7 +945,7 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////////// struct Backtrace; typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); class Definition : public Has_Block { public: enum Type { MIXIN, FUNCTION }; @@ -1184,6 +1186,27 @@ namespace Sass { } } + inline static const std::string sass_op_separator(enum Sass_OP op) { + switch (op) { + case AND: return "&&"; + case OR: return "||"; + case EQ: return "=="; + case NEQ: return "!="; + case GT: return ">"; + case GTE: return ">="; + case LT: return "<"; + case LTE: return "<="; + case ADD: return "+"; + case SUB: return "-"; + case MUL: return "*"; + case DIV: return "/"; + case MOD: return "%"; + // this is only used internally! + case NUM_OPS: return "[OPS]"; + default: return "invalid"; + } + } + ////////////////////////////////////////////////////////////////////////// // Binary expressions. Represents logical, relational, and arithmetic // operations. Templatized to avoid large switch statements and repetitive @@ -1208,44 +1231,10 @@ namespace Sass { hash_(ptr->hash_) { } const std::string type_name() { - switch (optype()) { - case AND: return "and"; - case OR: return "or"; - case EQ: return "eq"; - case NEQ: return "neq"; - case GT: return "gt"; - case GTE: return "gte"; - case LT: return "lt"; - case LTE: return "lte"; - case ADD: return "add"; - case SUB: return "sub"; - case MUL: return "mul"; - case DIV: return "div"; - case MOD: return "mod"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } + return sass_op_to_name(optype()); } const std::string separator() { - switch (optype()) { - case AND: return "&&"; - case OR: return "||"; - case EQ: return "=="; - case NEQ: return "!="; - case GT: return ">"; - case GTE: return ">="; - case LT: return "<"; - case LTE: return "<="; - case ADD: return "+"; - case SUB: return "-"; - case MUL: return "*"; - case DIV: return "/"; - case MOD: return "%"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } + return sass_op_separator(optype()); } bool is_left_interpolant(void) const; bool is_right_interpolant(void) const; @@ -1360,7 +1349,7 @@ namespace Sass { : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) { if (!name_.empty() && is_rest_argument_) { - error("variable-length argument may not be passed by name", pstate_); + coreError("variable-length argument may not be passed by name", pstate_); } } Argument(const Argument* ptr) @@ -1372,7 +1361,7 @@ namespace Sass { hash_(ptr->hash_) { if (!name_.empty() && is_rest_argument_) { - error("variable-length argument may not be passed by name", pstate_); + coreError("variable-length argument may not be passed by name", pstate_); } } @@ -1779,15 +1768,16 @@ namespace Sass { // evaluation phase. /////////////////////////////////////////////////////////////////////// class String_Schema : public String, public Vectorized { - // ADD_PROPERTY(bool, has_interpolants) + ADD_PROPERTY(bool, css) size_t hash_; public: - String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false) - : String(pstate), Vectorized(size), hash_(0) + String_Schema(ParserState pstate, size_t size = 0, bool css = true) + : String(pstate), Vectorized(size), css_(css), hash_(0) { concrete_type(STRING); } String_Schema(const String_Schema* ptr) : String(ptr), Vectorized(*ptr), + css_(ptr->css_), hash_(ptr->hash_) { concrete_type(STRING); } @@ -1840,17 +1830,17 @@ namespace Sass { value_(ptr->value_), hash_(ptr->hash_) { } - String_Constant(ParserState pstate, std::string val) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val)), hash_(0) + String_Constant(ParserState pstate, std::string val, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0) { } - String_Constant(ParserState pstate, const char* beg) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg))), hash_(0) + String_Constant(ParserState pstate, const char* beg, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0) { } - String_Constant(ParserState pstate, const char* beg, const char* end) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg))), hash_(0) + String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) { } - String_Constant(ParserState pstate, const Token& tok) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end))), hash_(0) + String_Constant(ParserState pstate, const Token& tok, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) { } std::string type() const { return "string"; } static std::string type_name() { return "string"; } @@ -1883,8 +1873,8 @@ namespace Sass { public: String_Quoted(ParserState pstate, std::string val, char q = 0, bool keep_utf8_escapes = false, bool skip_unquoting = false, - bool strict_unquoting = true) - : String_Constant(pstate, val) + bool strict_unquoting = true, bool css = true) + : String_Constant(pstate, val, css) { if (skip_unquoting == false) { value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); @@ -2217,22 +2207,22 @@ namespace Sass { { if (p->default_value()) { if (has_rest_parameter()) { - error("optional parameters may not be combined with variable-length parameters", p->pstate()); + coreError("optional parameters may not be combined with variable-length parameters", p->pstate()); } has_optional_parameters(true); } else if (p->is_rest_parameter()) { if (has_rest_parameter()) { - error("functions and mixins cannot have more than one variable-length parameter", p->pstate()); + coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate()); } has_rest_parameter(true); } else { if (has_rest_parameter()) { - error("required parameters must precede variable-length parameters", p->pstate()); + coreError("required parameters must precede variable-length parameters", p->pstate()); } if (has_optional_parameters()) { - error("required parameters must precede optional parameters", p->pstate()); + coreError("required parameters must precede optional parameters", p->pstate()); } } } @@ -2871,13 +2861,13 @@ namespace Sass { Complex_Selector_Obj innermost() { return last(); }; size_t length() const; - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, bool implicit_parent = true); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs); Combinator clear_innermost(); - void append(Complex_Selector_Obj); + void append(Complex_Selector_Obj, Backtraces& traces); void set_innermost(Complex_Selector_Obj, Combinator); virtual size_t hash() { @@ -2993,7 +2983,7 @@ namespace Sass { virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; void remove_parent_selectors(); - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, bool implicit_parent = true); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); diff --git a/src/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp index 2d399471c..b3a7f8d16 100644 --- a/src/libsass/src/ast_def_macros.hpp +++ b/src/libsass/src/ast_def_macros.hpp @@ -33,7 +33,7 @@ class LocalOption { #define NESTING_GUARD(name) \ LocalOption cnt_##name(name, name + 1); \ - if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate); \ + if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ #define ATTACH_OPERATIONS()\ virtual void perform(Operation* op) { (*op)(this); }\ diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp index 147b8b559..5145a092b 100644 --- a/src/libsass/src/ast_fwd_decl.hpp +++ b/src/libsass/src/ast_fwd_decl.hpp @@ -11,6 +11,7 @@ #include #include #include "memory/SharedPtr.hpp" +#include "sass/functions.h" ///////////////////////////////////////////// // Forward declarations for the AST visitors. @@ -418,6 +419,8 @@ namespace Sass { typedef std::set CompoundSelectorSet; typedef std::unordered_set SimpleSelectorDict; + typedef std::vector* ImporterStack; + // only to switch implementations for testing #define environment_map std::map diff --git a/src/libsass/src/backtrace.cpp b/src/libsass/src/backtrace.cpp new file mode 100644 index 000000000..8da963a72 --- /dev/null +++ b/src/libsass/src/backtrace.cpp @@ -0,0 +1,46 @@ +#include "backtrace.hpp" + +namespace Sass { + + const std::string traces_to_string(Backtraces traces, std::string indent) { + + std::stringstream ss; + std::string cwd(File::get_cwd()); + + bool first = true; + size_t i_beg = traces.size() - 1; + size_t i_end = std::string::npos; + for (size_t i = i_beg; i != i_end; i --) { + + const Backtrace& trace = traces[i]; + + // make path relative to the current directory + std::string rel_path(File::abs2rel(trace.pstate.path, cwd, cwd)); + + // skip functions on error cases (unsure why ruby sass does this) + // if (trace.caller.substr(0, 6) == ", in f") continue; + + if (first) { + ss << indent; + ss << "on line "; + ss << trace.pstate.line + 1; + ss << " of " << rel_path; + // ss << trace.caller; + first = false; + } else { + ss << trace.caller; + ss << std::endl; + ss << indent; + ss << "from line "; + ss << trace.pstate.line + 1; + ss << " of " << rel_path; + } + + } + + ss << std::endl; + return ss.str(); + + } + +}; diff --git a/src/libsass/src/backtrace.hpp b/src/libsass/src/backtrace.hpp index 57ce1786f..72d5fe517 100644 --- a/src/libsass/src/backtrace.hpp +++ b/src/libsass/src/backtrace.hpp @@ -1,75 +1,28 @@ #ifndef SASS_BACKTRACE_H #define SASS_BACKTRACE_H +#include #include - #include "file.hpp" #include "position.hpp" namespace Sass { - struct Backtrace { - Backtrace* parent; ParserState pstate; - std::string caller; + std::string caller; - Backtrace(Backtrace* prn, ParserState pstate, std::string c) - : parent(prn), - pstate(pstate), + Backtrace(ParserState pstate, std::string c = "") + : pstate(pstate), caller(c) { } - const std::string to_string(bool warning = false) - { - size_t i = -1; - std::stringstream ss; - std::string cwd(Sass::File::get_cwd()); - Backtrace* this_point = this; - - if (!warning) ss << std::endl << "Backtrace:"; - // the first tracepoint (which is parent-less) is an empty placeholder - while (this_point->parent) { - - // make path relative to the current directory - std::string rel_path(Sass::File::abs2rel(this_point->pstate.path, cwd, cwd)); - - if (warning) { - ss << std::endl - << "\t" - << (++i == 0 ? "on" : "from") - << " line " - << this_point->pstate.line + 1 - << " of " - << rel_path; - } else { - ss << std::endl - << "\t" - << rel_path - << ":" - << this_point->pstate.line + 1 - << this_point->parent->caller; - } - - this_point = this_point->parent; - } - - return ss.str(); - } + }; - size_t depth() - { - size_t d = std::string::npos; - Backtrace* p = parent; - while (p) { - ++d; - p = p->parent; - } - return d; - } + typedef std::vector Backtraces; - }; + const std::string traces_to_string(Backtraces traces, std::string indent = "\t"); } diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp index e579deac6..ec20ac838 100644 --- a/src/libsass/src/bind.cpp +++ b/src/libsass/src/bind.cpp @@ -2,6 +2,7 @@ #include "bind.hpp" #include "ast.hpp" #include "context.hpp" +#include "expand.hpp" #include "eval.hpp" #include #include @@ -53,7 +54,7 @@ namespace Sass { std::stringstream msg; msg << "wrong number of arguments (" << LA << " for " << LP << ")"; msg << " for `" << name << "'"; - return error(msg.str(), as->pstate()); + return error(msg.str(), as->pstate(), eval->exp.traces); } Parameter_Obj p = ps->at(ip); @@ -106,7 +107,8 @@ namespace Sass { false, false)); } else { - throw Exception::InvalidVarKwdType(key->pstate(), key->inspect(), a); + eval->exp.traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); } } @@ -219,13 +221,16 @@ namespace Sass { for (auto key : argmap->keys()) { String_Constant_Ptr val = Cast(key); - if (val == NULL) throw Exception::InvalidVarKwdType(key->pstate(), key->inspect(), a); + if (val == NULL) { + eval->exp.traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); + } std::string param = "$" + unquote(val->value()); if (!param_map.count(param)) { std::stringstream msg; msg << callee << " has no parameter named " << param; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } env->local_frame()[param] = argmap->at(key); } @@ -240,7 +245,7 @@ namespace Sass { std::stringstream msg; msg << "parameter " << p->name() << " provided more than once in call to " << callee; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } // ordinal arg -- bind it to the next param env->local_frame()[p->name()] = a->value(); @@ -254,7 +259,7 @@ namespace Sass { } else { std::stringstream msg; msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } } if (param_map[a->name()]) { @@ -262,14 +267,14 @@ namespace Sass { std::stringstream msg; msg << "argument " << a->name() << " of " << callee << "cannot be used as named argument"; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } } if (env->has_local(a->name())) { std::stringstream msg; msg << "parameter " << p->name() << "provided more than once in call to " << callee; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } env->local_frame()[a->name()] = a->value(); } @@ -294,7 +299,7 @@ namespace Sass { } else { // param is unbound and has no default value -- error - throw Exception::MissingArgument(as->pstate(), name, leftover->name(), type); + throw Exception::MissingArgument(as->pstate(), eval->exp.traces, name, leftover->name(), type); } } } diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp index 19b43e0d8..880bcca37 100644 --- a/src/libsass/src/check_nesting.cpp +++ b/src/libsass/src/check_nesting.cpp @@ -7,10 +7,15 @@ namespace Sass { CheckNesting::CheckNesting() : parents(std::vector()), - parent(0), - current_mixin_definition(0) + traces(std::vector()), + parent(0), current_mixin_definition(0) { } + void error(AST_Node_Ptr node, Backtraces traces, std::string msg) { + traces.push_back(Backtrace(node->pstate())); + throw Exception::InvalidSass(node->pstate(), traces, msg); + } + Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) { Statement_Ptr old_parent = this->parent; @@ -62,6 +67,12 @@ namespace Sass { Block_Ptr b = Cast(parent); + if (Trace_Ptr trace = Cast(parent)) { + if (trace->type() == 'i') { + this->traces.push_back(Backtrace(trace->pstate())); + } + } + if (!b) { if (Has_Block_Ptr bb = Cast(parent)) { b = bb->block(); @@ -73,9 +84,16 @@ namespace Sass { n->perform(this); } } + this->parent = old_parent; this->parents.pop_back(); + if (Trace_Ptr trace = Cast(parent)) { + if (trace->type() == 'i') { + this->traces.pop_back(); + } + } + return b; } @@ -126,75 +144,69 @@ namespace Sass { if (!this->parent) return true; if (Cast(node)) - { this->invalid_content_parent(this->parent); } + { this->invalid_content_parent(this->parent, node); } if (is_charset(node)) - { this->invalid_charset_parent(this->parent); } + { this->invalid_charset_parent(this->parent, node); } if (Cast(node)) - { this->invalid_extend_parent(this->parent); } + { this->invalid_extend_parent(this->parent, node); } // if (Cast(node)) // { this->invalid_import_parent(this->parent); } if (this->is_mixin(node)) - { this->invalid_mixin_definition_parent(this->parent); } + { this->invalid_mixin_definition_parent(this->parent, node); } if (this->is_function(node)) - { this->invalid_function_parent(this->parent); } + { this->invalid_function_parent(this->parent, node); } if (this->is_function(this->parent)) { this->invalid_function_child(node); } - if (Cast(node)) - { this->invalid_prop_parent(this->parent); } + if (Declaration_Ptr d = Cast(node)) + { + this->invalid_prop_parent(this->parent, node); + this->invalid_value_child(d->value()); + } if (Cast(this->parent)) { this->invalid_prop_child(node); } if (Cast(node)) - { this->invalid_return_parent(this->parent); } + { this->invalid_return_parent(this->parent, node); } return true; } - void CheckNesting::invalid_content_parent(Statement_Ptr parent) + void CheckNesting::invalid_content_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!this->current_mixin_definition) { - throw Exception::InvalidSass( - parent->pstate(), - "@content may only be used within a mixin." - ); + error(node, traces, "@content may only be used within a mixin."); } } - void CheckNesting::invalid_charset_parent(Statement_Ptr parent) + void CheckNesting::invalid_charset_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!( is_root_node(parent) )) { - throw Exception::InvalidSass( - parent->pstate(), - "@charset may only be used at the root of a document." - ); + error(node, traces, "@charset may only be used at the root of a document."); } } - void CheckNesting::invalid_extend_parent(Statement_Ptr parent) + void CheckNesting::invalid_extend_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!( Cast(parent) || Cast(parent) || is_mixin(parent) )) { - throw Exception::InvalidSass( - parent->pstate(), - "Extend directives may only be used within rules." - ); + error(node, traces, "Extend directives may only be used within rules."); } } - // void CheckNesting::invalid_import_parent(Statement_Ptr parent) + // void CheckNesting::invalid_import_parent(Statement_Ptr parent, AST_Node_Ptr node) // { // for (auto pp : this->parents) { // if ( @@ -206,10 +218,7 @@ namespace Sass { // Cast(pp) || // is_mixin(pp) // ) { - // throw Exception::InvalidSass( - // parent->pstate(), - // "Import directives may not be defined within control directives or other mixins." - // ); + // error(node, traces, "Import directives may not be defined within control directives or other mixins."); // } // } @@ -218,14 +227,11 @@ namespace Sass { // } // if (false/*n.css_import?*/) { - // throw Exception::InvalidSass( - // parent->pstate(), - // "CSS import directives may only be used at the root of a document." - // ); + // error(node, traces, "CSS import directives may only be used at the root of a document."); // } // } - void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent) + void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent, AST_Node_Ptr node) { for (Statement_Ptr pp : this->parents) { if ( @@ -237,15 +243,12 @@ namespace Sass { Cast(pp) || is_mixin(pp) ) { - throw Exception::InvalidSass( - parent->pstate(), - "Mixins may not be defined within control directives or other mixins." - ); + error(node, traces, "Mixins may not be defined within control directives or other mixins."); } } } - void CheckNesting::invalid_function_parent(Statement_Ptr parent) + void CheckNesting::invalid_function_parent(Statement_Ptr parent, AST_Node_Ptr node) { for (Statement_Ptr pp : this->parents) { if ( @@ -257,10 +260,7 @@ namespace Sass { Cast(pp) || is_mixin(pp) ) { - throw Exception::InvalidSass( - parent->pstate(), - "Functions may not be defined within control directives or other mixins." - ); + error(node, traces, "Functions may not be defined within control directives or other mixins."); } } } @@ -282,10 +282,7 @@ namespace Sass { Cast(child) || Cast(child) )) { - throw Exception::InvalidSass( - child->pstate(), - "Functions can only contain variable declarations and control directives." - ); + error(child, traces, "Functions can only contain variable declarations and control directives."); } } @@ -301,14 +298,11 @@ namespace Sass { Cast(child) || Cast(child) )) { - throw Exception::InvalidSass( - child->pstate(), - "Illegal nesting: Only properties may be nested beneath properties." - ); + error(child, traces, "Illegal nesting: Only properties may be nested beneath properties."); } } - void CheckNesting::invalid_prop_parent(Statement_Ptr parent) + void CheckNesting::invalid_prop_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!( is_mixin(parent) || @@ -318,20 +312,31 @@ namespace Sass { Cast(parent) || Cast(parent) )) { - throw Exception::InvalidSass( - parent->pstate(), - "Properties are only allowed within rules, directives, mixin includes, or other properties." - ); + error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties."); + } + } + + void CheckNesting::invalid_value_child(AST_Node_Ptr d) + { + if (Map_Ptr m = Cast(d)) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::InvalidValue(traces, *m); } + if (Number_Ptr n = Cast(d)) { + if (!n->is_valid_css_unit()) { + traces.push_back(Backtrace(n->pstate())); + throw Exception::InvalidValue(traces, *n); + } + } + + // error(dbg + " isn't a valid CSS value.", m->pstate(),); + } - void CheckNesting::invalid_return_parent(Statement_Ptr parent) + void CheckNesting::invalid_return_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!this->is_function(parent)) { - throw Exception::InvalidSass( - parent->pstate(), - "@return may only be used within a function." - ); + error(node, traces, "@return may only be used within a function."); } } diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp index 5ba303ee1..62c38d9dc 100644 --- a/src/libsass/src/check_nesting.hpp +++ b/src/libsass/src/check_nesting.hpp @@ -9,6 +9,7 @@ namespace Sass { class CheckNesting : public Operation_CRTP { std::vector parents; + Backtraces traces; Statement_Ptr parent; Definition_Ptr current_mixin_definition; @@ -34,17 +35,18 @@ namespace Sass { } private: - void invalid_content_parent(Statement_Ptr); - void invalid_charset_parent(Statement_Ptr); - void invalid_extend_parent(Statement_Ptr); + void invalid_content_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_charset_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_extend_parent(Statement_Ptr, AST_Node_Ptr); // void invalid_import_parent(Statement_Ptr); - void invalid_mixin_definition_parent(Statement_Ptr); - void invalid_function_parent(Statement_Ptr); + void invalid_mixin_definition_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_function_parent(Statement_Ptr, AST_Node_Ptr); void invalid_function_child(Statement_Ptr); void invalid_prop_child(Statement_Ptr); - void invalid_prop_parent(Statement_Ptr); - void invalid_return_parent(Statement_Ptr); + void invalid_prop_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_return_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_value_child(AST_Node_Ptr); bool is_transparent_parent(Statement_Ptr, Statement_Ptr); diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp index b64369f63..dae2cbd75 100644 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -74,6 +74,7 @@ namespace Sass { subset_map(), import_stack(), callee_stack(), + traces(), c_compiler(NULL), c_headers (std::vector()), @@ -250,10 +251,9 @@ namespace Sass { return vec; } - // register include with resolved path and its content // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res, ParserState* prstate) + void Context::register_resource(const Include& inc, const Resource& res) { // do not parse same resource twice @@ -301,21 +301,22 @@ namespace Sass { for (size_t i = 0; i < import_stack.size() - 2; ++i) { auto parent = import_stack[i]; if (std::strcmp(parent->abs_path, import->abs_path) == 0) { + std::string cwd(File::get_cwd()); + // make path relative to the current directory std::string stack("An @import loop has been found:"); for (size_t n = 1; n < i + 2; ++n) { - stack += "\n " + std::string(import_stack[n]->imp_path) + - " imports " + std::string(import_stack[n+1]->imp_path); + stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + + " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); } // implement error throw directly until we // decided how to handle full stack traces - ParserState state = prstate ? *prstate : pstate; - throw Exception::InvalidSyntax(state, stack, &import_stack); + throw Exception::InvalidSyntax(pstate, traces, stack); // error(stack, prstate ? *prstate : pstate, import_stack); } } // create a parser instance from the given c_str buffer - Parser p(Parser::from_c_str(contents, *this, pstate)); + Parser p(Parser::from_c_str(contents, *this, traces, pstate)); // do not yet dispose these buffers sass_import_take_source(import); sass_import_take_srcmap(import); @@ -330,7 +331,15 @@ namespace Sass { ast_pair(inc.abs_path, { res, root }); // register resulting resource sheets.insert(ast_pair); + } + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) + { + traces.push_back(Backtrace(prstate)); + register_resource(inc, res); + traces.pop_back(); } // Add a new import to the context (called from `import_url`) @@ -350,7 +359,7 @@ namespace Sass { for (size_t i = 0, L = resolved.size(); i < L; ++i) { msg_stream << " " << resolved[i].imp_path << "\n"; } msg_stream << "Please delete or rename all but one of these files." << "\n"; - error(msg_stream.str(), pstate); + error(msg_stream.str(), pstate, traces); } // process the resolved entry @@ -362,7 +371,7 @@ namespace Sass { // the memory buffer returned must be freed by us! if (char* contents = read_file(resolved[0].abs_path)) { // register the newly resolved file resource - register_resource(resolved[0], { contents, 0 }, &pstate); + register_resource(resolved[0], { contents, 0 }, pstate); // return resolved entry return resolved[0]; } @@ -403,7 +412,7 @@ namespace Sass { const Importer importer(imp_path, ctx_path); Include include(load_import(importer, pstate)); if (include.abs_path.empty()) { - error("File to import not found or unreadable: " + imp_path + ".\nParent style sheet: " + ctx_path, pstate); + error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); } imp->incs().push_back(include); } @@ -448,9 +457,9 @@ namespace Sass { // handle error message passed back from custom importer // it may (or may not) override the line and column info if (const char* err_message = sass_import_get_error_message(include_ent)) { - if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, &pstate); - if (line == std::string::npos && column == std::string::npos) error(err_message, pstate); - else error(err_message, ParserState(ctx_path, source, Position(line, column))); + if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); + if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); + else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); } // content for import was set else if (source) { @@ -462,7 +471,7 @@ namespace Sass { // attach information to AST node imp->incs().push_back(include); // register the resource buffers - register_resource(include, { source, srcmap }, &pstate); + register_resource(include, { source, srcmap }, pstate); } // only a path was retuned // try to load it like normal @@ -648,10 +657,9 @@ namespace Sass { for (size_t i = 0, S = c_functions.size(); i < S; ++i) { register_c_function(*this, &global, c_functions[i]); } // create initial backtrace entry - Backtrace backtrace(0, ParserState("", 0), ""); // create crtp visitor objects - Expand expand(*this, &global, &backtrace); - Cssize cssize(*this, &backtrace); + Expand expand(*this, &global); + Cssize cssize(*this); CheckNesting check_nesting; // check nesting in all files for (auto sheet : sheets) { diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp index 07a21a5d2..d3caba13e 100644 --- a/src/libsass/src/context.hpp +++ b/src/libsass/src/context.hpp @@ -15,6 +15,7 @@ #include "environment.hpp" #include "source_map.hpp" #include "subset_map.hpp" +#include "backtrace.hpp" #include "output.hpp" #include "plugins.hpp" #include "file.hpp" @@ -54,6 +55,7 @@ namespace Sass { Subset_Map subset_map; std::vector import_stack; std::vector callee_stack; + std::vector traces; struct Sass_Compiler* c_compiler; @@ -94,7 +96,8 @@ namespace Sass { virtual char* render(Block_Obj root); virtual char* render_srcmap(); - void register_resource(const Include&, const Resource&, ParserState* = 0); + void register_resource(const Include&, const Resource&); + void register_resource(const Include&, const Resource&, ParserState&); std::vector find_includes(const Importer& import); Include load_import(const Importer&, ParserState pstate); diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp index e1f083c5c..4c062a628 100644 --- a/src/libsass/src/cssize.cpp +++ b/src/libsass/src/cssize.cpp @@ -5,15 +5,14 @@ #include "cssize.hpp" #include "context.hpp" -#include "backtrace.hpp" namespace Sass { - Cssize::Cssize(Context& ctx, Backtrace* bt) + Cssize::Cssize(Context& ctx) : ctx(ctx), + traces(ctx.traces), block_stack(std::vector()), - p_stack(std::vector()), - backtrace(bt) + p_stack(std::vector()) { } Statement_Ptr Cssize::parent() @@ -33,7 +32,10 @@ namespace Sass { Statement_Ptr Cssize::operator()(Trace_Ptr t) { - return t->block()->perform(this); + traces.push_back(Backtrace(t->pstate())); + auto result = t->block()->perform(this); + traces.pop_back(); + return result; } Statement_Ptr Cssize::operator()(Declaration_Ptr d) @@ -149,7 +151,7 @@ namespace Sass { // this should protect us (at least a bit) from our mess // fixing this properly is harder that it should be ... if (Cast(bb) == NULL) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); } Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, r->pstate(), @@ -161,7 +163,7 @@ namespace Sass { p_stack.pop_back(); if (!rr->block()) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); } Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp index 506b075f7..5a6c704b0 100644 --- a/src/libsass/src/cssize.hpp +++ b/src/libsass/src/cssize.hpp @@ -13,14 +13,14 @@ namespace Sass { class Cssize : public Operation_CRTP { Context& ctx; - std::vector block_stack; - std::vector p_stack; - Backtrace* backtrace; + Backtraces& traces; + std::vector block_stack; + std::vector p_stack; Statement_Ptr fallback_impl(AST_Node_Ptr n); public: - Cssize(Context&, Backtrace*); + Cssize(Context&); ~Cssize() { } Selector_List_Ptr selector(); diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp index e9fc8a279..ee0d6eba7 100644 --- a/src/libsass/src/debugger.hpp +++ b/src/libsass/src/debugger.hpp @@ -79,7 +79,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) Trace_Ptr trace = Cast(node); std::cerr << ind << "Trace " << trace; std::cerr << " (" << pstate_source_position(node) << ")" - << " [name:" << trace->name() << "]" + << " [name:" << trace->name() << ", type: " << trace->type() << "]" << std::endl; debug_ast(trace->block(), ind + " ", env); } else if (Cast(node)) { @@ -664,6 +664,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(expression) << ")"; std::cerr << " " << expression->concrete_type(); std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->css()) std::cerr << " [css]"; if (expression->is_delayed()) std::cerr << " [delayed]"; if (expression->is_interpolant()) std::cerr << " [is interpolant]"; if (expression->has_interpolant()) std::cerr << " [has interpolant]"; diff --git a/src/libsass/src/error_handling.cpp b/src/libsass/src/error_handling.cpp index 67affc81e..745f65508 100644 --- a/src/libsass/src/error_handling.cpp +++ b/src/libsass/src/error_handling.cpp @@ -10,19 +10,18 @@ namespace Sass { namespace Exception { - Base::Base(ParserState pstate, std::string msg, std::vector* import_stack) + Base::Base(ParserState pstate, std::string msg, Backtraces traces) : std::runtime_error(msg), msg(msg), - prefix("Error"), pstate(pstate), - import_stack(import_stack) + prefix("Error"), pstate(pstate), traces(traces) { } - InvalidSass::InvalidSass(ParserState pstate, std::string msg) - : Base(pstate, msg) + InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) { } - InvalidParent::InvalidParent(Selector_Ptr parent, Selector_Ptr selector) - : Base(selector->pstate()), parent(parent), selector(selector) + InvalidParent::InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector) + : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) { msg = "Invalid parent selector for \""; msg += selector->to_string(Sass_Inspect_Options()); @@ -31,15 +30,15 @@ namespace Sass { msg += "\""; } - InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, std::string name, const Argument_Ptr arg) - : Base(pstate), name(name), arg(arg) + InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg) + : Base(pstate, def_msg, traces), name(name), arg(arg) { msg = "Variable keyword argument map must have string keys.\n"; msg += name + " is not a string in " + arg->to_string() + "."; } - InvalidArgumentType::InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value) - : Base(pstate), fn(fn), arg(arg), type(type), value(value) + InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value) + : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) { msg = arg + ": \""; if (value) msg += value->to_string(Sass_Inspect_Options()); @@ -47,50 +46,24 @@ namespace Sass { msg += " for `" + fn + "'"; } - MissingArgument::MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype) - : Base(pstate), fn(fn), arg(arg), fntype(fntype) + MissingArgument::MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype) + : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) { msg = fntype + " " + fn; msg += " is missing argument "; msg += arg + "."; } - InvalidSyntax::InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack) - : Base(pstate, msg, import_stack) + InvalidSyntax::InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) { } - NestingLimitError::NestingLimitError(ParserState pstate, std::string msg, std::vector* import_stack) - : Base(pstate, msg, import_stack) + NestingLimitError::NestingLimitError(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) { } - UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) - : lhs(lhs), rhs(rhs), op(op) - { - msg = def_op_msg + ": \""; - msg += lhs->to_string({ NESTED, 5 }); - msg += " " + op + " "; - msg += rhs->to_string({ TO_SASS, 5 }); - msg += "\"."; - } - - InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) - : UndefinedOperation(lhs, rhs, op) - { - msg = def_op_null_msg + ": \""; - msg += lhs->inspect(); - msg += " " + op + " "; - msg += rhs->inspect(); - msg += "\"."; - } - - ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) - : lhs(lhs), rhs(rhs) - { - msg = "divided by 0"; - } - - DuplicateKeyError::DuplicateKeyError(const Map& dup, const Expression& org) - : Base(org.pstate()), dup(dup), org(org) + DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) + : Base(org.pstate(), def_msg, traces), dup(dup), org(org) { msg = "Duplicate key "; msg += dup.get_duplicate_key()->inspect(); @@ -99,8 +72,8 @@ namespace Sass { msg += ")."; } - TypeMismatch::TypeMismatch(const Expression& var, const std::string type) - : Base(var.pstate()), var(var), type(type) + TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const std::string type) + : Base(var.pstate(), def_msg, traces), var(var), type(type) { msg = var.to_string(); msg += " is not an "; @@ -108,15 +81,15 @@ namespace Sass { msg += "."; } - InvalidValue::InvalidValue(const Expression& val) - : Base(val.pstate()), val(val) + InvalidValue::InvalidValue(Backtraces traces, const Expression& val) + : Base(val.pstate(), def_msg, traces), val(val) { msg = val.to_string(); msg += " isn't a valid CSS value."; } - StackError::StackError(const AST_Node& node) - : Base(node.pstate()), node(node) + StackError::StackError(Backtraces traces, const AST_Node& node) + : Base(node.pstate(), def_msg, traces), node(node) { msg = "stack level too deep"; } @@ -139,19 +112,44 @@ namespace Sass { msg += "'."; } - AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) - : lhs(lhs), rhs(rhs), op(op) + AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : OperationError(), lhs(lhs), rhs(rhs), op(op) { msg = "Alpha channels must be equal: "; msg += lhs->to_string({ NESTED, 5 }); - msg += " " + op + " "; + msg += " " + sass_op_to_name(op) + " "; msg += rhs->to_string({ NESTED, 5 }); msg += "."; } + ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) + : OperationError(), lhs(lhs), rhs(rhs) + { + msg = "divided by 0"; + } + + UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : OperationError(), lhs(lhs), rhs(rhs), op(op) + { + msg = def_op_msg + ": \""; + msg += lhs->to_string({ NESTED, 5 }); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->to_string({ TO_SASS, 5 }); + msg += "\"."; + } + + InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : UndefinedOperation(lhs, rhs, op) + { + msg = def_op_null_msg + ": \""; + msg += lhs->inspect(); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->inspect(); + msg += "\"."; + } - SassValueError::SassValueError(ParserState pstate, OperationError& err) - : Base(pstate, err.what()) + SassValueError::SassValueError(Backtraces traces, ParserState pstate, OperationError& err) + : Base(pstate, err.what(), traces) { msg = err.what(); prefix = err.errtype(); @@ -162,7 +160,7 @@ namespace Sass { void warn(std::string msg, ParserState pstate) { - std::cerr << "Warning: " << msg<< std::endl; + std::cerr << "Warning: " << msg << std::endl; } void warning(std::string msg, ParserState pstate) @@ -178,8 +176,6 @@ namespace Sass { void warn(std::string msg, ParserState pstate, Backtrace* bt) { - Backtrace top(bt, pstate, ""); - msg += top.to_string(); warn(msg, pstate); } @@ -223,16 +219,17 @@ namespace Sass { std::cerr << "This will be an error in future versions of Sass." << std::endl; } - void error(std::string msg, ParserState pstate) + // should be replaced with error with backtraces + void coreError(std::string msg, ParserState pstate) { - throw Exception::InvalidSyntax(pstate, msg); + Backtraces traces; + throw Exception::InvalidSyntax(pstate, traces, msg); } - void error(std::string msg, ParserState pstate, Backtrace* bt) + void error(std::string msg, ParserState pstate, Backtraces& traces) { - Backtrace top(bt, pstate, ""); - msg += "\n" + top.to_string(); - error(msg, pstate); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSyntax(pstate, traces, msg); } } diff --git a/src/libsass/src/error_handling.hpp b/src/libsass/src/error_handling.hpp index e7890f6ed..f863792ea 100644 --- a/src/libsass/src/error_handling.hpp +++ b/src/libsass/src/error_handling.hpp @@ -5,6 +5,7 @@ #include #include #include "position.hpp" +#include "backtrace.hpp" #include "ast_fwd_decl.hpp" #include "sass/functions.h" @@ -25,9 +26,9 @@ namespace Sass { std::string prefix; public: ParserState pstate; - std::vector* import_stack; + Backtraces traces; public: - Base(ParserState pstate, std::string msg = def_msg, std::vector* import_stack = 0); + Base(ParserState pstate, std::string msg, Backtraces traces); virtual const char* errtype() const { return prefix.c_str(); } virtual const char* what() const throw() { return msg.c_str(); } virtual ~Base() throw() {}; @@ -35,7 +36,7 @@ namespace Sass { class InvalidSass : public Base { public: - InvalidSass(ParserState pstate, std::string msg); + InvalidSass(ParserState pstate, Backtraces traces, std::string msg); virtual ~InvalidSass() throw() {}; }; @@ -44,7 +45,7 @@ namespace Sass { Selector_Ptr parent; Selector_Ptr selector; public: - InvalidParent(Selector_Ptr parent, Selector_Ptr selector); + InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector); virtual ~InvalidParent() throw() {}; }; @@ -54,7 +55,7 @@ namespace Sass { std::string arg; std::string fntype; public: - MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype); + MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype); virtual ~MissingArgument() throw() {}; }; @@ -65,7 +66,7 @@ namespace Sass { std::string type; const Value_Ptr value; public: - InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); + InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); virtual ~InvalidArgumentType() throw() {}; }; @@ -74,52 +75,28 @@ namespace Sass { std::string name; const Argument_Ptr arg; public: - InvalidVarKwdType(ParserState pstate, std::string name, const Argument_Ptr arg = 0); + InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg = 0); virtual ~InvalidVarKwdType() throw() {}; }; class InvalidSyntax : public Base { public: - InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack = 0); + InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg); virtual ~InvalidSyntax() throw() {}; }; class NestingLimitError : public Base { public: - NestingLimitError(ParserState pstate, std::string msg = def_nesting_limit, std::vector* import_stack = 0); + NestingLimitError(ParserState pstate, Backtraces traces, std::string msg = def_nesting_limit); virtual ~NestingLimitError() throw() {}; }; - /* common virtual base class (has no pstate) */ - class OperationError : public std::runtime_error { - protected: - std::string msg; - public: - OperationError(std::string msg = def_op_msg) - : std::runtime_error(msg), msg(msg) - {}; - public: - virtual const char* errtype() const { return "Error"; } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~OperationError() throw() {}; - }; - - class ZeroDivisionError : public OperationError { - protected: - const Expression& lhs; - const Expression& rhs; - public: - ZeroDivisionError(const Expression& lhs, const Expression& rhs); - virtual const char* errtype() const { return "ZeroDivisionError"; } - virtual ~ZeroDivisionError() throw() {}; - }; - class DuplicateKeyError : public Base { protected: const Map& dup; const Expression& org; public: - DuplicateKeyError(const Map& dup, const Expression& org); + DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org); virtual const char* errtype() const { return "Error"; } virtual ~DuplicateKeyError() throw() {}; }; @@ -129,7 +106,7 @@ namespace Sass { const Expression& var; const std::string type; public: - TypeMismatch(const Expression& var, const std::string type); + TypeMismatch(Backtraces traces, const Expression& var, const std::string type); virtual const char* errtype() const { return "Error"; } virtual ~TypeMismatch() throw() {}; }; @@ -138,7 +115,7 @@ namespace Sass { protected: const Expression& val; public: - InvalidValue(const Expression& val); + InvalidValue(Backtraces traces, const Expression& val); virtual const char* errtype() const { return "Error"; } virtual ~InvalidValue() throw() {}; }; @@ -147,11 +124,35 @@ namespace Sass { protected: const AST_Node& node; public: - StackError(const AST_Node& node); + StackError(Backtraces traces, const AST_Node& node); virtual const char* errtype() const { return "SystemStackError"; } virtual ~StackError() throw() {}; }; + /* common virtual base class (has no pstate or trace) */ + class OperationError : public std::runtime_error { + protected: + std::string msg; + public: + OperationError(std::string msg = def_op_msg) + : std::runtime_error(msg), msg(msg) + {}; + public: + virtual const char* errtype() const { return "Error"; } + virtual const char* what() const throw() { return msg.c_str(); } + virtual ~OperationError() throw() {}; + }; + + class ZeroDivisionError : public OperationError { + protected: + const Expression& lhs; + const Expression& rhs; + public: + ZeroDivisionError(const Expression& lhs, const Expression& rhs); + virtual const char* errtype() const { return "ZeroDivisionError"; } + virtual ~ZeroDivisionError() throw() {}; + }; + class IncompatibleUnits : public OperationError { protected: // const Sass::UnitType lhs; @@ -166,16 +167,16 @@ namespace Sass { protected: Expression_Ptr_Const lhs; Expression_Ptr_Const rhs; - const std::string op; + const Sass_OP op; public: - UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); // virtual const char* errtype() const { return "Error"; } virtual ~UndefinedOperation() throw() {}; }; class InvalidNullOperation : public UndefinedOperation { public: - InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); virtual ~InvalidNullOperation() throw() {}; }; @@ -183,16 +184,16 @@ namespace Sass { protected: Expression_Ptr_Const lhs; Expression_Ptr_Const rhs; - const std::string op; + const Sass_OP op; public: - AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); // virtual const char* errtype() const { return "Error"; } virtual ~AlphaChannelsNotEqual() throw() {}; }; class SassValueError : public Base { public: - SassValueError(ParserState pstate, OperationError& err); + SassValueError(Backtraces traces, ParserState pstate, OperationError& err); virtual ~SassValueError() throw() {}; }; @@ -207,8 +208,8 @@ namespace Sass { void deprecated_bind(std::string msg, ParserState pstate); // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); - void error(std::string msg, ParserState pstate); - void error(std::string msg, ParserState pstate, Backtrace* bt); + void coreError(std::string msg, ParserState pstate); + void error(std::string msg, ParserState pstate, Backtraces& traces); } diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp index 67fa628a3..2ddfa93ea 100644 --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -12,6 +12,7 @@ #include "bind.hpp" #include "util.hpp" #include "inspect.hpp" +#include "operators.hpp" #include "environment.hpp" #include "position.hpp" #include "sass/values.h" @@ -28,28 +29,10 @@ namespace Sass { - inline double add(double x, double y) { return x + y; } - inline double sub(double x, double y) { return x - y; } - inline double mul(double x, double y) { return x * y; } - inline double div(double x, double y) { return x / y; } // x/0 checked by caller - inline double mod(double x, double y) { // x/0 checked by caller - if ((x > 0 && y < 0) || (x < 0 && y > 0)) { - double ret = std::fmod(x, y); - return ret ? ret + y : ret; - } else { - return std::fmod(x, y); - } - } - typedef double (*bop)(double, double); - bop ops[Sass_OP::NUM_OPS] = { - 0, 0, // and, or - 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte - add, sub, mul, div, mod - }; - Eval::Eval(Expand& exp) : exp(exp), ctx(exp.ctx), + traces(exp.traces), force(false), is_in_comment(false), is_in_selector_schema(false) @@ -69,11 +52,6 @@ namespace Sass { return exp.selector(); } - Backtrace* Eval::backtrace() - { - return exp.backtrace(); - } - Expression_Ptr Eval::operator()(Block_Ptr b) { Expression_Ptr val = 0; @@ -169,11 +147,13 @@ namespace Sass { std::string variable(f->variable()); Expression_Obj low = f->lower_bound()->perform(this); if (low->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*low, "integer"); + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); } Expression_Obj high = f->upper_bound()->perform(this); if (high->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*high, "integer"); + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); } Number_Obj sass_start = Cast(low); Number_Obj sass_end = Cast(high); @@ -182,7 +162,7 @@ namespace Sass { std::stringstream msg; msg << "Incompatible units: '" << sass_end->unit() << "' and '" << sass_start->unit() << "'."; - error(msg.str(), low->pstate(), backtrace()); + error(msg.str(), low->pstate(), traces); } double start = sass_start->value(); double end = sass_end->value(); @@ -366,11 +346,12 @@ namespace Sass { } std::string result(unquote(message->to_sass())); - Backtrace top(backtrace(), w->pstate(), ""); - std::cerr << "WARNING: " << result; - std::cerr << top.to_string(); - std::cerr << std::endl << std::endl; + std::cerr << "WARNING: " << result << std::endl; + traces.push_back(Backtrace(w->pstate())); + std::cerr << traces_to_string(traces, " "); + std::cerr << std::endl; ctx.c_options.output_style = outstyle; + traces.pop_back(); return 0; } @@ -414,7 +395,7 @@ namespace Sass { std::string result(unquote(message->to_sass())); ctx.c_options.output_style = outstyle; - error(result, e->pstate()); + error(result, e->pstate(), traces); return 0; } @@ -484,7 +465,8 @@ namespace Sass { *lm << std::make_pair(key, val); } if (lm->has_duplicate_key()) { - throw Exception::DuplicateKeyError(*lm, *l); + traces.push_back(Backtrace(l->pstate())); + throw Exception::DuplicateKeyError(traces, *lm, *l); } lm->is_interpolant(l->is_interpolant()); @@ -515,7 +497,8 @@ namespace Sass { // make sure we're not starting with duplicate keys. // the duplicate key state will have been set in the parser phase. if (m->has_duplicate_key()) { - throw Exception::DuplicateKeyError(*m, *m); + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *m, *m); } Map_Obj mm = SASS_MEMORY_NEW(Map, @@ -531,7 +514,8 @@ namespace Sass { // check the evaluated keys aren't duplicates. if (mm->has_duplicate_key()) { - throw Exception::DuplicateKeyError(*mm, *m); + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *mm, *m); } mm->is_expanded(true); @@ -599,13 +583,14 @@ namespace Sass { case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); + return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } // lhs is number and rhs is color @@ -619,13 +604,14 @@ namespace Sass { case Sass_OP::LTE: return *l_n < *r_c || *l_n == *r_c ? bool_true : bool_false; case Sass_OP::GT: return *l_n < *r_c || *l_n == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); + return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } } @@ -641,13 +627,14 @@ namespace Sass { case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); + return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } // lhs is color and rhs is number @@ -661,13 +648,14 @@ namespace Sass { case Sass_OP::LTE: return *l_c < *r_n || *l_c == *r_n ? bool_true : bool_false; case Sass_OP::GT: return *l_c < *r_n || *l_c == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); + return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } } @@ -786,19 +774,20 @@ namespace Sass { // see if it's a relational expression try { switch(op_type) { - case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), eq(lhs, rhs)); - case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), !eq(lhs, rhs)); - case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gt") && !eq(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gte")); - case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lt")); - case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lte") || eq(lhs, rhs)); - default: break; + case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); + case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); + case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); + default: break; } } catch (Exception::OperationError& err) { // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(b->pstate(), err); + traces.push_back(Backtrace(b->pstate())); + throw Exception::SassValueError(traces, b->pstate(), err); } l_type = lhs->concrete_type(); @@ -813,22 +802,22 @@ namespace Sass { Number_Ptr l_n = Cast(lhs); Number_Ptr r_n = Cast(rhs); l_n->reduce(); r_n->reduce(); - rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); + rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { Number_Ptr l_n = Cast(lhs); Color_Ptr r_c = Cast(rhs); - rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); + rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); } else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { Color_Ptr l_c = Cast(lhs); Number_Ptr r_n = Cast(rhs); - rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); + rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); } else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { Color_Ptr l_c = Cast(lhs); Color_Ptr r_c = Cast(rhs); - rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); + rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); } else { To_Value to_value(ctx); @@ -842,12 +831,14 @@ namespace Sass { // if (op_type == Sass_OP::DIV) interpolant = true; // check for type violations if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - throw Exception::InvalidValue(*v_l); + traces.push_back(Backtrace(v_l->pstate())); + throw Exception::InvalidValue(traces, *v_l); } if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - throw Exception::InvalidValue(*v_r); + traces.push_back(Backtrace(v_r->pstate())); + throw Exception::InvalidValue(traces, *v_r); } - Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress + Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress if (String_Constant_Ptr str = Cast(ex)) { if (str->concrete_type() == Expression::STRING) @@ -866,8 +857,9 @@ namespace Sass { } catch (Exception::OperationError& err) { + traces.push_back(Backtrace(b->pstate())); // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(b->pstate(), err); + throw Exception::SassValueError(traces, b->pstate(), err); } if (rv) { @@ -932,11 +924,11 @@ namespace Sass { Expression_Ptr Eval::operator()(Function_Call_Ptr c) { - if (backtrace()->parent != NULL && backtrace()->depth() > Constants::MaxCallStack) { + if (traces.size() > Constants::MaxCallStack) { // XXX: this is never hit via spec tests std::ostringstream stm; stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str(), c->pstate(), backtrace()); + error(stm.str(), c->pstate(), traces); } std::string name(Util::normalize_underscores(c->name())); std::string full_name(name + "[f]"); @@ -948,7 +940,7 @@ namespace Sass { if (!env->has("*[f]")) { for (Argument_Obj arg : args->elements()) { if (List_Obj ls = Cast(arg->value())) { - if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate()); + if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); } } args = Cast(args->perform(this)); @@ -957,7 +949,7 @@ namespace Sass { c->name(), args); if (args->has_named_arguments()) { - error("Function " + c->name() + " doesn't support keyword arguments", c->pstate()); + error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); } String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), @@ -994,7 +986,7 @@ namespace Sass { ss << full_name << L; full_name = ss.str(); std::string resolved_name(full_name); - if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate()); + if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); def = Cast((*env)[resolved_name]); } @@ -1011,8 +1003,8 @@ namespace Sass { if (func || body) { bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); - exp.backtrace_stack.push_back(&here); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); ctx.callee_stack.push_back({ c->name().c_str(), c->pstate().path, @@ -1027,13 +1019,13 @@ namespace Sass { result = body->perform(this); } else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace(), exp.selector_stack); + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); } if (!result) { - error(std::string("Function ") + c->name() + " did not return a value", c->pstate()); + error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); } - exp.backtrace_stack.pop_back(); ctx.callee_stack.pop_back(); + traces.pop_back(); } // else if it's a user-defined c function @@ -1051,9 +1043,8 @@ namespace Sass { // populates env with default values for params std::string ff(c->name()); bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - - Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); - exp.backtrace_stack.push_back(&here); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); ctx.callee_stack.push_back({ c->name().c_str(), c->pstate().path, @@ -1074,14 +1065,14 @@ namespace Sass { } union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); if (sass_value_get_tag(c_val) == SASS_ERROR) { - error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), backtrace()); + error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); } else if (sass_value_get_tag(c_val) == SASS_WARNING) { - error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), backtrace()); + error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); } - result = cval_to_astnode(c_val, backtrace(), c->pstate()); + result = cval_to_astnode(c_val, traces, c->pstate()); - exp.backtrace_stack.pop_back(); ctx.callee_stack.pop_back(); + traces.pop_back(); sass_delete_value(c_args); if (c_val != c_args) sass_delete_value(c_val); @@ -1115,7 +1106,7 @@ namespace Sass { const std::string& name(v->name()); EnvResult rv(env->find(name)); if (rv.found) value = static_cast(rv.it->second.ptr()); - else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); if (Argument_Ptr arg = Cast(value)) value = arg->value(); if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag value->is_interpolant(v->is_interpolant()); @@ -1159,7 +1150,8 @@ namespace Sass { Number reduced(nr); reduced.reduce(); if (!reduced.is_valid_css_unit()) { - throw Exception::InvalidValue(*nr); + traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue(traces, *nr); } } if (Argument_Ptr arg = Cast(ex)) { @@ -1258,10 +1250,10 @@ namespace Sass { } if (!s->is_interpolant()) { if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); - return SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); } // string schema seems to have a special unquoting behavior (also handles "nested" quotes) - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false); + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); // if (s->is_interpolant()) str->quote_mark(0); // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); if (str->quote_mark()) str->quote_mark('*'); @@ -1482,206 +1474,7 @@ namespace Sass { // All the binary helpers. - bool Eval::eq(Expression_Obj lhs, Expression_Obj rhs) - { - // use compare operator from ast node - return lhs && rhs && *lhs == *rhs; - } - - bool Eval::lt(Expression_Obj lhs, Expression_Obj rhs, std::string op) - { - Number_Obj l = Cast(lhs); - Number_Obj r = Cast(rhs); - // use compare operator from ast node - if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); - // use compare operator from ast node - return *l < *r; - } - - Value_Ptr Eval::op_numbers(enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - double lv = l.value(); - double rv = r.value(); - - if (op == Sass_OP::DIV && rv == 0) { - // XXX: this is never hit via spec tests - return SASS_MEMORY_NEW(String_Quoted, pstate, lv ? "Infinity" : "NaN"); - } - - if (op == Sass_OP::MOD && !rv) { - // XXX: this is never hit via spec tests - throw Exception::ZeroDivisionError(l, r); - } - - size_t l_n_units = l.numerators.size(); - size_t l_d_units = l.numerators.size(); - size_t r_n_units = r.denominators.size(); - size_t r_d_units = r.denominators.size(); - // optimize out the most common and simplest case - if (l_n_units == r_n_units && l_d_units == r_d_units) { - if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { - if (l.numerators == r.numerators) { - if (l.denominators == r.denominators) { - Number_Ptr v = SASS_MEMORY_COPY(&l); - v->value(ops[op](lv, rv)); - return v; - } - } - } - } - - Number_Obj v = SASS_MEMORY_COPY(&l); - - if (l.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerators = r.numerators; - v->denominators = r.denominators; - } - - if (op == Sass_OP::MUL) { - v->value(ops[op](lv, rv)); - v->numerators.insert(v->numerators.end(), - r.numerators.begin(), r.numerators.end() - ); - v->denominators.insert(v->denominators.end(), - r.denominators.begin(), r.denominators.end() - ); - } - else if (op == Sass_OP::DIV) { - v->value(ops[op](lv, rv)); - v->numerators.insert(v->numerators.end(), - r.denominators.begin(), r.denominators.end() - ); - v->denominators.insert(v->denominators.end(), - r.numerators.begin(), r.numerators.end() - ); - } - else { - Number ln(l), rn(r); - ln.reduce(); rn.reduce(); - double f(rn.convert_factor(ln)); - v->value(ops[op](lv, rn.value() * f)); - } - - v->reduce(); - v->pstate(pstate); - return v.detach(); - } - - Value_Ptr Eval::op_number_color(enum Sass_OP op, const Number& l, const Color& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - double lv = l.value(); - switch (op) { - case Sass_OP::ADD: - case Sass_OP::MUL: { - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lv, r.r()), - ops[op](lv, r.g()), - ops[op](lv, r.b()), - r.a()); - } - case Sass_OP::SUB: - case Sass_OP::DIV: { - std::string sep(op == Sass_OP::SUB ? "-" : "/"); - std::string color(r.to_string(opt)); - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l.to_string(opt) - + sep - + color); - } - case Sass_OP::MOD: { - throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op)); - } - default: break; // caller should ensure that we don't get here - } - // unreachable - return NULL; - } - - Value_Ptr Eval::op_color_number(enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - double rv = r.value(); - if (op == Sass_OP::DIV && !rv) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(l, r); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](l.r(), rv), - ops[op](l.g(), rv), - ops[op](l.b(), rv), - l.a()); - } - - Value_Ptr Eval::op_colors(enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - if (l.a() != r.a()) { - throw Exception::AlphaChannelsNotEqual(&l, &r, "+"); - } - if (op == Sass_OP::DIV && (!r.r() || !r.g() ||!r.b())) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(l, r); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](l.r(), r.r()), - ops[op](l.g(), r.g()), - ops[op](l.b(), r.b()), - l.a()); - } - - Value_Ptr Eval::op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - Expression::Concrete_Type ltype = lhs.concrete_type(); - Expression::Concrete_Type rtype = rhs.concrete_type(); - enum Sass_OP op = operand.operand; - - String_Quoted_Ptr lqstr = Cast(&lhs); - String_Quoted_Ptr rqstr = Cast(&rhs); - - std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); - std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); - - if (ltype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); - if (rtype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); - std::string sep; - switch (op) { - case Sass_OP::SUB: sep = "-"; break; - case Sass_OP::DIV: sep = "/"; break; - // cases are already handled above - case Sass_OP::EQ: sep = "=="; break; - case Sass_OP::NEQ: sep = "!="; break; - case Sass_OP::LT: sep = "<"; break; - case Sass_OP::GT: sep = ">"; break; - case Sass_OP::LTE: sep = "<="; break; - case Sass_OP::GTE: sep = ">="; break; - case Sass_OP::MUL: throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); - case Sass_OP::MOD: throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); - default: break; - } - - if ( (sep == "") /* && - (sep != "/" || !rqstr || !rqstr->quote_mark()) */ - ) { - // create a new string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); - } - - if (sep != "" && !delayed) { - if (operand.ws_before) sep = " " + sep; - if (operand.ws_after) sep = sep + " "; - } - - if (op == Sass_OP::SUB || op == Sass_OP::DIV) { - if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); - if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); - } - - return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); - } - - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate) + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) { using std::strlen; using std::strcpy; @@ -1706,7 +1499,7 @@ namespace Sass { case SASS_LIST: { List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { - l->append(cval_to_astnode(sass_list_get_value(v, i), backtrace, pstate)); + l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); } l->is_bracketed(sass_list_get_is_bracketed(v)); e = l; @@ -1715,8 +1508,8 @@ namespace Sass { Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { *m << std::make_pair( - cval_to_astnode(sass_map_get_key(v, i), backtrace, pstate), - cval_to_astnode(sass_map_get_value(v, i), backtrace, pstate)); + cval_to_astnode(sass_map_get_key(v, i), traces, pstate), + cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); } e = m; } break; @@ -1724,10 +1517,10 @@ namespace Sass { e = SASS_MEMORY_NEW(Null, pstate); } break; case SASS_ERROR: { - error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, backtrace); + error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); } break; case SASS_WARNING: { - error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, backtrace); + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); } break; default: break; } @@ -1771,7 +1564,7 @@ namespace Sass { { bool implicit_parent = !exp.old_at_root_without_rule; if (is_in_selector_schema) exp.selector_stack.push_back(0); - Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, implicit_parent); + Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); if (is_in_selector_schema) exp.selector_stack.pop_back(); for (size_t i = 0; i < resolved->length(); i++) { Complex_Selector_Ptr is = resolved->at(i)->first(); @@ -1807,7 +1600,7 @@ namespace Sass { result_str = unquote(Util::rtrim(result_str)); char* temp_cstr = sass_copy_c_string(result_str.c_str()); ctx.strings.push_back(temp_cstr); // attach to context - Parser p = Parser::from_c_str(temp_cstr, ctx, s->pstate()); + Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); p.last_media_block = s->media_block(); // a selector schema may or may not connect to parent? bool chroot = s->connect_parent() == false; diff --git a/src/libsass/src/eval.hpp b/src/libsass/src/eval.hpp index 16a1b9210..aeaada87e 100644 --- a/src/libsass/src/eval.hpp +++ b/src/libsass/src/eval.hpp @@ -18,8 +18,9 @@ namespace Sass { Expression_Ptr fallback_impl(AST_Node_Ptr n); public: - Expand& exp; + Expand& exp; Context& ctx; + Backtraces& traces; Eval(Expand& exp); ~Eval(); @@ -31,7 +32,6 @@ namespace Sass { Boolean_Obj bool_false; Env* environment(); - Backtrace* backtrace(); Selector_List_Obj selector(); // for evaluating function bodies @@ -91,22 +91,12 @@ namespace Sass { template Expression_Ptr fallback(U x) { return fallback_impl(x); } - // -- only need to define two comparisons, and the rest can be implemented in terms of them - static bool eq(Expression_Obj, Expression_Obj); - static bool lt(Expression_Obj, Expression_Obj, std::string op); - // -- arithmetic on the combinations that matter - static Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool interpolant = false); - private: void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); }; - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate = ParserState("[AST]")); + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate = ParserState("[AST]")); } diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp index 8c85024ae..ccd2822df 100644 --- a/src/libsass/src/expand.cpp +++ b/src/libsass/src/expand.cpp @@ -16,8 +16,9 @@ namespace Sass { // simple endless recursion protection const size_t maxRecursion = 500; - Expand::Expand(Context& ctx, Env* env, Backtrace* bt, std::vector* stack) + Expand::Expand(Context& ctx, Env* env, std::vector* stack) : ctx(ctx), + traces(ctx.traces), eval(Eval(*this)), recursions(0), in_keyframes(false), @@ -27,8 +28,7 @@ namespace Sass { block_stack(std::vector()), call_stack(std::vector()), selector_stack(std::vector()), - media_block_stack(std::vector()), - backtrace_stack(std::vector()) + media_block_stack(std::vector()) { env_stack.push_back(0); env_stack.push_back(env); @@ -37,8 +37,6 @@ namespace Sass { if (stack == NULL) { selector_stack.push_back(0); } else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } media_block_stack.push_back(0); - backtrace_stack.push_back(0); - backtrace_stack.push_back(bt); } Env* Expand::environment() @@ -55,13 +53,6 @@ namespace Sass { return 0; } - Backtrace* Expand::backtrace() - { - if (backtrace_stack.size() > 0) - return backtrace_stack.back(); - return 0; - } - // blocks create new variable scopes Block_Ptr Expand::operator()(Block_Ptr b) { @@ -126,7 +117,7 @@ namespace Sass { Parent_Selector_Ptr ptr = Cast(header); if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace()); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); } tail = tail->tail(); } @@ -136,7 +127,7 @@ namespace Sass { else { if (sel->length() == 0 || sel->has_parent_ref()) { if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), backtrace()); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); } } } @@ -189,7 +180,7 @@ namespace Sass { std::string str_mq(mq->to_string(ctx.c_options)); char* str = sass_copy_c_string(str_mq.c_str()); ctx.strings.push_back(str); - Parser p(Parser::from_c_str(str, ctx, mq->pstate())); + Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); mq = p.parse_media_queries(); // re-assign now cpy->media_queries(mq); media_block_stack.push_back(cpy); @@ -349,10 +340,11 @@ namespace Sass { Statement_Ptr Expand::operator()(Import_Stub_Ptr i) { + traces.push_back(Backtrace(i->pstate())); // get parent node from call stack AST_Node_Obj parent = call_stack.back(); if (Cast(parent) == NULL) { - error("Import directives may not be used within control directives or mixins.", i->pstate()); + error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); } // we don't seem to need that actually afterall Sass_Import_Entry import = sass_make_import( @@ -361,10 +353,18 @@ namespace Sass { 0, 0 ); ctx.import_stack.push_back(import); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); + block_stack.back()->append(trace); + block_stack.push_back(trace_block); + const std::string& abs_path(i->resource().abs_path); append_block(ctx.sheets.at(abs_path).root); sass_delete_import(ctx.import_stack.back()); ctx.import_stack.pop_back(); + block_stack.pop_back(); + traces.pop_back(); return 0; } @@ -428,11 +428,13 @@ namespace Sass { std::string variable(f->variable()); Expression_Obj low = f->lower_bound()->perform(&eval); if (low->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*low, "integer"); + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); } Expression_Obj high = f->upper_bound()->perform(&eval); if (high->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*high, "integer"); + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); } Number_Obj sass_start = Cast(low); Number_Obj sass_end = Cast(high); @@ -441,7 +443,7 @@ namespace Sass { std::stringstream msg; msg << "Incompatible units: '" << sass_start->unit() << "' and '" << sass_end->unit() << "'."; - error(msg.str(), low->pstate(), backtrace()); + error(msg.str(), low->pstate(), traces); } double start = sass_start->value(); double end = sass_end->value(); @@ -579,7 +581,7 @@ namespace Sass { Statement_Ptr Expand::operator()(Return_Ptr r) { - error("@return may only be used within a function", r->pstate(), backtrace()); + error("@return may only be used within a function", r->pstate(), traces); return 0; } @@ -593,7 +595,7 @@ namespace Sass { if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { if (Cast(header) == NULL) continue; // skip all others std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace()); + error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); } tail = tail->tail(); } @@ -607,7 +609,7 @@ namespace Sass { Complex_Selector_Obj c = complex_sel; if (!c->head() || c->tail()) { std::string sel_str(contextualized->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace()); + error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); } Compound_Selector_Obj target = c->head(); if (contextualized->is_optional()) target->is_optional(true); @@ -694,7 +696,7 @@ namespace Sass { Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) { if (recursions > maxRecursion) { - throw Exception::StackError(*c); + throw Exception::StackError(traces, *c); } recursions ++; @@ -702,19 +704,19 @@ namespace Sass { Env* env = environment(); std::string full_name(c->name() + "[m]"); if (!env->has(full_name)) { - error("no mixin named " + c->name(), c->pstate(), backtrace()); + error("no mixin named " + c->name(), c->pstate(), traces); } Definition_Obj def = Cast((*env)[full_name]); Block_Obj body = def->block(); Parameters_Obj params = def->parameters(); if (c->block() && c->name() != "@content" && !body->has_content()) { - error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), backtrace()); + error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); } Expression_Obj rv = c->arguments()->perform(&eval); Arguments_Obj args = Cast(rv); - Backtrace new_bt(backtrace(), c->pstate(), ", in mixin `" + c->name() + "`"); - backtrace_stack.push_back(&new_bt); + std::string msg(", in mixin `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); ctx.callee_stack.push_back({ c->name().c_str(), c->pstate().path, @@ -758,9 +760,9 @@ namespace Sass { block_stack.pop_back(); env->del_global("is_in_mixin"); - env_stack.pop_back(); - backtrace_stack.pop_back(); ctx.callee_stack.pop_back(); + env_stack.pop_back(); + traces.pop_back(); recursions --; return trace.detach(); @@ -795,7 +797,7 @@ namespace Sass { { std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); - error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace()); + error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); } diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp index 348503b2b..3464c98f6 100644 --- a/src/libsass/src/expand.hpp +++ b/src/libsass/src/expand.hpp @@ -20,9 +20,9 @@ namespace Sass { Env* environment(); Selector_List_Obj selector(); - Backtrace* backtrace(); Context& ctx; + Backtraces& traces; Eval eval; size_t recursions; bool in_keyframes; @@ -35,7 +35,6 @@ namespace Sass { std::vector call_stack; std::vector selector_stack; std::vector media_block_stack; - std::vector backtrace_stack; Boolean_Obj bool_true; @@ -45,7 +44,7 @@ namespace Sass { void expand_selector_list(Selector_Obj, Selector_List_Obj extender); public: - Expand(Context&, Env*, Backtrace*, std::vector* stack = NULL); + Expand(Context&, Env*, std::vector* stack = NULL); ~Expand() { } Block_Ptr operator()(Block_Ptr); diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp index 5348e5dcf..602269880 100644 --- a/src/libsass/src/extend.cpp +++ b/src/libsass/src/extend.cpp @@ -1718,7 +1718,7 @@ namespace Sass { err << "You may only @extend selectors within the same directive.\n"; err << "From \"@extend " << ext.second->to_string() << "\""; err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; - error(err.str(), selector->pstate()); + error(err.str(), selector->pstate(), eval->exp.traces); } if (entries.size() > 0) hasExtension = true; } @@ -2098,7 +2098,7 @@ namespace Sass { error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" "The selector \"" + str_ext + "\" was not found.\n" "Use \"@extend " + str_ext + " !optional\" if the" - " extend should be able to fail.", (ext ? ext->pstate() : NULL)); + " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces); } } diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp index aa3c55ec5..32d4a7c63 100644 --- a/src/libsass/src/file.cpp +++ b/src/libsass/src/file.cpp @@ -56,6 +56,8 @@ namespace Sass { #ifndef _WIN32 char wd[wd_len]; char* pwd = getcwd(wd, wd_len); + // we should check error for more detailed info (e.g. ENOENT) + // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); std::string cwd = pwd; #else @@ -74,11 +76,15 @@ namespace Sass { bool file_exists(const std::string& path) { #ifdef _WIN32 + wchar_t resolved[32768]; // windows unicode filepaths are encoded in utf16 std::string abspath(join_paths(get_cwd(), path)); std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); - DWORD dwAttrib = GetFileAttributesW(wpath.c_str()); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + DWORD dwAttrib = GetFileAttributesW(resolved); return (dwAttrib != INVALID_FILE_ATTRIBUTES && (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); #else @@ -200,6 +206,13 @@ namespace Sass { if (is_absolute_path(r)) return r; if (l[l.length()-1] != '/') l += '/'; + // this does a logical cleanup of the right hand path + // Note that this does collapse x/../y sections into y. + // This is by design. If /foo on your system is a symlink + // to /bar/baz, then /foo/../cd is actually /bar/cd, + // not /cd as a naive ../ removal would give you. + // will only work on leading double dot dirs on rhs + // therefore it is safe if lhs is already resolved cwd while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); @@ -395,11 +408,15 @@ namespace Sass { #ifdef _WIN32 BYTE* pBuffer; DWORD dwBytes; + wchar_t resolved[32768]; // windows unicode filepaths are encoded in utf16 std::string abspath(join_paths(get_cwd(), path)); std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); - HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return 0; DWORD dwFileLength = GetFileSize(hFile, NULL); if (dwFileLength == INVALID_FILE_SIZE) return 0; diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp index aaa3394fb..9b118d53a 100644 --- a/src/libsass/src/functions.cpp +++ b/src/libsass/src/functions.cpp @@ -10,6 +10,7 @@ #include "eval.hpp" #include "util.hpp" #include "expand.hpp" +#include "operators.hpp" #include "utf8_string.hpp" #include "sass/base.h" #include "utf8.h" @@ -30,27 +31,26 @@ #include "wincrypt.h" #endif -#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, backtrace) -#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, backtrace, ctx) -#define ARGNR(argname) get_arg_nr(argname, env, sig, pstate, backtrace) +#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) +#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) // return a number object (copied since we want to have reduced units) -#define ARGN(argname) get_arg_n(argname, env, sig, pstate, backtrace) // Number copy +#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy // special function for weird hsla percent (10px == 10% == 10 != 0.1) -#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, backtrace) // double +#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double // macros for common ranges (u mean unsigned or upper, r for full range) -#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 1.0) // double -#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 1.0, 1.0) // double -#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 255.0) // double -#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 255.0, 255.0) // double -#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 100.0) // double -#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 100.0, 100.0) // double +#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double +#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double +#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double +#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double +#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double +#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double // macros for color related inputs (rbg and alpha/opacity values) -#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, backtrace) // double -#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, backtrace) // double +#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double +#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double namespace Sass { using std::stringstream; @@ -58,7 +58,7 @@ namespace Sass { Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) { - Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[built-in function]")); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); sig_parser.lex(); std::string name(Util::normalize_underscores(sig_parser.lexed)); Parameters_Obj params = sig_parser.parse_parameters(); @@ -76,7 +76,7 @@ namespace Sass { using namespace Prelexer; const char* sig = sass_function_get_signature(c_func); - Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[c function]")); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); // allow to overload generic callback plus @warn, @error and @debug with custom functions sig_parser.lex < alternatives < identifier, exactly <'*'>, exactly < Constants::warn_kwd >, @@ -102,28 +102,28 @@ namespace Sass { namespace Functions { - inline void handle_utf8_error (const ParserState& pstate, Backtrace* backtrace) + inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) { try { throw; } catch (utf8::invalid_code_point) { std::string msg("utf8::invalid_code_point"); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } catch (utf8::not_enough_room) { std::string msg("utf8::not_enough_room"); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } catch (utf8::invalid_utf8) { std::string msg("utf8::invalid_utf8"); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } catch (...) { throw; } } template - T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! T* val = Cast(env[argname]); @@ -134,12 +134,12 @@ namespace Sass { msg += sig; msg += "` must be a "; msg += T::type_name(); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } return val; } - Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) + Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { // Minimal error handling -- the expectation is that built-ins will be written correctly! Map_Ptr val = Cast(env[argname]); @@ -149,14 +149,14 @@ namespace Sass { if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); // fallback on get_arg for error handling - val = get_arg(argname, env, sig, pstate, backtrace); + val = get_arg(argname, env, sig, pstate, traces); return val; } - double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, double lo, double hi) + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); double v = tmpnr.value(); @@ -164,33 +164,24 @@ namespace Sass { std::stringstream msg; msg << "argument `" << argname << "` of `" << sig << "` must be between "; msg << lo << " and " << hi; - error(msg.str(), pstate, backtrace); + error(msg.str(), pstate, traces); } return v; } - const Number& get_arg_nr(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr; - } - - Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); val = SASS_MEMORY_COPY(val); val->reduce(); return val; } - double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); /* @@ -204,18 +195,18 @@ namespace Sass { return tmpnr.value(); } - double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); return tmpnr.value(); } - double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); if (tmpnr.unit() == "%") { @@ -226,8 +217,8 @@ namespace Sass { } - inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) { - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); if (tmpnr.unit() == "%") { @@ -237,40 +228,40 @@ namespace Sass { } } - #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, backtrace, ctx) + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) template - T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx); + T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); template <> - Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { Expression_Obj exp = ARG(argname, Expression); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << argname << ": null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - return Parser::parse_selector(exp_src.c_str(), ctx); + return Parser::parse_selector(exp_src.c_str(), ctx, traces); } template <> - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { Expression_Obj exp = ARG(argname, Expression); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx); + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); if (sel_list->length() == 0) return NULL; Complex_Selector_Obj first = sel_list->first(); if (!first->tail()) return first->head(); @@ -870,7 +861,7 @@ namespace Sass { bool hsl = h || s || l; if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate); + error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); } if (rgb) { double rr = r ? DARG_R_BYTE("$red") : 0; @@ -904,7 +895,7 @@ namespace Sass { color->b(), color->a() + (a ? a->value() : 0)); } - error("not enough arguments for `adjust-color'", pstate); + error("not enough arguments for `adjust-color'", pstate, traces); // unreachable return color; } @@ -925,7 +916,7 @@ namespace Sass { bool hsl = h || s || l; if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate); + error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); } if (rgb) { double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; @@ -960,7 +951,7 @@ namespace Sass { color->b(), color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); } - error("not enough arguments for `scale-color'", pstate); + error("not enough arguments for `scale-color'", pstate, traces); // unreachable return color; } @@ -981,7 +972,7 @@ namespace Sass { bool hsl = h || s || l; if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate); + error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); } if (rgb) { return SASS_MEMORY_NEW(Color, @@ -1008,7 +999,7 @@ namespace Sass { color->b(), alpha); } - error("not enough arguments for `change-color'", pstate); + error("not enough arguments for `change-color'", pstate, traces); // unreachable return color; } @@ -1101,7 +1092,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } // return something even if we had an error (-1) return SASS_MEMORY_NEW(Number, pstate, (double)len); } @@ -1147,7 +1138,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } return SASS_MEMORY_NEW(String_Quoted, pstate, str); } @@ -1171,7 +1162,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } // return something even if we had an error (-1) return SASS_MEMORY_NEW(Number, pstate, (double)index); } @@ -1224,7 +1215,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); } @@ -1278,7 +1269,7 @@ namespace Sass { BUILT_IN(percentage) { Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate); + if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); } @@ -1327,7 +1318,7 @@ namespace Sass { Expression_Obj val = arglist->value_at_index(i); Number_Obj xi = Cast(val); if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate); + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); } if (least) { if (*xi < *least) least = xi; @@ -1345,7 +1336,7 @@ namespace Sass { Expression_Obj val = arglist->value_at_index(i); Number_Obj xi = Cast(val); if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate); + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); } if (greatest) { if (*greatest < *xi) greatest = xi; @@ -1366,13 +1357,13 @@ namespace Sass { if (lv < 1) { stringstream err; err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; - error(err.str(), pstate); + error(err.str(), pstate, traces); } bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; if (!eq_int) { stringstream err; err << "Expected $limit to be an integer but got " << lv << " for `random'"; - error(err.str(), pstate); + error(err.str(), pstate, traces); } std::uniform_real_distribution<> distributor(1, lv + 1); uint_fast32_t distributed = static_cast(distributor(rand)); @@ -1383,9 +1374,11 @@ namespace Sass { double distributed = static_cast(distributor(rand)); return SASS_MEMORY_NEW(Number, pstate, distributed); } else if (v) { - throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number", v); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); } else { - throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number"); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); } } @@ -1428,15 +1421,15 @@ namespace Sass { if (Selector_List_Ptr sl = Cast(env["$list"])) { size_t len = m ? m->length() : sl->length(); bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); // return (*sl)[static_cast(index)]; Listize listize; return (*sl)[static_cast(index)]->perform(&listize); } List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); // if the argument isn't a list, then wrap it in a singleton list if (!m && !l) { l = SASS_MEMORY_NEW(List, pstate, 1); @@ -1444,9 +1437,9 @@ namespace Sass { } size_t len = m ? m->length() : l->length(); bool empty = m ? m->empty() : l->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); if (m) { l = SASS_MEMORY_NEW(List, pstate, 1); @@ -1475,9 +1468,9 @@ namespace Sass { if (m) { l = m->to_list(pstate); } - if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); - if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { result->append(((i == index) ? v : (*l)[i])); @@ -1499,7 +1492,7 @@ namespace Sass { l = m->to_list(pstate); } for (size_t i = 0, L = l->length(); i < L; ++i) { - if (Eval::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); + if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); } return SASS_MEMORY_NEW(Null, pstate); } @@ -1536,7 +1529,7 @@ namespace Sass { std::string sep_str = unquote(sep->value()); if (sep_str == "space") sep_val = SASS_SPACE; else if (sep_str == "comma") sep_val = SASS_COMMA; - else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); + else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); String_Constant_Obj bracketed_as_str = Cast(bracketed); bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; if (!bracketed_is_auto) { @@ -1571,7 +1564,7 @@ namespace Sass { if (sep_str != "auto") { // check default first if (sep_str == "space") result->separator(SASS_SPACE); else if (sep_str == "comma") result->separator(SASS_COMMA); - else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); + else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); } if (l->is_arglist()) { result->append(SASS_MEMORY_NEW(Argument, @@ -1712,7 +1705,7 @@ namespace Sass { for (auto key : m->keys()) { remove = false; for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { - remove = Eval::eq(key, arglist->value_at_index(j)); + remove = Operators::eq(key, arglist->value_at_index(j)); } if (!remove) *result << std::make_pair(key, m->at(key)); } @@ -1809,7 +1802,7 @@ namespace Sass { { String_Constant_Ptr ss = Cast(env["$name"]); if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, backtrace); + error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); } std::string name = Util::normalize_underscores(unquote(ss->value())); @@ -1893,7 +1886,7 @@ namespace Sass { } } Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); - Expand expand(ctx, &d_env, backtrace, &selector_stack); + Expand expand(ctx, &d_env, &selector_stack); func->via_call(true); // calc invoke is allowed if (ff) func->func(ff); return func->perform(&expand.eval); @@ -1914,7 +1907,7 @@ namespace Sass { // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } BUILT_IN(sass_if) { - Expand expand(ctx, &d_env, backtrace, &selector_stack); + Expand expand(ctx, &d_env, &selector_stack); Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); bool is_true = !cond->is_false(); Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); @@ -1961,7 +1954,7 @@ namespace Sass { // Not enough parameters if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-nest'", pstate); + error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); // Parse args into vector of selectors std::vector parsedSelectors; @@ -1971,13 +1964,13 @@ namespace Sass { std::stringstream msg; msg << "$selectors: null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Obj str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); parsedSelectors.push_back(sel); } @@ -1995,7 +1988,7 @@ namespace Sass { Selector_List_Obj child = *itr; std::vector exploded; selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(selector_stack); + Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); selector_stack.pop_back(); for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { exploded.push_back((*rv)[m]); @@ -2014,7 +2007,7 @@ namespace Sass { // Not enough parameters if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-append'", pstate); + error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); // Parse args into vector of selectors std::vector parsedSelectors; @@ -2024,13 +2017,13 @@ namespace Sass { std::stringstream msg; msg << "$selectors: null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for 'selector-append'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); parsedSelectors.push_back(sel); } @@ -2068,7 +2061,7 @@ namespace Sass { msg += "\" to \""; msg += parentSeqClone->to_string(); msg += "\" for `selector-append'"; - error(msg, pstate, backtrace); + error(msg, pstate, traces); } // Cannot be a Universal selector @@ -2079,7 +2072,7 @@ namespace Sass { msg += "\" to \""; msg += parentSeqClone->to_string(); msg += "\" for `selector-append'"; - error(msg, pstate, backtrace); + error(msg, pstate, traces); } // TODO: Add check for namespace stuff @@ -2202,7 +2195,7 @@ namespace Sass { BUILT_IN(content_exists) { if (!d_env.has_global("is_in_mixin")) { - error("Cannot call content-exists() except within a mixin.", pstate, backtrace); + error("Cannot call content-exists() except within a mixin.", pstate, traces); } return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); } @@ -2212,7 +2205,7 @@ namespace Sass { { String_Constant_Ptr ss = Cast(env["$name"]); if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, backtrace); + error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); } std::string name = Util::normalize_underscores(unquote(ss->value())); @@ -2231,7 +2224,7 @@ namespace Sass { if (!d_env.has_global(full_name)) { - error("Function not found: " + name, pstate, backtrace); + error("Function not found: " + name, pstate, traces); } Definition_Ptr def = Cast(d_env[full_name]); diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp index 131160d4c..7019be934 100644 --- a/src/libsass/src/functions.hpp +++ b/src/libsass/src/functions.hpp @@ -8,12 +8,12 @@ #include "sass/functions.h" #define BUILT_IN(name) Expression_Ptr \ -name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtrace* backtrace, std::vector selector_stack) +name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtraces traces, std::vector selector_stack) namespace Sass { struct Backtrace; typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx); Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx); diff --git a/src/libsass/src/operators.cpp b/src/libsass/src/operators.cpp new file mode 100644 index 000000000..65885bf19 --- /dev/null +++ b/src/libsass/src/operators.cpp @@ -0,0 +1,240 @@ +#include "sass.hpp" +#include "operators.hpp" + +namespace Sass { + + namespace Operators { + + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } // x/0 checked by caller + + inline double mod(double x, double y) { // x/0 checked by caller + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } else { + return std::fmod(x, y); + } + } + + typedef double (*bop)(double, double); + bop ops[Sass_OP::NUM_OPS] = { + 0, 0, // and, or + 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte + add, sub, mul, div, mod + }; + + /* static function, has no pstate or traces */ + bool eq(Expression_Obj lhs, Expression_Obj rhs) + { + // operation is undefined if one is not a number + if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); + // use compare operator from ast node + return *lhs == *rhs; + } + + /* static function, throws OperationError, has no pstate or traces */ + bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) + { + // can only compare numbers!? + Number_Obj l = Cast(lhs); + Number_Obj r = Cast(rhs); + // operation is undefined if one is not a number + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); + // use compare operator from ast node + return *l < *r; + } + + /* static functions, throws OperationError, has no pstate or traces */ + bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } + bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } + bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } + bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } + bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + enum Sass_OP op = operand.operand; + + String_Quoted_Ptr lqstr = Cast(&lhs); + String_Quoted_Ptr rqstr = Cast(&rhs); + + std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); + std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); + + if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + + std::string sep; + switch (op) { + case Sass_OP::ADD: sep = ""; break; + case Sass_OP::SUB: sep = "-"; break; + case Sass_OP::DIV: sep = "/"; break; + case Sass_OP::EQ: sep = "=="; break; + case Sass_OP::NEQ: sep = "!="; break; + case Sass_OP::LT: sep = "<"; break; + case Sass_OP::GT: sep = ">"; break; + case Sass_OP::LTE: sep = "<="; break; + case Sass_OP::GTE: sep = ">="; break; + default: + throw Exception::UndefinedOperation(&lhs, &rhs, op); + break; + } + + if (op == Sass_OP::ADD) { + // create string that might be quoted on output (but do not unquote what we pass) + return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); + } + + // add whitespace around operator + // but only if result is not delayed + if (sep != "" && delayed == false) { + if (operand.ws_before) sep = " " + sep; + if (operand.ws_after) sep = sep + " "; + } + + if (op == Sass_OP::SUB || op == Sass_OP::DIV) { + if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); + if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); + } + + return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + if (lhs.a() != rhs.a()) { + throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); + } + if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rhs.r()), + ops[op](lhs.g(), rhs.g()), + ops[op](lhs.b(), rhs.b()), + lhs.a()); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + double rval = rhs.value(); + + if (op == Sass_OP::DIV && rval == 0) { + std::string result(lval ? "Infinity" : "NaN"); + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + if (op == Sass_OP::MOD && rval == 0) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + + size_t l_n_units = lhs.numerators.size(); + size_t l_d_units = lhs.numerators.size(); + size_t r_n_units = rhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + // optimize out the most common and simplest case + if (l_n_units == r_n_units && l_d_units == r_d_units) { + if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { + if (lhs.numerators == rhs.numerators) { + if (lhs.denominators == rhs.denominators) { + Number_Ptr v = SASS_MEMORY_COPY(&lhs); + v->value(ops[op](lval, rval)); + return v; + } + } + } + } + + Number_Obj v = SASS_MEMORY_COPY(&lhs); + + if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerators = rhs.numerators; + v->denominators = rhs.denominators; + } + + if (op == Sass_OP::MUL) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->reduce(); + } + else if (op == Sass_OP::DIV) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->reduce(); + } + else { + Number ln(lhs), rn(rhs); + ln.reduce(); rn.reduce(); + double f(rn.convert_factor(ln)); + v->value(ops[op](lval, rn.value() * f)); + } + + v->pstate(pstate); + return v.detach(); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + switch (op) { + case Sass_OP::ADD: + case Sass_OP::MUL: { + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lval, rhs.r()), + ops[op](lval, rhs.g()), + ops[op](lval, rhs.b()), + rhs.a()); + } + case Sass_OP::SUB: + case Sass_OP::DIV: { + std::string color(rhs.to_string(opt)); + return SASS_MEMORY_NEW(String_Quoted, + pstate, + lhs.to_string(opt) + + sass_op_separator(op) + + color); + } + default: break; + } + throw Exception::UndefinedOperation(&lhs, &rhs, op); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double rval = rhs.value(); + if (op == Sass_OP::DIV && rval == 0) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rval), + ops[op](lhs.g(), rval), + ops[op](lhs.b(), rval), + lhs.a()); + } + + } + +} diff --git a/src/libsass/src/operators.hpp b/src/libsass/src/operators.hpp new file mode 100644 index 000000000..f89eb4ee2 --- /dev/null +++ b/src/libsass/src/operators.hpp @@ -0,0 +1,30 @@ +#ifndef SASS_OPERATORS_H +#define SASS_OPERATORS_H + +#include "values.hpp" +#include "sass/values.h" + +namespace Sass { + + namespace Operators { + + // equality operator using AST Node operator== + bool eq(Expression_Obj, Expression_Obj); + bool neq(Expression_Obj, Expression_Obj); + // specific operators based on cmp and eq + bool lt(Expression_Obj, Expression_Obj); + bool gt(Expression_Obj, Expression_Obj); + bool lte(Expression_Obj, Expression_Obj); + bool gte(Expression_Obj, Expression_Obj); + // arithmetic for all the combinations that matter + Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + + }; + +} + +#endif diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp index cc847ee08..b2ca65e7e 100644 --- a/src/libsass/src/output.cpp +++ b/src/libsass/src/output.cpp @@ -19,13 +19,14 @@ namespace Sass { void Output::operator()(Number_Ptr n) { - // use values to_string facility - std::string res = n->to_string(opt); // check for a valid unit here // includes result for reporting if (!n->is_valid_css_unit()) { - throw Exception::InvalidValue(*n); + // should be handle in check_expression + throw Exception::InvalidValue({}, *n); } + // use values to_string facility + std::string res = n->to_string(opt); // output the final token append_token(res, n); } @@ -37,8 +38,8 @@ namespace Sass { void Output::operator()(Map_Ptr m) { - std::string dbg(m->to_string(opt)); - error(dbg + " isn't a valid CSS value.", m->pstate()); + // should be handle in check_expression + throw Exception::InvalidValue({}, *m); } OutputBuffer Output::get_buffer(void) diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp index 71858de10..525f199d1 100644 --- a/src/libsass/src/parser.cpp +++ b/src/libsass/src/parser.cpp @@ -30,11 +30,11 @@ namespace Sass { using namespace Constants; using namespace Prelexer; - Parser Parser::from_c_str(const char* beg, Context& ctx, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate); + Parser p(ctx, pstate, traces); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = p.position + strlen(p.position); @@ -44,11 +44,11 @@ namespace Sass { return p; } - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate); + Parser p(ctx, pstate, traces); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = end ? end : p.position + strlen(p.position); @@ -66,9 +66,9 @@ namespace Sass { pstate.offset.line = 0; } - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, ParserState pstate, const char* source) + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { - Parser p = Parser::from_c_str(beg, ctx, pstate, source); + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); // ToDo: ruby sass errors on parent references // ToDo: remap the source-map entries somehow return p.parse_selector_list(false); @@ -80,9 +80,9 @@ namespace Sass { && ! peek_css>(start); } - Parser Parser::from_token(Token t, Context& ctx, ParserState pstate, const char* source) + Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { - Parser p(ctx, pstate); + Parser p(ctx, pstate, traces); p.source = source ? source : t.begin; p.position = t.begin ? t.begin : p.source; p.end = t.end ? t.end : p.position + strlen(p.position); @@ -105,7 +105,8 @@ namespace Sass { // report invalid utf8 if (it != end) { pstate += Offset::init(position, it); - throw Exception::InvalidSass(pstate, "Invalid UTF-8 sequence"); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); } // create a block AST node to hold children @@ -240,7 +241,7 @@ namespace Sass { Scope parent = stack.empty() ? Scope::Rules : stack.back(); if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 - error("Import directives may not be used within control directives or mixins.", pstate); + error("Import directives may not be used within control directives or mixins."); } } // this puts the parsed doc into sheets @@ -350,14 +351,14 @@ namespace Sass { args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); } else { - error("malformed URL", pstate); + error("malformed URL"); } - if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate); + if (!lex< exactly<')'> >()) error("URI is missing ')'"); to_import.push_back(std::pair("", result)); } else { - if (first) error("@import directive requires a url or quoted path", pstate); - else error("expecting another url or quoted path in @import list", pstate); + if (first) error("@import directive requires a url or quoted path"); + else error("expecting another url or quoted path in @import list"); } first = false; } while (lex_css< exactly<','> >()); @@ -384,10 +385,10 @@ namespace Sass { Definition_Obj Parser::parse_definition(Definition::Type which_type) { std::string which_str(lexed); - if (!lex< identifier >()) error("invalid name in " + which_str + " definition", pstate); + if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); std::string name(Util::normalize_underscores(lexed)); if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) - { error("Invalid function name \"" + name + "\".", pstate); } + { error("Invalid function name \"" + name + "\"."); } ParserState source_position_of_def = pstate; Parameters_Obj params = parse_parameters(); if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); @@ -494,7 +495,7 @@ namespace Sass { { std::string name(Util::normalize_underscores(lexed)); ParserState var_source_position = pstate; - if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement", pstate); + if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } @@ -583,7 +584,7 @@ namespace Sass { } // pass inner expression to the parser to resolve nested interpolations pstate.add(p, p+2); - Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list(); + Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); // set status on the list expression interpolant->is_interpolant(true); // schema->has_interpolants(true); @@ -904,7 +905,7 @@ namespace Sass { ParserState nsource_position = pstate; Selector_List_Obj negated = parse_selector_list(true); if (!lex< exactly<')'> >()) { - error("negated selector is missing ')'", pstate); + error("negated selector is missing ')'"); } name.erase(name.size() - 1); return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); @@ -981,7 +982,7 @@ namespace Sass { Attribute_Selector_Obj Parser::parse_attribute_selector() { ParserState p = pstate; - if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector", pstate); + if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); std::string name(lexed); if (lex_css< re_attr_sensitive_close >()) { return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); @@ -992,7 +993,7 @@ namespace Sass { } if (!lex_css< alternatives< exact_match, class_match, dash_match, prefix_match, suffix_match, substring_match > >()) { - error("invalid operator in attribute selector for " + name, pstate); + error("invalid operator in attribute selector for " + name); } std::string matcher(lexed); @@ -1004,7 +1005,7 @@ namespace Sass { value = parse_interpolated_chunk(lexed, true); // needed! } else { - error("expected a string constant or identifier in attribute selector for " + name, pstate); + error("expected a string constant or identifier in attribute selector for " + name); } if (lex_css< re_attr_sensitive_close >()) { @@ -1014,7 +1015,7 @@ namespace Sass { char modifier = lexed.begin[0]; return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); } - error("unterminated attribute selector for " + name, pstate); + error("unterminated attribute selector for " + name); return NULL; // to satisfy compilers (error must not return) } @@ -1026,7 +1027,7 @@ namespace Sass { while (lex< block_comment >()) { bool is_important = lexed.begin[2] == '!'; // flag on second param is to skip loosely over comments - String_Obj contents = parse_interpolated_chunk(lexed, true); + String_Obj contents = parse_interpolated_chunk(lexed, true, false); block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); } } @@ -1049,8 +1050,8 @@ namespace Sass { } bool is_indented = true; const std::string property(lexed); - if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + property + "\" must be followed by a ':'", pstate); - if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value", pstate); + if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); + if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty if (is_custom_property) { return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); @@ -1448,7 +1449,7 @@ namespace Sass { // parse_map may return a list Expression_Obj value = parse_map(); // lex the expected closing parenthesis - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); // expression can be evaluated return value; } @@ -1456,7 +1457,7 @@ namespace Sass { // explicit bracketed Expression_Obj value = parse_bracket_list(); // lex the expected closing square bracket - if (!lex_css< exactly<']'> >()) error("unclosed squared bracket", pstate); + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); return value; } // string may be interpolated @@ -1721,7 +1722,7 @@ namespace Sass { // this parses interpolation inside other strings // means the result should later be quoted again - String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant) + String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) { const char* i = chunk.begin; // see if there any interpolants @@ -1729,12 +1730,12 @@ namespace Sass { find_first_in_interval< exactly, block_comment >(i, chunk.end); if (!p) { - String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end)); + String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); return str_quoted; } - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); schema->is_interpolant(true); while (i < chunk.end) { p = constant ? find_first_in_interval< exactly >(i, chunk.end) : @@ -1742,7 +1743,7 @@ namespace Sass { if (p) { if (i < p) { // accumulate the preceding segment if it's nonempty - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); } // we need to skip anything inside strings // create a new target in parser/prelexer @@ -1752,19 +1753,19 @@ namespace Sass { const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace if (j) { --j; // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); interp_node->is_interpolant(true); schema->append(interp_node); i = j; } else { // throw an error if the interpolant is unterminated - error("unterminated interpolant inside string constant " + chunk.to_string(), pstate); + error("unterminated interpolant inside string constant " + chunk.to_string()); } } else { // no interpolants left; add the last segment if nonempty // check if we need quotes here (was not sure after merge) - if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end))); + if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); break; } ++ i; @@ -1884,14 +1885,14 @@ namespace Sass { const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace if (j) { // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); interp_node->is_interpolant(true); schema->append(interp_node); i = j; } else { // throw an error if the interpolant is unterminated - error("unterminated interpolant inside IE function " + str.to_string(), pstate); + error("unterminated interpolant inside IE function " + str.to_string()); } } else { // no interpolants left; add the last segment if nonempty @@ -2062,7 +2063,7 @@ namespace Sass { const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace if (j) { // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(DELAYED); + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); interp_node->is_interpolant(true); schema->append(interp_node); // schema->has_interpolants(true); @@ -2070,7 +2071,7 @@ namespace Sass { } else { // throw an error if the interpolant is unterminated - error("unterminated interpolant inside interpolated identifier " + id.to_string(), pstate); + error("unterminated interpolant inside interpolated identifier " + id.to_string()); } } else { // no interpolants left; add the last segment if nonempty @@ -2171,7 +2172,7 @@ namespace Sass { std::string name(lexed); if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) - { error("Cannot call content-exists() except within a mixin.", pstate); } + { error("Cannot call content-exists() except within a mixin."); } ParserState call_pos = pstate; Arguments_Obj args = parse_arguments(); @@ -2221,12 +2222,12 @@ namespace Sass { bool root = block_stack.back()->is_root(); lex_variable(); std::string var(Util::normalize_underscores(lexed)); - if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive", pstate); + if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); Expression_Obj lower_bound = parse_expression(); bool inclusive = false; if (lex< kwd_through >()) inclusive = true; else if (lex< kwd_to >()) inclusive = false; - else error("expected 'through' or 'to' keyword in @for directive", pstate); + else error("expected 'through' or 'to' keyword in @for directive"); Expression_Obj upper_bound = parse_expression(); Block_Obj body = parse_block(root); stack.pop_back(); @@ -2268,10 +2269,10 @@ namespace Sass { lex_variable(); vars.push_back(Util::normalize_underscores(lexed)); while (lex< exactly<','> >()) { - if (!lex< variable >()) error("@each directive requires an iteration variable", pstate); + if (!lex< variable >()) error("@each directive requires an iteration variable"); vars.push_back(Util::normalize_underscores(lexed)); } - if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive", pstate); + if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); Expression_Obj list = parse_list(); Block_Obj body = parse_block(root); stack.pop_back(); @@ -2360,11 +2361,11 @@ namespace Sass { return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); } if (!lex_css< exactly<'('> >()) { - error("media query expression must begin with '('", pstate); + error("media query expression must begin with '('"); } Expression_Obj feature; if (peek_css< exactly<')'> >()) { - error("media feature required in media query expression", pstate); + error("media feature required in media query expression"); } feature = parse_expression(); Expression_Obj expression = 0; @@ -2372,7 +2373,7 @@ namespace Sass { expression = parse_list(DELAYED); } if (!lex_css< exactly<')'> >()) { - error("unclosed parenthesis in media query expression", pstate); + error("unclosed parenthesis in media query expression"); } return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); } @@ -2453,7 +2454,7 @@ namespace Sass { if (lex_css< exactly<':'> >()) { expression = parse_list(DELAYED); } - if (!feature || !expression) error("@supports condition expected declaration", pstate); + if (!feature || !expression) error("@supports condition expected declaration"); cond = SASS_MEMORY_NEW(Supports_Declaration, feature->pstate(), feature, @@ -2472,10 +2473,10 @@ namespace Sass { Supports_Condition_Obj cond = parse_supports_condition(); if (cond != 0) { - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); } else { cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); } lex < css_whitespace >(); return cond; @@ -2508,14 +2509,14 @@ namespace Sass { At_Root_Query_Obj Parser::parse_at_root_query() { - if (peek< exactly<')'> >()) error("at-root feature required in at-root expression", pstate); + if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); } Expression_Obj feature = parse_list(); - if (!lex_css< exactly<':'> >()) error("style declaration must contain a value", pstate); + if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); Expression_Obj expression = parse_list(); List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); @@ -2528,7 +2529,7 @@ namespace Sass { value->pstate(), feature, value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression", pstate); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); return cond; } @@ -2536,7 +2537,7 @@ namespace Sass { { std::string kwd(lexed); - if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); // this whole branch is never hit via spec tests @@ -2568,7 +2569,7 @@ namespace Sass { { std::string kwd(lexed); - if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); Lookahead lookahead = lookahead_for_include(position); @@ -2725,7 +2726,7 @@ namespace Sass { stack.back() != Scope::Mixin && stack.back() != Scope::Control && stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + error("Illegal nesting: Only properties may be nested beneath properties."); } return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); } @@ -2737,7 +2738,7 @@ namespace Sass { stack.back() != Scope::Mixin && stack.back() != Scope::Control && stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + error("Illegal nesting: Only properties may be nested beneath properties."); } return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); } @@ -2749,7 +2750,7 @@ namespace Sass { stack.back() != Scope::Mixin && stack.back() != Scope::Control && stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + error("Illegal nesting: Only properties may be nested beneath properties."); } return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); } @@ -2955,7 +2956,7 @@ namespace Sass { break; default: break; } - if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding, pstate); + if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); position += skip; } @@ -3035,7 +3036,15 @@ namespace Sass { void Parser::error(std::string msg, Position pos) { - throw Exception::InvalidSass(ParserState(path, source, pos.line ? pos : before_token, Offset(0, 0)), msg); + Position p(pos.line ? pos : before_token); + ParserState pstate(path, source, p, Offset(0, 0)); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, msg); + } + + void Parser::error(std::string msg) + { + error(msg, pstate); } // print a css parsing error with actual context information from parsed source @@ -3106,7 +3115,7 @@ namespace Sass { // Hotfix when source is null, probably due to interpolation parsing!? if (source == NULL || *source == 0) source = pstate.src; // now pass new message to the more generic error function - error(msg + prefix + quote(left) + middle + quote(right), pstate); + error(msg + prefix + quote(left) + middle + quote(right)); } } diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp index 71b00a854..83c7f34ba 100644 --- a/src/libsass/src/parser.hpp +++ b/src/libsass/src/parser.hpp @@ -45,22 +45,26 @@ namespace Sass { Position before_token; Position after_token; ParserState pstate; + Backtraces traces; size_t indentation; size_t nestings; Token lexed; - Parser(Context& ctx, const ParserState& pstate) + Parser(Context& ctx, const ParserState& pstate, Backtraces traces) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0), nestings(0) - { stack.push_back(Scope::Root); } + source(0), position(0), end(0), before_token(pstate), after_token(pstate), + pstate(pstate), traces(traces), indentation(0), nestings(0) + { + stack.push_back(Scope::Root); + } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); - static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); #ifdef __clang__ @@ -232,6 +236,7 @@ namespace Sass { #endif + void error(std::string msg); void error(std::string msg, Position pos); // generate message with given and expected sample // text before and in the middle are configurable @@ -283,7 +288,7 @@ namespace Sass { Function_Call_Schema_Obj parse_function_call_schema(); String_Obj parse_url_function_string(); String_Obj parse_url_function_argument(); - String_Obj parse_interpolated_chunk(Token, bool constant = false); + String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); String_Obj parse_string(); String_Constant_Obj parse_static_value(); String_Schema_Obj parse_css_variable_value(bool top_level = true); diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp index 03a2f12fb..afadc66e1 100644 --- a/src/libsass/src/sass_context.cpp +++ b/src/libsass/src/sass_context.cpp @@ -35,7 +35,6 @@ namespace Sass { catch (Exception::Base& e) { std::stringstream msg_stream; std::string cwd(Sass::File::get_cwd()); - std::string msg_prefix(e.errtype()); bool got_newline = false; msg_stream << msg_prefix << ": "; @@ -55,20 +54,17 @@ namespace Sass { ++msg; } if (!got_newline) msg_stream << "\n"; - if (e.import_stack) { - for (size_t i = 1; i < e.import_stack->size() - 1; ++i) { - std::string path((*e.import_stack)[i]->imp_path); - std::string rel_path(Sass::File::abs2rel(path, cwd, cwd)); - msg_stream << std::string(msg_prefix.size() + 2, ' '); - msg_stream << (i == 1 ? " on line " : " from line "); - msg_stream << e.pstate.line + 1 << " of " << rel_path << "\n"; - } - } - else { + + if (e.traces.empty()) { + // we normally should have some traces, still here as a fallback std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); msg_stream << std::string(msg_prefix.size() + 2, ' '); msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; } + else { + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << traces_to_string(e.traces, " "); + } // now create the code trace (ToDo: maybe have util functions?) if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp index 25abd49ba..34c591a24 100644 --- a/src/libsass/src/sass_values.cpp +++ b/src/libsass/src/sass_values.cpp @@ -4,6 +4,7 @@ #include "util.hpp" #include "eval.hpp" #include "values.hpp" +#include "operators.hpp" #include "sass/values.h" #include "sass_values.hpp" @@ -299,41 +300,41 @@ extern "C" { // see if it's a relational expression switch(op) { - case Sass_OP::EQ: return sass_make_boolean(Eval::eq(lhs, rhs)); - case Sass_OP::NEQ: return sass_make_boolean(!Eval::eq(lhs, rhs)); - case Sass_OP::GT: return sass_make_boolean(!Eval::lt(lhs, rhs, "gt") && !Eval::eq(lhs, rhs)); - case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(lhs, rhs, "gte")); - case Sass_OP::LT: return sass_make_boolean(Eval::lt(lhs, rhs, "lt")); - case Sass_OP::LTE: return sass_make_boolean(Eval::lt(lhs, rhs, "lte") || Eval::eq(lhs, rhs)); + case Sass_OP::EQ: return sass_make_boolean(Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return sass_make_boolean(Operators::neq(lhs, rhs)); + case Sass_OP::GT: return sass_make_boolean(Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return sass_make_boolean(Operators::gte(lhs, rhs)); + case Sass_OP::LT: return sass_make_boolean(Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return sass_make_boolean(Operators::lte(lhs, rhs)); case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); - default: break; + default: break; } if (sass_value_is_number(a) && sass_value_is_number(b)) { Number_Ptr_Const l_n = Cast(lhs); Number_Ptr_Const r_n = Cast(rhs); - rv = Eval::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); + rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); } else if (sass_value_is_number(a) && sass_value_is_color(a)) { Number_Ptr_Const l_n = Cast(lhs); Color_Ptr_Const r_c = Cast(rhs); - rv = Eval::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); + rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); } else if (sass_value_is_color(a) && sass_value_is_number(b)) { Color_Ptr_Const l_c = Cast(lhs); Number_Ptr_Const r_n = Cast(rhs); - rv = Eval::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); + rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); } else if (sass_value_is_color(a) && sass_value_is_color(b)) { Color_Ptr_Const l_c = Cast(lhs); Color_Ptr_Const r_c = Cast(rhs); - rv = Eval::op_colors(op, *l_c, *r_c, options, l_c->pstate()); + rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate()); } else /* convert other stuff to string and apply operation */ { Value_Ptr l_v = Cast(lhs); Value_Ptr r_v = Cast(rhs); - rv = Eval::op_strings(op, *l_v, *r_v, options, l_v->pstate()); + rv = Operators::op_strings(op, *l_v, *r_v, options, l_v->pstate()); } // ToDo: maybe we should should return null value? diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp index 49187d95d..60f69ab76 100644 --- a/src/libsass/src/util.cpp +++ b/src/libsass/src/util.cpp @@ -96,8 +96,9 @@ namespace Sass { } // read css string (handle multiline DELIM) - std::string read_css_string(const std::string& str) + std::string read_css_string(const std::string& str, bool css) { + if (!css) return str; std::string out(""); bool esc = false; for (auto i : str) { @@ -180,6 +181,23 @@ namespace Sass { return out; } + std::string escape_string(const std::string& str) + { + std::string out(""); + for (auto i : str) { + if (i == '\n') { + out += "\\n"; + } else if (i == '\r') { + out += "\\r"; + } else if (i == '\t') { + out += "\\t"; + } else { + out += i; + } + } + return out; + } + std::string comment_to_string(const std::string& text) { std::string str = ""; diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp index ee263711b..4313d502d 100644 --- a/src/libsass/src/util.hpp +++ b/src/libsass/src/util.hpp @@ -22,11 +22,12 @@ namespace Sass { const char* safe_str(const char *, const char* = ""); void free_string_array(char **); char **copy_strings(const std::vector&, char ***, int = 0); - std::string read_css_string(const std::string& str); + std::string read_css_string(const std::string& str, bool css = true); std::string evacuate_escapes(const std::string& str); std::string string_to_output(const std::string& str); std::string comment_to_string(const std::string& text); std::string read_hex_escapes(const std::string& str); + std::string escape_string(const std::string& str); void newline_to_space(std::string& str); std::string quote(const std::string&, char q = 0); diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets index 2f079b9d5..c1c7d45f3 100644 --- a/src/libsass/win/libsass.targets +++ b/src/libsass/win/libsass.targets @@ -97,6 +97,8 @@ + + diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters index 36d85b389..980f00f3f 100644 --- a/src/libsass/win/libsass.vcxproj.filters +++ b/src/libsass/win/libsass.vcxproj.filters @@ -302,6 +302,12 @@ Sources + + Sources + + + Sources + Sources From 5a8ea51363f2897e4e9f6ac85211c55e49824cd9 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sat, 17 Mar 2018 18:04:56 +1100 Subject: [PATCH 115/286] Lock to nan@2.9.2 Work around https://github.com/nodejs/nan/issues/755 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0772a83b..4f0d45758 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.9.2", + "nan": "2.9.2", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", "request": "~2.79.0", From b5c73acb5df019b552c49aaf8bcb46c868f7cf13 Mon Sep 17 00:00:00 2001 From: Benjamin Byholm Date: Sat, 17 Mar 2018 14:30:33 +0200 Subject: [PATCH 116/286] Update NAN --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4f0d45758..5a2267b06 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "2.9.2", + "nan": "^2.10.0", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", "request": "~2.79.0", From ad00d8b46c73b9cc705b504b2b17ab75b8ca9f93 Mon Sep 17 00:00:00 2001 From: Benjamin Byholm Date: Sat, 17 Mar 2018 14:30:50 +0200 Subject: [PATCH 117/286] Synchronous call should be synchronous --- src/callback_bridge.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callback_bridge.h b/src/callback_bridge.h index 918cf8930..2670fa1dc 100644 --- a/src/callback_bridge.h +++ b/src/callback_bridge.h @@ -107,7 +107,7 @@ T CallbackBridge::operator()(std::vector argv) { argv_v8.push_back(Nan::New(wrapper)); return this->post_process_return_value( - this->callback->Call(argv_v8.size(), &argv_v8[0]) + Nan::Call(*callback, argv_v8.size(), &argv_v8[0]).ToLocalChecked() ); } else { /* From 968020e9fe20d9ab463de4e04125b41571ef8a07 Mon Sep 17 00:00:00 2001 From: Benjamin Byholm Date: Sat, 17 Mar 2018 14:35:54 +0200 Subject: [PATCH 118/286] Propagate async_context --- src/binding.cpp | 5 +++-- src/callback_bridge.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/binding.cpp b/src/binding.cpp index 4f2cfdaac..de0a5234a 100644 --- a/src/binding.cpp +++ b/src/binding.cpp @@ -229,6 +229,7 @@ int GetResult(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = fal void MakeCallback(uv_work_t* req) { Nan::HandleScope scope; + Nan::AsyncResource async("sass:MakeCallback"); Nan::TryCatch try_catch; sass_context_wrapper* ctx_w = static_cast(req->data); @@ -245,7 +246,7 @@ void MakeCallback(uv_work_t* req) { if (status == 0 && ctx_w->success_callback) { // if no error, do callback(null, result) - ctx_w->success_callback->Call(0, 0); + ctx_w->success_callback->Call(0, 0, &async); } else if (ctx_w->error_callback) { // if error, do callback(error) @@ -253,7 +254,7 @@ void MakeCallback(uv_work_t* req) { v8::Local argv[] = { Nan::New(err).ToLocalChecked() }; - ctx_w->error_callback->Call(1, argv); + ctx_w->error_callback->Call(1, argv, &async); } if (try_catch.HasCaught()) { Nan::FatalException(try_catch); diff --git a/src/callback_bridge.h b/src/callback_bridge.h index 2670fa1dc..68ecaa809 100644 --- a/src/callback_bridge.h +++ b/src/callback_bridge.h @@ -151,6 +151,7 @@ void CallbackBridge::dispatched_async_uv_callback(uv_async_t *req) { * post_process_args(). */ Nan::HandleScope scope; + Nan::AsyncResource async("sass:CallbackBridge"); Nan::TryCatch try_catch; std::vector> argv_v8 = bridge->pre_process_args(bridge->argv); @@ -159,7 +160,7 @@ void CallbackBridge::dispatched_async_uv_callback(uv_async_t *req) { } argv_v8.push_back(Nan::New(bridge->wrapper)); - bridge->callback->Call(argv_v8.size(), &argv_v8[0]); + bridge->callback->Call(argv_v8.size(), &argv_v8[0], &async); if (try_catch.HasCaught()) { Nan::FatalException(try_catch); From 2558763266ecb515951809e5d6b9c846faa17a14 Mon Sep 17 00:00:00 2001 From: Benjamin Byholm Date: Sat, 17 Mar 2018 14:44:10 +0200 Subject: [PATCH 119/286] Silence g++7 switch case fallthrough warnings --- src/sass_types/color.cpp | 2 +- src/sass_types/color.h | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sass_types/color.cpp b/src/sass_types/color.cpp index 57efab3da..40358a2c3 100644 --- a/src/sass_types/color.cpp +++ b/src/sass_types/color.cpp @@ -28,7 +28,7 @@ namespace SassTypes } a = Nan::To(raw_val[3]).FromJust(); - // fall through vvv + NODE_SASS_FALLTHROUGH; case 3: if (!raw_val[0]->IsNumber() || !raw_val[1]->IsNumber() || !raw_val[2]->IsNumber()) { diff --git a/src/sass_types/color.h b/src/sass_types/color.h index 0be355110..1bf904307 100644 --- a/src/sass_types/color.h +++ b/src/sass_types/color.h @@ -4,6 +4,12 @@ #include #include "sass_value_wrapper.h" +#if defined(__GNUC__) && __GNUC__ >= 7 +#define NODE_SASS_FALLTHROUGH __attribute__ ((fallthrough)) +#else +#define NODE_SASS_FALLTHROUGH +#endif + namespace SassTypes { class Color : public SassValueWrapper { From b2df434a02c10b2263d3879fe75dfbed4460b75f Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Sun, 18 Mar 2018 08:23:14 +1100 Subject: [PATCH 120/286] 4.8.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a2267b06..d61efb451 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.8.2", + "version": "4.8.3", "libsass": "3.5.2", "description": "Wrapper around libsass", "license": "MIT", From 8e69acf0ff5b736d86fcf94eab85283bd2a5e2e1 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 29 Mar 2018 00:59:41 -0500 Subject: [PATCH 121/286] Add note about bumping Request (#2291) --- .github/ISSUE_TEMPLATE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 7359135cc..2c3f5d06d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,7 @@ this will be freed by resources + // make sure we free the source even if not processed! + // if (resources.size() == 0 && source_c_str) free(source_c_str); + // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); + // source_c_str = 0; srcmap_c_str = 0; + } + + File_Context::~File_Context() + { + } + + void Context::collect_extensions(const char* exts_str) + { + if (exts_str) { + const char* beg = exts_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string ext(beg, end - beg); + if (!ext.empty()) { + extensions.push_back(ext); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string ext(beg); + if (!ext.empty()) { + extensions.push_back(ext); + } + } + } + + void Context::collect_extensions(string_list* paths_array) + { + while (paths_array) + { + collect_extensions(paths_array->string); + paths_array = paths_array->next; + } + } + + void Context::collect_include_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + } + } + + void Context::collect_include_paths(string_list* paths_array) + { + while (paths_array) + { + collect_include_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + void Context::collect_plugin_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + } + } + + void Context::collect_plugin_paths(string_list* paths_array) + { + while (paths_array) + { + collect_plugin_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + // resolve the imp_path in base_path or include_paths + // looks for alternatives and returns a list from one directory + std::vector Context::find_includes(const Importer& import) + { + // include configured extensions + std::vector exts(File::defaultExtensions); + if (extensions.size() > 0) { + exts.insert(exts.end(), extensions.begin(), extensions.end()); + } + // make sure we resolve against an absolute path + std::string base_path(rel2abs(import.base_path)); + // first try to resolve the load path relative to the base path + std::vector vec(resolve_includes(base_path, import.imp_path, exts)); + // then search in every include path (but only if nothing found yet) + for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) + { + // call resolve_includes and individual base path and append all results + std::vector resolved(resolve_includes(include_paths[i], import.imp_path, exts)); + if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); + } + // return vector + return vec; + } + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res) + { + + // do not parse same resource twice + // maybe raise an error in this case + // if (sheets.count(inc.abs_path)) { + // free(res.contents); free(res.srcmap); + // throw std::runtime_error("duplicate resource registered"); + // return; + // } + + // get index for this resource + size_t idx = resources.size(); + + // tell emitter about new resource + emitter.add_source_index(idx); + + // put resources under our control + // the memory will be freed later + resources.push_back(res); + + // add a relative link to the working directory + included_files.push_back(inc.abs_path); + // add a relative link to the source map output file + srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); + + // get pointer to the loaded content + Sass_Import_Entry import = sass_make_import( + inc.imp_path.c_str(), + inc.abs_path.c_str(), + res.contents, + res.srcmap + ); + // add the entry to the stack + import_stack.push_back(import); + + // get pointer to the loaded content + const char* contents = resources[idx].contents; + // keep a copy of the path around (for parserstates) + // ToDo: we clean it, but still not very elegant!? + strings.push_back(sass_copy_c_string(inc.abs_path.c_str())); + // create the initial parser state from resource + ParserState pstate(strings.back(), contents, idx); + + // check existing import stack for possible recursion + for (size_t i = 0; i < import_stack.size() - 2; ++i) { + auto parent = import_stack[i]; + if (std::strcmp(parent->abs_path, import->abs_path) == 0) { + std::string cwd(File::get_cwd()); + // make path relative to the current directory + std::string stack("An @import loop has been found:"); + for (size_t n = 1; n < i + 2; ++n) { + stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + + " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); + } + // implement error throw directly until we + // decided how to handle full stack traces + throw Exception::InvalidSyntax(pstate, traces, stack); + // error(stack, prstate ? *prstate : pstate, import_stack); + } + } + + // create a parser instance from the given c_str buffer + Parser p(Parser::from_c_str(contents, *this, traces, pstate)); + // do not yet dispose these buffers + sass_import_take_source(import); + sass_import_take_srcmap(import); + // then parse the root block + Block_Obj root = p.parse(); + // delete memory of current stack frame + sass_delete_import(import_stack.back()); + // remove current stack frame + import_stack.pop_back(); + // create key/value pair for ast node + std::pair + ast_pair(inc.abs_path, { res, root }); + // register resulting resource + sheets.insert(ast_pair); + } + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) + { + traces.push_back(Backtrace(prstate)); + register_resource(inc, res); + traces.pop_back(); + } + + // Add a new import to the context (called from `import_url`) + Include Context::load_import(const Importer& imp, ParserState pstate) + { + + // search for valid imports (ie. partials) on the filesystem + // this may return more than one valid result (ambiguous imp_path) + const std::vector resolved(find_includes(imp)); + + // error nicely on ambiguous imp_path + if (resolved.size() > 1) { + std::stringstream msg_stream; + msg_stream << "It's not clear which file to import for "; + msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; + msg_stream << "Candidates:" << "\n"; + for (size_t i = 0, L = resolved.size(); i < L; ++i) + { msg_stream << " " << resolved[i].imp_path << "\n"; } + msg_stream << "Please delete or rename all but one of these files." << "\n"; + error(msg_stream.str(), pstate, traces); + } + + // process the resolved entry + else if (resolved.size() == 1) { + bool use_cache = c_importers.size() == 0; + if (resolved[0].deprecated) { + // emit deprecation warning when import resolves to a .css file + deprecated( + "Including .css files with @import is non-standard behaviour which will be removed in future versions of LibSass.", + "Use a custom importer to maintain this behaviour. Check your implementations documentation on how to create a custom importer.", + true, pstate + ); + } + // use cache for the resource loading + if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; + // try to read the content of the resolved file entry + // the memory buffer returned must be freed by us! + if (char* contents = read_file(resolved[0].abs_path)) { + // register the newly resolved file resource + register_resource(resolved[0], { contents, 0 }, pstate); + // return resolved entry + return resolved[0]; + } + } + + // nothing found + return { imp, "" }; + + } + + void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { + + ParserState pstate(imp->pstate()); + std::string imp_path(unquote(load_path)); + std::string protocol("file"); + + using namespace Prelexer; + if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { + + protocol = std::string(imp_path.c_str(), proto - 3); + // if (protocol.compare("file") && true) { } + } + + // add urls (protocol other than file) and urls without procotol to `urls` member + // ToDo: if ctx_path is already a file resource, we should not add it here? + if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { + imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); + } + else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { + String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); + Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); + Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); + loc_args->append(loc_arg); + Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); + imp->urls().push_back(new_url); + } + else { + const Importer importer(imp_path, ctx_path); + Include include(load_import(importer, pstate)); + if (include.abs_path.empty()) { + error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); + } + imp->incs().push_back(include); + } + + } + + + // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet + bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) + { + // unique counter + size_t count = 0; + // need one correct import + bool has_import = false; + // process all custom importers (or custom headers) + for (Sass_Importer_Entry& importer_ent : importers) { + // int priority = sass_importer_get_priority(importer); + Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); + // skip importer if it returns NULL + if (Sass_Import_List includes = + fn(load_path.c_str(), importer_ent, c_compiler) + ) { + // get c pointer copy to iterate over + Sass_Import_List it_includes = includes; + while (*it_includes) { ++count; + // create unique path to use as key + std::string uniq_path = load_path; + if (!only_one && count) { + std::stringstream path_strm; + path_strm << uniq_path << ":" << count; + uniq_path = path_strm.str(); + } + // create the importer struct + Importer importer(uniq_path, ctx_path); + // query data from the current include + Sass_Import_Entry include_ent = *it_includes; + char* source = sass_import_take_source(include_ent); + char* srcmap = sass_import_take_srcmap(include_ent); + size_t line = sass_import_get_error_line(include_ent); + size_t column = sass_import_get_error_column(include_ent); + const char *abs_path = sass_import_get_abs_path(include_ent); + // handle error message passed back from custom importer + // it may (or may not) override the line and column info + if (const char* err_message = sass_import_get_error_message(include_ent)) { + if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); + if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); + else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); + } + // content for import was set + else if (source) { + // resolved abs_path should be set by custom importer + // use the created uniq_path as fallback (maybe enforce) + std::string path_key(abs_path ? abs_path : uniq_path); + // create the importer struct + Include include(importer, path_key); + // attach information to AST node + imp->incs().push_back(include); + // register the resource buffers + register_resource(include, { source, srcmap }, pstate); + } + // only a path was retuned + // try to load it like normal + else if(abs_path) { + // checks some urls to preserve + // `http://`, `https://` and `//` + // or dispatchs to `import_file` + // which will check for a `.css` extension + // or resolves the file on the filesystem + // added and resolved via `add_file` + // finally stores everything on `imp` + import_url(imp, abs_path, ctx_path); + } + // move to next + ++it_includes; + } + // deallocate the returned memory + sass_delete_import_list(includes); + // set success flag + has_import = true; + // break out of loop + if (only_one) break; + } + } + // return result + return has_import; + } + + void register_function(Context&, Signature sig, Native_Function f, Env* env); + void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); + void register_overload_stub(Context&, std::string name, Env* env); + void register_built_in_functions(Context&, Env* env); + void register_c_functions(Context&, Env* env, Sass_Function_List); + void register_c_function(Context&, Env* env, Sass_Function_Entry); + + char* Context::render(Block_Obj root) + { + // check for valid block + if (!root) return 0; + // start the render process + root->perform(&emitter); + // finish emitter stream + emitter.finalize(); + // get the resulting buffer from stream + OutputBuffer emitted = emitter.get_buffer(); + // should we append a source map url? + if (!c_options.omit_source_map_url) { + // generate an embeded source map + if (c_options.source_map_embed) { + emitted.buffer += linefeed; + emitted.buffer += format_embedded_source_map(); + } + // or just link the generated one + else if (source_map_file != "") { + emitted.buffer += linefeed; + emitted.buffer += format_source_mapping_url(source_map_file); + } + } + // create a copy of the resulting buffer string + // this must be freed or taken over by implementor + return sass_copy_c_string(emitted.buffer.c_str()); + } + + void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, ParserState pstate) + { + // create a custom import to resolve headers + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + // dispatch headers which will add custom functions + // custom headers are added to the import instance + call_headers(entry_path, ctx_path, pstate, imp); + // increase head count to skip later + head_imports += resources.size() - 1; + // add the statement if we have urls + if (!imp->urls().empty()) root->append(imp); + // process all other resources (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + Block_Obj File_Context::parse() + { + + // check if entry file is given + if (input_path.empty()) return 0; + + // create absolute path from input filename + // ToDo: this should be resolved via custom importers + std::string abs_path(rel2abs(input_path, CWD)); + + // try to load the entry file + char* contents = read_file(abs_path); + + // alternatively also look inside each include path folder + // I think this differs from ruby sass (IMO too late to remove) + for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { + // build absolute path for this include path entry + abs_path = rel2abs(input_path, include_paths[i]); + // try to load the resulting path + contents = read_file(abs_path); + } + + // abort early if no content could be loaded (various reasons) + if (!contents) throw std::runtime_error("File to read not found or unreadable: " + input_path); + + // store entry path + entry_path = abs_path; + + // create entry only for import stack + Sass_Import_Entry import = sass_make_import( + input_path.c_str(), + entry_path.c_str(), + contents, + 0 + ); + // add the entry to the stack + import_stack.push_back(import); + + // create the source entry for file entry + register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); + + // create root ast tree node + return compile(); + + } + + Block_Obj Data_Context::parse() + { + + // check if source string is given + if (!source_c_str) return 0; + + // convert indented sass syntax + if(c_options.is_indented_syntax_src) { + // call sass2scss to convert the string + char * converted = sass2scss(source_c_str, + // preserve the structure as much as possible + SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); + // replace old source_c_str with converted + free(source_c_str); source_c_str = converted; + } + + // remember entry path (defaults to stdin for string) + entry_path = input_path.empty() ? "stdin" : input_path; + + // ToDo: this may be resolved via custom importers + std::string abs_path(rel2abs(entry_path)); + char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); + strings.push_back(abs_path_c_str); + + // create entry only for the import stack + Sass_Import_Entry import = sass_make_import( + entry_path.c_str(), + abs_path_c_str, + source_c_str, + srcmap_c_str + ); + // add the entry to the stack + import_stack.push_back(import); + + // register a synthetic resource (path does not really exist, skip in includes) + register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); + + // create root ast tree node + return compile(); + } + + + + // parse root block from includes + Block_Obj Context::compile() + { + // abort if there is no data + if (resources.size() == 0) return 0; + // get root block from the first style sheet + Block_Obj root = sheets.at(entry_path).root; + // abort on invalid root + if (root.isNull()) return 0; + Env global; // create root environment + // register built-in functions on env + register_built_in_functions(*this, &global); + // register custom functions (defined via C-API) + for (size_t i = 0, S = c_functions.size(); i < S; ++i) + { register_c_function(*this, &global, c_functions[i]); } + // create initial backtrace entry + // create crtp visitor objects + Expand expand(*this, &global); + Cssize cssize(*this); + CheckNesting check_nesting; + // check nesting in all files + for (auto sheet : sheets) { + auto styles = sheet.second; + check_nesting(styles.root); + } + // expand and eval the tree + root = expand(root); + // check nesting + check_nesting(root); + // merge and bubble certain rules + root = cssize(root); + // should we extend something? + if (!subset_map.empty()) { + // create crtp visitor object + Extend extend(subset_map); + extend.setEval(expand.eval); + // extend tree nodes + extend(root); + } + + // clean up by removing empty placeholders + // ToDo: maybe we can do this somewhere else? + Remove_Placeholders remove_placeholders; + root->perform(&remove_placeholders); + // return processed tree + return root; + } + // EO compile + + std::string Context::format_embedded_source_map() + { + std::string map = emitter.render_srcmap(*this); + std::istringstream is( map ); + std::ostringstream buffer; + base64::encoder E; + E.encode(is, buffer); + std::string url = "data:application/json;base64," + buffer.str(); + url.erase(url.size() - 1); + return "/*# sourceMappingURL=" + url + " */"; + } + + std::string Context::format_source_mapping_url(const std::string& file) + { + std::string url = abs2rel(file, output_path, CWD); + return "/*# sourceMappingURL=" + url + " */"; + } + + char* Context::render_srcmap() + { + if (source_map_file == "") return 0; + std::string map = emitter.render_srcmap(*this); + return sass_copy_c_string(map.c_str()); + } + + + // for data context we want to start after "stdin" + // we probably always want to skip the header includes? + std::vector Context::get_included_files(bool skip, size_t headers) + { + // create a copy of the vector for manipulations + std::vector includes = included_files; + if (includes.size() == 0) return includes; + if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } + else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } + includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); + std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); + return includes; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + std::stringstream ss; + ss << def->name() << "[f]" << arity; + def->environment(env); + (*env)[ss.str()] = def; + } + + void register_overload_stub(Context& ctx, std::string name, Env* env) + { + Definition_Ptr stub = SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + 0, + name, + 0, + 0, + true); + (*env)[name + "[f]"] = stub; + } + + + void register_built_in_functions(Context& ctx, Env* env) + { + using namespace Functions; + // RGB Functions + register_function(ctx, rgb_sig, rgb, env); + register_overload_stub(ctx, "rgba", env); + register_function(ctx, rgba_4_sig, rgba_4, 4, env); + register_function(ctx, rgba_2_sig, rgba_2, 2, env); + register_function(ctx, red_sig, red, env); + register_function(ctx, green_sig, green, env); + register_function(ctx, blue_sig, blue, env); + register_function(ctx, mix_sig, mix, env); + // HSL Functions + register_function(ctx, hsl_sig, hsl, env); + register_function(ctx, hsla_sig, hsla, env); + register_function(ctx, hue_sig, hue, env); + register_function(ctx, saturation_sig, saturation, env); + register_function(ctx, lightness_sig, lightness, env); + register_function(ctx, adjust_hue_sig, adjust_hue, env); + register_function(ctx, lighten_sig, lighten, env); + register_function(ctx, darken_sig, darken, env); + register_function(ctx, saturate_sig, saturate, env); + register_function(ctx, desaturate_sig, desaturate, env); + register_function(ctx, grayscale_sig, grayscale, env); + register_function(ctx, complement_sig, complement, env); + register_function(ctx, invert_sig, invert, env); + // Opacity Functions + register_function(ctx, alpha_sig, alpha, env); + register_function(ctx, opacity_sig, alpha, env); + register_function(ctx, opacify_sig, opacify, env); + register_function(ctx, fade_in_sig, opacify, env); + register_function(ctx, transparentize_sig, transparentize, env); + register_function(ctx, fade_out_sig, transparentize, env); + // Other Color Functions + register_function(ctx, adjust_color_sig, adjust_color, env); + register_function(ctx, scale_color_sig, scale_color, env); + register_function(ctx, change_color_sig, change_color, env); + register_function(ctx, ie_hex_str_sig, ie_hex_str, env); + // String Functions + register_function(ctx, unquote_sig, sass_unquote, env); + register_function(ctx, quote_sig, sass_quote, env); + register_function(ctx, str_length_sig, str_length, env); + register_function(ctx, str_insert_sig, str_insert, env); + register_function(ctx, str_index_sig, str_index, env); + register_function(ctx, str_slice_sig, str_slice, env); + register_function(ctx, to_upper_case_sig, to_upper_case, env); + register_function(ctx, to_lower_case_sig, to_lower_case, env); + // Number Functions + register_function(ctx, percentage_sig, percentage, env); + register_function(ctx, round_sig, round, env); + register_function(ctx, ceil_sig, ceil, env); + register_function(ctx, floor_sig, floor, env); + register_function(ctx, abs_sig, abs, env); + register_function(ctx, min_sig, min, env); + register_function(ctx, max_sig, max, env); + register_function(ctx, random_sig, random, env); + // List Functions + register_function(ctx, length_sig, length, env); + register_function(ctx, nth_sig, nth, env); + register_function(ctx, set_nth_sig, set_nth, env); + register_function(ctx, index_sig, index, env); + register_function(ctx, join_sig, join, env); + register_function(ctx, append_sig, append, env); + register_function(ctx, zip_sig, zip, env); + register_function(ctx, list_separator_sig, list_separator, env); + register_function(ctx, is_bracketed_sig, is_bracketed, env); + // Map Functions + register_function(ctx, map_get_sig, map_get, env); + register_function(ctx, map_merge_sig, map_merge, env); + register_function(ctx, map_remove_sig, map_remove, env); + register_function(ctx, map_keys_sig, map_keys, env); + register_function(ctx, map_values_sig, map_values, env); + register_function(ctx, map_has_key_sig, map_has_key, env); + register_function(ctx, keywords_sig, keywords, env); + // Introspection Functions + register_function(ctx, type_of_sig, type_of, env); + register_function(ctx, unit_sig, unit, env); + register_function(ctx, unitless_sig, unitless, env); + register_function(ctx, comparable_sig, comparable, env); + register_function(ctx, variable_exists_sig, variable_exists, env); + register_function(ctx, global_variable_exists_sig, global_variable_exists, env); + register_function(ctx, function_exists_sig, function_exists, env); + register_function(ctx, mixin_exists_sig, mixin_exists, env); + register_function(ctx, feature_exists_sig, feature_exists, env); + register_function(ctx, call_sig, call, env); + register_function(ctx, content_exists_sig, content_exists, env); + register_function(ctx, get_function_sig, get_function, env); + // Boolean Functions + register_function(ctx, not_sig, sass_not, env); + register_function(ctx, if_sig, sass_if, env); + // Misc Functions + register_function(ctx, inspect_sig, inspect, env); + register_function(ctx, unique_id_sig, unique_id, env); + // Selector functions + register_function(ctx, selector_nest_sig, selector_nest, env); + register_function(ctx, selector_append_sig, selector_append, env); + register_function(ctx, selector_extend_sig, selector_extend, env); + register_function(ctx, selector_replace_sig, selector_replace, env); + register_function(ctx, selector_unify_sig, selector_unify, env); + register_function(ctx, is_superselector_sig, is_superselector, env); + register_function(ctx, simple_selectors_sig, simple_selectors, env); + register_function(ctx, selector_parse_sig, selector_parse, env); + } + + void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) + { + while (descrs && *descrs) { + register_c_function(ctx, env, *descrs); + ++descrs; + } + } + void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) + { + Definition_Ptr def = make_c_function(descr, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + +} diff --git a/src/libsass/context.h b/src/libsass/context.h new file mode 100644 index 000000000..29754b75f --- /dev/null +++ b/src/libsass/context.h @@ -0,0 +1,173 @@ +#ifndef SASS_C_CONTEXT_H +#define SASS_C_CONTEXT_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; // base struct +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// Create and initialize an option struct +ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); +// Create and initialize a specific context +ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); +ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); +ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// Usefull if you only want to query the included files +ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); +ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); + +// Release all memory allocated with the compiler +// This does _not_ include any contexts or options +ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); +ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); +ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for context from specific implementation +ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); +ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + + +// Getters for Context_Option values +ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); +ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); +ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); + +// Setters for Context_Option values +ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); +ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); +ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); +ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); +ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); + + +// Getters for Sass_Context values +ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); +ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); + +// Getters for options include path array +ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); + +// Calculate the size of the stored null terminated array +ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); + +// Take ownership of memory (value on context is set to 0) +ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); + +// Getters for Sass_Compiler options +ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); +ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); +ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); +ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); + +// Push function for import extenions +ADDAPI void ADDCALL sass_option_push_import_extension (struct Sass_Options* options, const char* ext); + +// Push function for paths (no manipulation support for now) +ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); + +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); +ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/src/libsass/context.hpp b/src/libsass/context.hpp new file mode 100644 index 000000000..f14e69f6d --- /dev/null +++ b/src/libsass/context.hpp @@ -0,0 +1,155 @@ +#ifndef SASS_CONTEXT_H +#define SASS_CONTEXT_H + +#include +#include +#include + +#define BUFFERSIZE 255 +#include "b64/encode.h" + +#include "ast_fwd_decl.hpp" +#include "kwd_arg_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "sass_context.hpp" +#include "environment.hpp" +#include "source_map.hpp" +#include "subset_map.hpp" +#include "backtrace.hpp" +#include "output.hpp" +#include "plugins.hpp" +#include "file.hpp" + + +struct Sass_Function; + +namespace Sass { + + class Context { + public: + void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); + bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; + bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; + + private: + bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); + + public: + const std::string CWD; + struct Sass_Options& c_options; + std::string entry_path; + size_t head_imports; + Plugins plugins; + Output emitter; + + // generic ast node garbage container + // used to avoid possible circular refs + std::vector ast_gc; + // resources add under our control + // these are guaranteed to be freed + std::vector strings; + std::vector resources; + std::map sheets; + Subset_Map subset_map; + std::vector import_stack; + std::vector callee_stack; + std::vector traces; + + struct Sass_Compiler* c_compiler; + + // absolute paths to includes + std::vector included_files; + // relative includes for sourcemap + std::vector srcmap_links; + // vectors above have same size + + std::vector plugin_paths; // relative paths to load plugins + std::vector include_paths; // lookup paths for includes + std::vector extensions; // lookup extensions for imports` + + + + + + void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); + + std::vector c_headers; + std::vector c_importers; + std::vector c_functions; + + void add_c_header(Sass_Importer_Entry header); + void add_c_importer(Sass_Importer_Entry importer); + void add_c_function(Sass_Function_Entry function); + + const std::string indent; // String to be used for indentation + const std::string linefeed; // String to be used for line feeds + const std::string input_path; // for relative paths in src-map + const std::string output_path; // for relative paths to the output + const std::string source_map_file; // path to source map file (enables feature) + const std::string source_map_root; // path for sourceRoot property (pass-through) + + virtual ~Context(); + Context(struct Sass_Context&); + virtual Block_Obj parse() = 0; + virtual Block_Obj compile(); + virtual char* render(Block_Obj root); + virtual char* render_srcmap(); + + void register_resource(const Include&, const Resource&); + void register_resource(const Include&, const Resource&, ParserState&); + std::vector find_includes(const Importer& import); + Include load_import(const Importer&, ParserState pstate); + + Sass_Output_Style output_style() { return c_options.output_style; }; + std::vector get_included_files(bool skip = false, size_t headers = 0); + + private: + void collect_plugin_paths(const char* paths_str); + void collect_plugin_paths(string_list* paths_array); + void collect_include_paths(const char* paths_str); + void collect_include_paths(string_list* paths_array); + void collect_extensions(const char* extensions_str); + void collect_extensions(string_list* extensions_array); + std::string format_embedded_source_map(); + std::string format_source_mapping_url(const std::string& out_path); + + + // void register_built_in_functions(Env* env); + // void register_function(Signature sig, Native_Function f, Env* env); + // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); + // void register_overload_stub(std::string name, Env* env); + + public: + const std::string& cwd() { return CWD; }; + }; + + class File_Context : public Context { + public: + File_Context(struct Sass_File_Context& ctx) + : Context(ctx) + { } + virtual ~File_Context(); + virtual Block_Obj parse(); + }; + + class Data_Context : public Context { + public: + char* source_c_str; + char* srcmap_c_str; + Data_Context(struct Sass_Data_Context& ctx) + : Context(ctx) + { + source_c_str = ctx.source_string; + srcmap_c_str = ctx.srcmap_string; + ctx.source_string = 0; // passed away + ctx.srcmap_string = 0; // passed away + } + virtual ~Data_Context(); + virtual Block_Obj parse(); + }; + +} + +#endif diff --git a/src/libsass/contributing.md b/src/libsass/contributing.md new file mode 100644 index 000000000..4a2d470ef --- /dev/null +++ b/src/libsass/contributing.md @@ -0,0 +1,17 @@ +First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! + +## I'm a programmer + +Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! + +To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). + +## I'm not a backend programmer + +COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. + +Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. + +## I don't know what a computer is? + +Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/src/libsass/cssize.cpp b/src/libsass/cssize.cpp new file mode 100644 index 000000000..6a12fdf7b --- /dev/null +++ b/src/libsass/cssize.cpp @@ -0,0 +1,606 @@ +#include "sass.hpp" +#include +#include +#include + +#include "cssize.hpp" +#include "context.hpp" + +namespace Sass { + + Cssize::Cssize(Context& ctx) + : ctx(ctx), + traces(ctx.traces), + block_stack(std::vector()), + p_stack(std::vector()) + { } + + Statement_Ptr Cssize::parent() + { + return p_stack.size() ? p_stack.back() : block_stack.front(); + } + + Block_Ptr Cssize::operator()(Block_Ptr b) + { + Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); + // bb->tabs(b->tabs()); + block_stack.push_back(bb); + append_block(b, bb); + block_stack.pop_back(); + return bb.detach(); + } + + Statement_Ptr Cssize::operator()(Trace_Ptr t) + { + traces.push_back(Backtrace(t->pstate())); + auto result = t->block()->perform(this); + traces.pop_back(); + return result; + } + + Statement_Ptr Cssize::operator()(Declaration_Ptr d) + { + String_Obj property = Cast(d->property()); + + if (Declaration_Ptr dd = Cast(parent())) { + String_Obj parent_property = Cast(dd->property()); + property = SASS_MEMORY_NEW(String_Constant, + d->property()->pstate(), + parent_property->to_string() + "-" + property->to_string()); + if (!dd->value()) { + d->tabs(dd->tabs() + 1); + } + } + + Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, + d->pstate(), + property, + d->value(), + d->is_important(), + d->is_custom_property()); + dd->is_indented(d->is_indented()); + dd->tabs(d->tabs()); + + p_stack.push_back(dd); + Block_Obj bb = d->block() ? operator()(d->block()) : NULL; + p_stack.pop_back(); + + if (bb && bb->length()) { + if (dd->value() && !dd->value()->is_invisible()) { + bb->unshift(dd); + } + return bb.detach(); + } + else if (dd->value() && !dd->value()->is_invisible()) { + return dd.detach(); + } + + return 0; + } + + Statement_Ptr Cssize::operator()(Directive_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + if (parent()->statement_type() == Statement::RULESET) + { + return (r->is_keyframes()) ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); + } + + p_stack.push_back(r); + Directive_Obj rr = SASS_MEMORY_NEW(Directive, + r->pstate(), + r->keyword(), + r->selector(), + r->block() ? operator()(r->block()) : 0); + if (r->value()) rr->value(r->value()); + p_stack.pop_back(); + + bool directive_exists = false; + size_t L = rr->block() ? rr->block()->length() : 0; + for (size_t i = 0; i < L && !directive_exists; ++i) { + Statement_Obj s = r->block()->at(i); + if (s->statement_type() != Statement::BUBBLE) directive_exists = true; + else { + Bubble_Obj s_obj = Cast(s); + s = s_obj->node(); + if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; + else directive_exists = (Cast(s)->keyword() == rr->keyword()); + } + + } + + Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); + if (!(directive_exists || rr->is_keyframes())) + { + Directive_Ptr empty_node = Cast(rr); + empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); + result->append(empty_node); + } + + Block_Obj db = rr->block(); + if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); + Block_Obj ss = debubble(db, rr); + for (size_t i = 0, L = ss->length(); i < L; ++i) { + result->append(ss->at(i)); + } + + return result; + } + + Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, + r->pstate(), + operator()(r->block())); + if (!r->name().isNull()) rr->name(r->name()); + + return debubble(rr->block(), rr); + } + + Statement_Ptr Cssize::operator()(Ruleset_Ptr r) + { + p_stack.push_back(r); + // this can return a string schema + // string schema is not a statement! + // r->block() is already a string schema + // and that is comming from propset expand + Block_Ptr bb = operator()(r->block()); + // this should protect us (at least a bit) from our mess + // fixing this properly is harder that it should be ... + if (Cast(bb) == NULL) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + } + Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + r->selector(), + bb); + + rr->is_root(r->is_root()); + // rr->tabs(r->block()->tabs()); + p_stack.pop_back(); + + if (!rr->block()) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + } + + Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + for (size_t i = 0, L = rr->block()->length(); i < L; i++) + { + Statement_Ptr s = rr->block()->at(i); + if (bubblable(s)) rules->append(s); + if (!bubblable(s)) props->append(s); + } + + if (props->length()) + { + Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + pb->concat(props); + rr->block(pb); + + for (size_t i = 0, L = rules->length(); i < L; i++) + { + Statement_Ptr stm = rules->at(i); + stm->tabs(stm->tabs() + 1); + } + + rules->unshift(rr); + } + + Block_Ptr ptr = rules; + rules = debubble(rules); + void* lp = ptr; + void* rp = rules; + if (lp != rp) { + Block_Obj obj = ptr; + } + + if (!(!rules->length() || + !bubblable(rules->last()) || + parent()->statement_type() == Statement::RULESET)) + { + rules->last()->group_end(true); + } + return rules; + } + + Statement_Ptr Cssize::operator()(Null_Ptr m) + { + return 0; + } + + Statement_Ptr Cssize::operator()(Media_Block_Ptr m) + { + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + if (parent()->statement_type() == Statement::MEDIA) + { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } + + p_stack.push_back(m); + + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + m->media_queries(), + operator()(m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(mm->block(), mm); + } + + Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) + { + if (!m->block()->length()) + { return m; } + + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + p_stack.push_back(m); + + Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + m->condition(), + operator()(m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(mm->block(), mm); + } + + Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) + { + bool tmp = false; + for (size_t i = 0, L = p_stack.size(); i < L; ++i) { + Statement_Ptr s = p_stack[i]; + tmp |= m->exclude_node(s); + } + + if (!tmp && m->block()) + { + Block_Ptr bb = operator()(m->block()); + for (size_t i = 0, L = bb->length(); i < L; ++i) { + // (bb->elements())[i]->tabs(m->tabs()); + Statement_Obj stm = bb->at(i); + if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); + } + if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); + return bb; + } + + if (m->exclude_node(parent())) + { + return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + } + + return bubble(m); + } + + Statement_Ptr Cssize::bubble(Directive_Ptr m) + { + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); + wrapper_block->append(new_rule); + Directive_Obj mm = SASS_MEMORY_NEW(Directive, + m->pstate(), + m->keyword(), + m->selector(), + wrapper_block); + if (m->value()) mm->value(m->value()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) + { + if (!m || !m->block()) return NULL; + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + if (new_rule) { + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + wrapper_block->append(new_rule); + } + + At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, + m->pstate(), + wrapper_block, + m->expression()); + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) + { + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + m->condition(), + wrapper_block); + + mm->tabs(m->tabs()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Media_Block_Ptr m) + { + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + m->media_queries(), + wrapper_block); + + mm->tabs(m->tabs()); + + return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + } + + bool Cssize::bubblable(Statement_Ptr s) + { + return Cast(s) || s->bubbles(); + } + + Block_Ptr Cssize::flatten(Block_Ptr b) + { + Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr ss = b->at(i); + if (Block_Ptr bb = Cast(ss)) { + Block_Obj bs = flatten(bb); + for (size_t j = 0, K = bs->length(); j < K; ++j) { + result->append(bs->at(j)); + } + } + else { + result->append(ss); + } + } + return result; + } + + std::vector> Cssize::slice_by_bubble(Block_Ptr b) + { + std::vector> results; + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj value = b->at(i); + bool key = Cast(value) != NULL; + + if (!results.empty() && results.back().first == key) + { + Block_Obj wrapper_block = results.back().second; + wrapper_block->append(value); + } + else + { + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); + wrapper_block->append(value); + results.push_back(std::make_pair(key, wrapper_block)); + } + } + return results; + } + + Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) + { + Has_Block_Obj previous_parent = 0; + std::vector> baz = slice_by_bubble(children); + Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); + + for (size_t i = 0, L = baz.size(); i < L; ++i) { + bool is_bubble = baz[i].first; + Block_Obj slice = baz[i].second; + + if (!is_bubble) { + if (!parent) { + result->append(slice); + } + else if (previous_parent) { + previous_parent->block()->concat(slice); + } + else { + previous_parent = Cast(SASS_MEMORY_COPY(parent)); + previous_parent->block(slice); + previous_parent->tabs(parent->tabs()); + + result->append(previous_parent); + } + continue; + } + + for (size_t j = 0, K = slice->length(); j < K; ++j) + { + Statement_Ptr ss; + Statement_Obj stm = slice->at(j); + // this has to go now here (too bad) + Bubble_Obj node = Cast(stm); + Media_Block_Ptr m1 = NULL; + Media_Block_Ptr m2 = NULL; + if (parent) m1 = Cast(parent); + if (node) m2 = Cast(node->node()); + if (!parent || + parent->statement_type() != Statement::MEDIA || + node->node()->statement_type() != Statement::MEDIA || + (m1 && m2 && *m1->media_queries() == *m2->media_queries()) + ) + { + ss = node->node(); + } + else + { + List_Obj mq = merge_media_queries( + Cast(node->node()), + Cast(parent) + ); + if (!mq->length()) continue; + if (Media_Block* b = Cast(node->node())) { + b->media_queries(mq); + } + ss = node->node(); + } + + if (!ss) continue; + + ss->tabs(ss->tabs() + node->tabs()); + ss->group_end(node->group_end()); + + Block_Obj bb = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + bb->append(ss->perform(this)); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + + Block_Ptr wrapper = flatten(bb); + wrapper_block->append(wrapper); + + if (wrapper->length()) { + previous_parent = NULL; + } + + if (wrapper_block) { + result->append(wrapper_block); + } + } + } + + return flatten(result); + } + + Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + void Cssize::append_block(Block_Ptr b, Block_Ptr cur) + { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj ith = b->at(i)->perform(this); + if (Block_Ptr bb = Cast(ith)) { + for (size_t j = 0, K = bb->length(); j < K; ++j) { + cur->append(bb->at(j)); + } + } + else if (ith) { + cur->append(ith); + } + } + } + + List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) + { + List_Ptr qq = SASS_MEMORY_NEW(List, + m1->media_queries()->pstate(), + m1->media_queries()->length(), + SASS_COMMA); + + for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { + for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { + Expression_Obj l1 = m1->media_queries()->at(i); + Expression_Obj l2 = m2->media_queries()->at(j); + Media_Query_Ptr mq1 = Cast(l1); + Media_Query_Ptr mq2 = Cast(l2); + Media_Query_Ptr mq = merge_media_query(mq1, mq2); + if (mq) qq->append(mq); + } + } + + return qq; + } + + + Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) + { + + std::string type; + std::string mod; + + std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; + std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); + std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; + + + if (t1.empty()) t1 = t2; + if (t2.empty()) t2 = t1; + + if ((m1 == "not") ^ (m2 == "not")) { + if (t1 == t2) { + return 0; + } + type = m1 == "not" ? t2 : t1; + mod = m1 == "not" ? m2 : m1; + } + else if (m1 == "not" && m2 == "not") { + if (t1 != t2) { + return 0; + } + type = t1; + mod = "not"; + } + else if (t1 != t2) { + return 0; + } else { + type = t1; + mod = m1.empty() ? m2 : m1; + } + + Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, + mq1->pstate(), + 0, + mq1->length() + mq2->length(), + mod == "not", + mod == "only"); + + if (!type.empty()) { + mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); + } + + mm->concat(mq2); + mm->concat(mq1); + + return mm; + } +} diff --git a/src/libsass/custom-functions-internal.md b/src/libsass/custom-functions-internal.md new file mode 100644 index 000000000..57fec82b8 --- /dev/null +++ b/src/libsass/custom-functions-internal.md @@ -0,0 +1,122 @@ +# Developer Documentation + +Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. + +## Sass_C_Function_Descriptor + +```C +struct Sass_C_Function_Descriptor { + const char* signature; + Sass_C_Function function; + void* cookie; +}; +``` + +- `signature`: The function declaration, like `foo($bar, $baz:1)` +- `function`: Reference to the C function callback +- `cookie`: any pointer you want to attach + +### signature + +The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. + + foo($bar, $baz: 2) + +In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. + +### function + +The callback function needs to be of the following form: + +```C +union Sass_Value* call_sass_function( + const union Sass_Value* s_args, + void* cookie +) { + return sass_clone_value(s_args); +} +``` + +### cookie + +The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! + +## Sass_Values + +```C +// allocate memory (copies passed strings) +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// deallocate memory (incl. all copied memory) +void sass_delete_value (const union Sass_Value* val); +``` + +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((size_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +## Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` diff --git a/src/libsass/debugger.hpp b/src/libsass/debugger.hpp new file mode 100644 index 000000000..f1ceabd9a --- /dev/null +++ b/src/libsass/debugger.hpp @@ -0,0 +1,801 @@ +#ifndef SASS_DEBUGGER_H +#define SASS_DEBUGGER_H + +#include +#include +#include "node.hpp" +#include "ast_fwd_decl.hpp" + +using namespace Sass; + +inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); + +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { + debug_ast(const_cast(node), ind, env); +} + +inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : set) { + debug_ast(pair, ind + ""); + // debug_ast(set[pair], ind + "first: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) +{ + size_t pos = 0; + while((pos = str.find(oldStr, pos)) != std::string::npos) + { + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return str; +} + +inline std::string prettyprint(const std::string& str) { + std::string clean = str_replace(str, "\n", "\\n"); + clean = str_replace(clean, " ", "\\t"); + clean = str_replace(clean, "\r", "\\r"); + return clean; +} + +inline std::string longToHex(long long t) { + std::stringstream is; + is << std::hex << t; + return is.str(); +} + +inline std::string pstate_source_position(AST_Node_Ptr node) +{ + std::stringstream str; + Position start(node->pstate()); + Position end(start + node->pstate().offset); + str << (start.file == std::string::npos ? -1 : start.file) + << "@[" << start.line << ":" << start.column << "]" + << "-[" << end.line << ":" << end.column << "]"; +#ifdef DEBUG_SHARED_PTR + str << "x" << node->getRefCount() << "" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); +#endif + return str.str(); +} + +inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) +{ + if (node == 0) return; + if (ind == "") std::cerr << "####################################################################\n"; + if (Cast(node)) { + Bubble_Ptr bubble = Cast(node); + std::cerr << ind << "Bubble " << bubble; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << bubble->tabs(); + std::cerr << std::endl; + debug_ast(bubble->node(), ind + " ", env); + } else if (Cast(node)) { + Trace_Ptr trace = Cast(node); + std::cerr << ind << "Trace " << trace; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [name:" << trace->name() << ", type: " << trace->type() << "]" + << std::endl; + debug_ast(trace->block(), ind + " ", env); + } else if (Cast(node)) { + At_Root_Block_Ptr root_block = Cast(node); + std::cerr << ind << "At_Root_Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << root_block->tabs(); + std::cerr << std::endl; + debug_ast(root_block->expression(), ind + ":", env); + debug_ast(root_block->block(), ind + " ", env); + } else if (Cast(node)) { + Selector_List_Ptr selector = Cast(node); + std::cerr << ind << "Selector_List " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); + std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->schema(), ind + "#{} "); + + for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + +// } else if (Cast(node)) { +// Expression_Ptr expression = Cast(node); +// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; + + } else if (Cast(node)) { + Parent_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Parent_Selector " << selector; +// if (selector->not_selector()) cerr << " [in_declaration]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; +// debug_ast(selector->selector(), ind + "->", env); + + } else if (Cast(node)) { + Complex_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Complex_Selector " << selector + << " (" << pstate_source_position(node) << ")" + << " <" << selector->hash() << ">" + << " [length:" << longToHex(selector->length()) << "]" + << " [weight:" << longToHex(selector->specificity()) << "]" + << " [@media:" << selector->media_block() << "]" + << (selector->is_invisible() ? " [INVISIBLE]": " -") + << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_parent_ref() ? " [has parent]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << " -- "; + std::string del; + switch (selector->combinator()) { + case Complex_Selector::PARENT_OF: del = ">"; break; + case Complex_Selector::PRECEDES: del = "~"; break; + case Complex_Selector::ADJACENT_TO: del = "+"; break; + case Complex_Selector::ANCESTOR_OF: del = " "; break; + case Complex_Selector::REFERENCE: del = "//"; break; + } + // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); + if (selector->tail()) { + debug_ast(selector->tail(), ind + "{" + del + "}", env); + } else if(del != " ") { + std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; + } + ComplexSelectorSet set = selector->sources(); + // debug_sources_set(set, ind + " @--> "); + } else if (Cast(node)) { + Compound_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Compound_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->extended() ? " [extended]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Wrapped_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Wrapped_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->selector(), ind + " () ", env); + } else if (Cast(node)) { + Pseudo_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Pseudo_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->expression(), ind + " <= ", env); + } else if (Cast(node)) { + Attribute_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Attribute_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); + } else if (Cast(node)) { + Class_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Class_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (Cast(node)) { + Id_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Id_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (Cast(node)) { + Element_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Element_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; + std::cerr << std::endl; + } else if (Cast(node)) { + + Placeholder_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; + std::cerr << " (" << pstate_source_position(selector) << ")" + << " <" << selector->hash() << ">" + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (Cast(node)) { + Simple_Selector* selector = Cast(node); + std::cerr << ind << "Simple_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; + + } else if (Cast(node)) { + Selector_Schema_Ptr selector = Cast(node); + std::cerr << ind << "Selector_Schema " << selector; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [@media:" << selector->media_block() << "]" + << (selector->connect_parent() ? " [connect-parent]": " -") + << std::endl; + + debug_ast(selector->contents(), ind + " "); + // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Selector_Ptr selector = Cast(node); + std::cerr << ind << "Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (Cast(node)) { + Media_Query_Expression_Ptr block = Cast(node); + std::cerr << ind << "Media_Query_Expression " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + + } else if (Cast(node)) { + Media_Query_Ptr block = Cast(node); + std::cerr << ind << "Media_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_negated() ? " [is_negated]": " -") + << (block->is_restricted() ? " [is_restricted]": " -") + << std::endl; + debug_ast(block->media_type(), ind + " "); + for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Media_Block_Ptr block = Cast(node); + std::cerr << ind << "Media_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->media_queries(), ind + " =@ "); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Supports_Block_Ptr block = Cast(node); + std::cerr << ind << "Supports_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->condition(), ind + " =@ "); + debug_ast(block->block(), ind + " <>"); + } else if (Cast(node)) { + Supports_Operator_Ptr block = Cast(node); + std::cerr << ind << "Supports_Operator " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->left(), ind + " left) "); + debug_ast(block->right(), ind + " right) "); + } else if (Cast(node)) { + Supports_Negation_Ptr block = Cast(node); + std::cerr << ind << "Supports_Negation " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->condition(), ind + " condition) "); + } else if (Cast(node)) { + At_Root_Query_Ptr block = Cast(node); + std::cerr << ind << "At_Root_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Supports_Declaration_Ptr block = Cast(node); + std::cerr << ind << "Supports_Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Block_Ptr root_block = Cast(node); + std::cerr << ind << "Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (root_block->is_root()) std::cerr << " [root]"; + std::cerr << " " << root_block->tabs() << std::endl; + for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Warning_Ptr block = Cast(node); + std::cerr << ind << "Warning " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->message(), ind + " : "); + } else if (Cast(node)) { + Error_Ptr block = Cast(node); + std::cerr << ind << "Error " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Debug_Ptr block = Cast(node); + std::cerr << ind << "Debug " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->value(), ind + " "); + } else if (Cast(node)) { + Comment_Ptr block = Cast(node); + std::cerr << ind << "Comment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << + " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(block->text(), ind + "// ", env); + } else if (Cast(node)) { + If_Ptr block = Cast(node); + std::cerr << ind << "If " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->predicate(), ind + " = "); + debug_ast(block->block(), ind + " <>"); + debug_ast(block->alternative(), ind + " ><"); + } else if (Cast(node)) { + Return_Ptr block = Cast(node); + std::cerr << ind << "Return " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Extension_Ptr block = Cast(node); + std::cerr << ind << "Extension " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->selector(), ind + "-> ", env); + } else if (Cast(node)) { + Content_Ptr block = Cast(node); + std::cerr << ind << "Content " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << block->media_block() << "]"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Import_Stub_Ptr block = Cast(node); + std::cerr << ind << "Import_Stub " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->imp_path() << "] "; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Import_Ptr block = Cast(node); + std::cerr << ind << "Import " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + // std::vector files_; + for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); + debug_ast(block->import_queries(), ind + "@@ "); + } else if (Cast(node)) { + Assignment_Ptr block = Cast(node); + std::cerr << ind << "Assignment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; + debug_ast(block->value(), ind + "=", env); + } else if (Cast(node)) { + Declaration_Ptr block = Cast(node); + std::cerr << ind << "Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->property(), ind + " prop: ", env); + debug_ast(block->value(), ind + " value: ", env); + debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Keyframe_Rule_Ptr has_block = Cast(node); + std::cerr << ind << "Keyframe_Rule " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->name()) debug_ast(has_block->name(), ind + "@"); + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Directive_Ptr block = Cast(node); + std::cerr << ind << "Directive " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; + debug_ast(block->selector(), ind + "~", env); + debug_ast(block->value(), ind + "+", env); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Each_Ptr block = Cast(node); + std::cerr << ind << "Each " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + For_Ptr block = Cast(node); + std::cerr << ind << "For " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + While_Ptr block = Cast(node); + std::cerr << ind << "While " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Definition_Ptr block = Cast(node); + std::cerr << ind << "Definition " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << block->name() << "] "; + std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; + // this seems to lead to segfaults some times? + // std::cerr << " [signature: " << block->signature() << "] "; + std::cerr << " [native: " << block->native_function() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->parameters(), ind + " params: ", env); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Mixin_Call_Ptr block = Cast(node); + std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); + std::cerr << " (" << pstate_source_position(block) << ")"; + std::cerr << " [" << block->name() << "]"; + std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; + debug_ast(block->arguments(), ind + " args: "); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Ruleset_Ptr ruleset = Cast(node)) { + std::cerr << ind << "Ruleset " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << (ruleset->is_root() ? " [root]" : ""); + std::cerr << std::endl; + debug_ast(ruleset->selector(), ind + ">"); + debug_ast(ruleset->block(), ind + " "); + } else if (Cast(node)) { + Block_Ptr block = Cast(node); + std::cerr << ind << "Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << " [indent: " << block->tabs() << "]" << std::endl; + for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Variable_Ptr expression = Cast(node); + std::cerr << ind << "Variable " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]" << std::endl; + std::string name(expression->name()); + if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); + } else if (Cast(node)) { + Function_Call_Schema_Ptr expression = Cast(node); + std::cerr << ind << "Function_Call_Schema " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << "" << std::endl; + debug_ast(expression->name(), ind + "name: ", env); + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (Cast(node)) { + Function_Call_Ptr expression = Cast(node); + std::cerr << ind << "Function_Call " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->arguments(), ind + " args: ", env); + debug_ast(expression->func(), ind + " func: ", env); + } else if (Cast(node)) { + Function_Ptr expression = Cast(node); + std::cerr << ind << "Function " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->definition(), ind + " definition: ", env); + } else if (Cast(node)) { + Arguments_Ptr expression = Cast(node); + std::cerr << ind << "Arguments " << expression; + if (expression->is_delayed()) std::cerr << " [delayed]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; + if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; + if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; + std::cerr << std::endl; + for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Argument_Ptr expression = Cast(node); + std::cerr << ind << "Argument " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->value().ptr() << "]"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [rest: " << expression->is_rest_argument() << "] "; + std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; + debug_ast(expression->value(), ind + " value: ", env); + } else if (Cast(node)) { + Parameters_Ptr expression = Cast(node); + std::cerr << ind << "Parameters " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; + std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; + std::cerr << std::endl; + for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Parameter_Ptr expression = Cast(node); + std::cerr << ind << "Parameter " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [default: " << expression->default_value().ptr() << "] "; + std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; + } else if (Cast(node)) { + Unary_Expression_Ptr expression = Cast(node); + std::cerr << ind << "Unary_Expression " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type() << "]" << std::endl; + debug_ast(expression->operand(), ind + " operand: ", env); + } else if (Cast(node)) { + Binary_Expression_Ptr expression = Cast(node); + std::cerr << ind << "Binary_Expression " << expression; + if (expression->is_interpolant()) std::cerr << " [is interpolant] "; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [ws_before: " << expression->op().ws_before << "] "; + std::cerr << " [ws_after: " << expression->op().ws_after << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type_name() << "]" << std::endl; + debug_ast(expression->left(), ind + " left: ", env); + debug_ast(expression->right(), ind + " right: ", env); + } else if (Cast(node)) { + Map_Ptr expression = Cast(node); + std::cerr << ind << "Map " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [Hashed]" << std::endl; + for (const auto& i : expression->elements()) { + debug_ast(i.first, ind + " key: "); + debug_ast(i.second, ind + " val: "); + } + } else if (Cast(node)) { + List_Ptr expression = Cast(node); + std::cerr << ind << "List " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->length() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << + " [delayed: " << expression->is_delayed() << "] " << + " [interpolant: " << expression->is_interpolant() << "] " << + " [listized: " << expression->from_selector() << "] " << + " [arglist: " << expression->is_arglist() << "] " << + " [bracketed: " << expression->is_bracketed() << "] " << + " [expanded: " << expression->is_expanded() << "] " << + " [hash: " << expression->hash() << "] " << + std::endl; + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Content_Ptr expression = Cast(node); + std::cerr << ind << "Content " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << expression->media_block() << "]"; + std::cerr << " [Statement]" << std::endl; + } else if (Cast(node)) { + Boolean_Ptr expression = Cast(node); + std::cerr << ind << "Boolean " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << "]" << std::endl; + } else if (Cast(node)) { + Color_Ptr expression = Cast(node); + std::cerr << ind << "Color " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } else if (Cast(node)) { + Number_Ptr expression = Cast(node); + std::cerr << ind << "Number " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << expression->unit() << "]" << + " [hash: " << expression->hash() << "] " << + std::endl; + } else if (Cast(node)) { + Null_Ptr expression = Cast(node); + std::cerr << ind << "Null " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] " + // " [hash: " << expression->hash() << "] " + << std::endl; + } else if (Cast(node)) { + String_Quoted_Ptr expression = Cast(node); + std::cerr << ind << "String_Quoted " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + String_Constant_Ptr expression = Cast(node); + std::cerr << ind << "String_Constant " << expression; + if (expression->concrete_type()) { + std::cerr << " " << expression->concrete_type(); + } + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + String_Schema_Ptr expression = Cast(node); + std::cerr << ind << "String_Schema " << expression; + std::cerr << " (" << pstate_source_position(expression) << ")"; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->css()) std::cerr << " [css]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [is interpolant]"; + if (expression->has_interpolant()) std::cerr << " [has interpolant]"; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + String_Ptr expression = Cast(node); + std::cerr << ind << "String " << expression; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + Expression_Ptr expression = Cast(node); + std::cerr << ind << "Expression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + switch (expression->concrete_type()) { + case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; + case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; + case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; + case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; + case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; + case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; + case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; + case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; + case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; + case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; + case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; + case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; + case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; + case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; + case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; + } + std::cerr << std::endl; + } else if (Cast(node)) { + Has_Block_Ptr has_block = Cast(node); + std::cerr << ind << "Has_Block " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Statement_Ptr statement = Cast(node); + std::cerr << ind << "Statement " << statement; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << statement->tabs() << std::endl; + } + + if (ind == "") std::cerr << "####################################################################\n"; +} + +inline void debug_node(Node* node, std::string ind = "") +{ + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; + if (node->isCombinator()) { + std::cerr << ind; + std::cerr << "Combinator "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + switch (node->combinator()) { + case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; + case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; + case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; + case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; + case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; + } + std::cerr << std::endl; + // debug_ast(node->combinator(), ind + " "); + } else if (node->isSelector()) { + std::cerr << ind; + std::cerr << "Selector "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + debug_ast(node->selector(), ind + " "); + } else if (node->isCollection()) { + std::cerr << ind; + std::cerr << "Collection "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + for(auto n : (*node->collection())) { + debug_node(&n, ind + " "); + } + } else if (node->isNil()) { + std::cerr << ind; + std::cerr << "Nil "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } else { + std::cerr << ind; + std::cerr << "OTHER "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; +} + +/* +inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) +{ + debug_ast(const_cast(node), ind, env); +} +*/ +inline void debug_node(const Node* node, std::string ind = "") +{ + debug_node(const_cast(node), ind); +} + +inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &it : map.values()) { + debug_ast(it.first, ind + "first: "); + debug_ast(it.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : *entries) { + debug_ast(pair.first, ind + "first: "); + debug_ast(pair.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +#endif // SASS_DEBUGGER diff --git a/src/libsass/dev-ast-memory.md b/src/libsass/dev-ast-memory.md new file mode 100644 index 000000000..31004bcf2 --- /dev/null +++ b/src/libsass/dev-ast-memory.md @@ -0,0 +1,223 @@ +# LibSass smart pointer implementation + +LibSass uses smart pointers very similar to `shared_ptr` known +by Boost or C++11. Implementation is a bit less modular since +it was not needed. Various compile time debug options are +available if you need to debug memory life-cycles. + + +## Memory Classes + +### SharedObj + +Base class for the actual node implementations. This ensures +that every object has a reference counter and other values. + +```c++ +class AST_Node : public SharedObj { ... }; +``` + +### SharedPtr (base class for SharedImpl) + +Base class that holds on to the pointer. The reference counter +is stored inside the pointer object directly (`SharedObj`). + +### SharedImpl (inherits from SharedPtr) + +This is the main base class for objects you use in your code. It +will make sure that the memory it points at will be deleted once +all copies to the same object/memory go out of scope. + +```c++ +Class* pointer = new Class(...); +SharedImpl obj(pointer); +``` + +To spare the developer of typing the templated class every time, +we created typedefs for each available AST Node specialization. + +```c++ +typedef SharedImpl Number_Obj; +Number_Obj number = SASS_MEMORY_NEW(...); +``` + + +## Memory life-cycles + +### Pointer pickups + +I often use the terminology of "pickup". This means the moment when +a raw pointer not under any control is assigned to a reference counted +object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be +automatically released once the object goes out of scope (but only +if the reference counter reaches zero). Main point beeing, you don't +have to worry about memory management yourself. + +### Object detach + +Sometimes we can't return reference counted objects directly (see +invalid covariant return types problems below). But we often still +need to use reference objects inside a function to avoid leaks when +something throws. For this you can use `detach`, which basically +detaches the pointer memory from the reference counted object. So +when the reference counted object goes out of scope, it will not +free the attached memory. You are now again in charge of freeing +the memory (just assign it to a reference counted object again). + + +## Circular references + +Reference counted memory implementations are prone to circular references. +This can be addressed by using a multi generation garbage collector. But +for our use-case that seems overkill. There is no way so far for users +(sass code) to create circular references. Therefore we can code around +this possible issue. But developers should be aware of this limitation. + +There are AFAIR two places where circular references could happen. One is +the `sources` member on every `Selector`. The other one can happen in the +extend code (Node handling). The easy way to avoid this is to only assign +complete object clones to these members. If you know the objects lifetime +is longer than the reference you create, you can also just store the raw +pointer. Once needed this could be solved with weak pointers. + + +## Addressing the invalid covariant return types problems + +If you are not familiar with the mentioned problem, you may want +to read up on covariant return types and virtual functions, i.e. + +- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers +- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers +- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr + +We hit this issue at least with the CRTP visitor pattern (eval, expand, +listize and so forth). This means we cannot return reference counted +objects directly. We are forced to return raw pointers or we would need +to have a lot of explicit and expensive upcasts by callers/consumers. + +### Simple functions that allocate new AST Nodes + +In the parser step we often create new objects and can just return a +unique pointer (meaning ownership clearly shifts back to the caller). +The caller/consumer is responsible that the memory is freed. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + return 42; +} +Number_Ptr parse_number() { + Number_Ptr p_nr = SASS_MEMORY_NEW(...); + p_nr->value(parse_integer()); + return p_nr; +} +Number_Obj nr = parse_number(); +``` + +The above would be the encouraged pattern for such simple cases. + +### Allocate new AST Nodes in functions that can throw + +There is a major caveat with the previous example, considering this +more real-life implementation that throws an error. The throw may +happen deep down in another function. Holding raw pointers that +we need to free would leak in this case. + +```c++ +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +``` + +With this `parse_integer` function the previous example would leak memory. +I guess it is pretty obvious, as the allocated memory will not be freed, +as it was never assigned to a SharedObj value. Therefore the above code +would better be written as: + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +// this leaks due to pointer return +// should return Number_Obj instead +// though not possible for virtuals! +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return &nr; // Ptr from Obj +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + +The example above unfortunately will not work as is, since we return a +`Number_Ptr` from that function. Therefore the object allocated inside +the function is already gone when it is picked up again by the caller. +The easy fix for the given simplified use case would be to change the +return type of `parse_number` to `Number_Obj`. Indeed we do it exactly +this way in the parser. But as stated above, this will not work for +virtual functions due to invalid covariant return types! + +### Return managed objects from virtual functions + +The easy fix would be to just create a new copy on the heap and return +that. But this seems like a very inelegant solution to this problem. I +mean why can't we just tell the object to treat it like a newly allocated +object? And indeed we can. I've added a `detach` method that will tell +the object to survive deallocation until the next pickup. This means +that it will leak if it is not picked up by consumer. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return nr.detach(); +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + + +## Compile time debug options + +To enable memory debugging you need to define `DEBUG_SHARED_PTR`. +This can i.e. be done in `include/sass/base.h` + +```c++ +define DEBUG_SHARED_PTR +``` + +This will print lost memory on exit to stderr. You can also use +`setDbg(true)` on sepecific variables to emit reference counter +increase, decrease and other events. + + +## Why reinvent the wheel when there is `shared_ptr` from C++11 + +First, implementing a smart pointer class is not really that hard. It +was indeed also a learning experience for myself. But there are more +profound advantages: + +- Better GCC 4.4 compatibility (which most code still has OOTB) +- Not thread safe (give us some free performance on some compiler) +- Beeing able to track memory allocations for debugging purposes +- Adding additional features if needed (as seen in `detach`) +- Optional: optimized weak pointer implementation possible + +### Thread Safety + +As said above, this is not thread safe currently. But we don't need +this ATM anyway. And I guess we probably never will share AST Nodes +across different threads. \ No newline at end of file diff --git a/src/libsass/eval.cpp b/src/libsass/eval.cpp new file mode 100644 index 000000000..841f7277b --- /dev/null +++ b/src/libsass/eval.cpp @@ -0,0 +1,1663 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "file.hpp" +#include "eval.hpp" +#include "ast.hpp" +#include "bind.hpp" +#include "util.hpp" +#include "inspect.hpp" +#include "operators.hpp" +#include "environment.hpp" +#include "position.hpp" +#include "sass/values.h" +#include "to_value.hpp" +#include "to_c.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "lexer.hpp" +#include "prelexer.hpp" +#include "parser.hpp" +#include "expand.hpp" +#include "color_maps.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + Eval::Eval(Expand& exp) + : exp(exp), + ctx(exp.ctx), + traces(exp.traces), + force(false), + is_in_comment(false), + is_in_selector_schema(false) + { + bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); + bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); + } + Eval::~Eval() { } + + Env* Eval::environment() + { + return exp.environment(); + } + + Selector_List_Obj Eval::selector() + { + return exp.selector(); + } + + Expression_Ptr Eval::operator()(Block_Ptr b) + { + Expression_Ptr val = 0; + for (size_t i = 0, L = b->length(); i < L; ++i) { + val = b->at(i)->perform(this); + if (val) return val; + } + return val; + } + + Expression_Ptr Eval::operator()(Assignment_Ptr a) + { + Env* env = exp.environment(); + std::string var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Ptr e = Cast(env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Ptr e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(this)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Ptr e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(this)); + } + else { + env->set_local(var, a->value()->perform(this)); + } + } + else { + env->set_lexical(var, a->value()->perform(this)); + } + return 0; + } + + Expression_Ptr Eval::operator()(If_Ptr i) + { + Expression_Obj rv = 0; + Env env(exp.environment()); + exp.env_stack.push_back(&env); + Expression_Obj cond = i->predicate()->perform(this); + if (!cond->is_false()) { + rv = i->block()->perform(this); + } + else { + Block_Obj alt = i->alternative(); + if (alt) rv = alt->perform(this); + } + exp.env_stack.pop_back(); + return rv.detach(); + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(this); + if (low->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(this); + if (high->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); + } + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_end->unit() << "' and '" + << sass_start->unit() << "'."; + error(msg.str(), low->pstate(), traces); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + exp.env_stack.push_back(&env); + Block_Obj body = f->block(); + Expression_Ptr val = 0; + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(this); + Env env(environment(), true); + exp.env_stack.push_back(&env); + List_Obj list = 0; + Map_Ptr map = 0; + if (expr->concrete_type() == Expression::MAP) { + map = Cast(expr); + } + else if (Selector_List_Ptr ls = Cast(expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = Cast(rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = Cast(expr); + } + + Block_Obj body = e->block(); + Expression_Obj val = 0; + + if (map) { + for (Expression_Obj key : map->keys()) { + Expression_Obj value = map->at(key); + + if (variables.size() == 1) { + List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(key); + variable->append(value); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], key); + env.set_local(variables[1], value); + } + + val = body->perform(this); + if (val) break; + } + } + else { + if (list->length() == 1 && Cast(list)) { + list = Cast(list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Ptr item = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Ptr arg = Cast(item)) item = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Ptr scalars = Cast(item)) { + if (variables.size() == 1) { + Expression_Ptr var = scalars; + env.set_local(variables[0], var); + } else { + // XXX: this is never hit via spec tests + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Ptr res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : scalars->at(j); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + // XXX: this is never hit via spec tests + Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val.detach(); + } + + Expression_Ptr Eval::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Obj body = w->block(); + Env env(environment(), true); + exp.env_stack.push_back(&env); + Expression_Obj cond = pred->perform(this); + while (!cond->is_false()) { + Expression_Obj val = body->perform(this); + if (val) { + exp.env_stack.pop_back(); + return val.detach(); + } + cond = pred->perform(this); + } + exp.env_stack.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Return_Ptr r) + { + return r->value()->perform(this); + } + + Expression_Ptr Eval::operator()(Warning_Ptr w) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = w->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@warn[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@warn", + w->pstate().path, + w->pstate().line + 1, + w->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@warn[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + std::cerr << "WARNING: " << result << std::endl; + traces.push_back(Backtrace(w->pstate())); + std::cerr << traces_to_string(traces, " "); + std::cerr << std::endl; + ctx.c_options.output_style = outstyle; + traces.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Error_Ptr e) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = e->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@error[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@error", + e->pstate().path, + e->pstate().line + 1, + e->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@error[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + ctx.c_options.output_style = outstyle; + error(result, e->pstate(), traces); + return 0; + } + + Expression_Ptr Eval::operator()(Debug_Ptr d) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = d->value()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@debug[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@debug", + d->pstate().path, + d->pstate().line + 1, + d->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@debug[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string cwd(ctx.cwd()); + std::string result(unquote(message->to_sass())); + std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); + ctx.c_options.output_style = outstyle; + + std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; + std::cerr << std::endl; + return 0; + } + + Expression_Ptr Eval::operator()(List_Ptr l) + { + // special case for unevaluated map + if (l->separator() == SASS_HASH) { + Map_Obj lm = SASS_MEMORY_NEW(Map, + l->pstate(), + l->length() / 2); + for (size_t i = 0, L = l->length(); i < L; i += 2) + { + Expression_Obj key = (*l)[i+0]->perform(this); + Expression_Obj val = (*l)[i+1]->perform(this); + // make sure the color key never displays its real name + key->is_delayed(true); // verified + *lm << std::make_pair(key, val); + } + if (lm->has_duplicate_key()) { + traces.push_back(Backtrace(l->pstate())); + throw Exception::DuplicateKeyError(traces, *lm, *l); + } + + lm->is_interpolant(l->is_interpolant()); + return lm->perform(this); + } + // check if we should expand it + if (l->is_expanded()) return l; + // regular case for unevaluated lists + List_Obj ll = SASS_MEMORY_NEW(List, + l->pstate(), + l->length(), + l->separator(), + l->is_arglist(), + l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + ll->append((*l)[i]->perform(this)); + } + ll->is_interpolant(l->is_interpolant()); + ll->from_selector(l->from_selector()); + ll->is_expanded(true); + return ll.detach(); + } + + Expression_Ptr Eval::operator()(Map_Ptr m) + { + if (m->is_expanded()) return m; + + // make sure we're not starting with duplicate keys. + // the duplicate key state will have been set in the parser phase. + if (m->has_duplicate_key()) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *m, *m); + } + + Map_Obj mm = SASS_MEMORY_NEW(Map, + m->pstate(), + m->length()); + for (auto key : m->keys()) { + Expression_Ptr ex_key = key->perform(this); + Expression_Ptr ex_val = m->at(key); + if (ex_val == NULL) continue; + ex_val = ex_val->perform(this); + *mm << std::make_pair(ex_key, ex_val); + } + + // check the evaluated keys aren't duplicates. + if (mm->has_duplicate_key()) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *mm, *m); + } + + mm->is_expanded(true); + return mm.detach(); + } + + Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) + { + + Expression_Obj lhs = b_in->left(); + Expression_Obj rhs = b_in->right(); + enum Sass_OP op_type = b_in->optype(); + + if (op_type == Sass_OP::AND) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (!*lhs) return lhs.detach(); + return rhs->perform(this); + } + else if (op_type == Sass_OP::OR) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (*lhs) return lhs.detach(); + return rhs->perform(this); + } + + // Evaluate variables as early o + while (Variable_Ptr l_v = Cast(lhs)) { + lhs = operator()(l_v); + } + while (Variable_Ptr r_v = Cast(rhs)) { + rhs = operator()(r_v); + } + + Binary_Expression_Obj b = b_in; + + // Evaluate sub-expressions early on + while (Binary_Expression_Ptr l_b = Cast(lhs)) { + if (!force && l_b->is_delayed()) break; + lhs = operator()(l_b); + } + while (Binary_Expression_Ptr r_b = Cast(rhs)) { + if (!force && r_b->is_delayed()) break; + rhs = operator()(r_b); + } + + // don't eval delayed expressions (the '/' when used as a separator) + if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { + b->right(b->right()->perform(this)); + b->left(b->left()->perform(this)); + return b.detach(); + } + + // specific types we know are final + // handle them early to avoid overhead + if (Number_Ptr l_n = Cast(lhs)) { + // lhs is number and rhs is number + if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; + case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; + case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + // lhs is number and rhs is color + else if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + } + else if (Color_Ptr l_c = Cast(lhs)) { + // lhs is color and rhs is color + if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; + case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; + case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + // lhs is color and rhs is number + else if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + } + + String_Schema_Obj ret_schema; + + // only the last item will be used to eval the binary expression + if (String_Schema_Ptr s_l = Cast(b->left())) { + if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), s_l->last(), b->right()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified + for (size_t i = 0; i < s_l->length() - 1; ++i) { + ret_schema->append(s_l->at(i)->perform(this)); + } + ret_schema->append(bin_ex->perform(this)); + return ret_schema->perform(this); + } + } + if (String_Schema_Ptr s_r = Cast(b->right())) { + + if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), b->left(), s_r->first()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified + ret_schema->append(bin_ex->perform(this)); + for (size_t i = 1; i < s_r->length(); ++i) { + ret_schema->append(s_r->at(i)->perform(this)); + } + return ret_schema->perform(this); + } + } + + // fully evaluate their values + if (op_type == Sass_OP::EQ || + op_type == Sass_OP::NEQ || + op_type == Sass_OP::GT || + op_type == Sass_OP::GTE || + op_type == Sass_OP::LT || + op_type == Sass_OP::LTE) + { + LOCAL_FLAG(force, true); + lhs->is_expanded(false); + lhs->set_delayed(false); + lhs = lhs->perform(this); + rhs->is_expanded(false); + rhs->set_delayed(false); + rhs = rhs->perform(this); + } + else { + lhs = lhs->perform(this); + } + + // not a logical connective, so go ahead and eval the rhs + rhs = rhs->perform(this); + AST_Node_Obj lu = lhs; + AST_Node_Obj ru = rhs; + + Expression::Concrete_Type l_type; + Expression::Concrete_Type r_type; + + // Is one of the operands an interpolant? + String_Schema_Obj s1 = Cast(b->left()); + String_Schema_Obj s2 = Cast(b->right()); + Binary_Expression_Obj b1 = Cast(b->left()); + Binary_Expression_Obj b2 = Cast(b->right()); + + bool schema_op = false; + + bool force_delay = (s2 && s2->is_left_interpolant()) || + (s1 && s1->is_right_interpolant()) || + (b1 && b1->is_right_interpolant()) || + (b2 && b2->is_left_interpolant()); + + if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) + { + if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || + op_type == Sass_OP::EQ) { + // If possible upgrade LHS to a number (for number to string compare) + if (String_Constant_Ptr str = Cast(lhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { + lhs = Parser::lexed_dimension(b->pstate(), str->value()); + } + } + // If possible upgrade RHS to a number (for string to number compare) + if (String_Constant_Ptr str = Cast(rhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { + rhs = Parser::lexed_dimension(b->pstate(), str->value()); + } + } + } + + To_Value to_value(ctx); + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); + + if (force_delay) { + std::string str(""); + str += v_l->to_string(ctx.c_options); + if (b->op().ws_before) str += " "; + str += b->separator(); + if (b->op().ws_after) str += " "; + str += v_r->to_string(ctx.c_options); + String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); + val->is_interpolant(b->left()->has_interpolant()); + return val; + } + } + + // see if it's a relational expression + try { + switch(op_type) { + case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); + case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); + case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); + default: break; + } + } + catch (Exception::OperationError& err) + { + // throw Exception::Base(b->pstate(), err.what()); + traces.push_back(Backtrace(b->pstate())); + throw Exception::SassValueError(traces, b->pstate(), err); + } + + l_type = lhs->concrete_type(); + r_type = rhs->concrete_type(); + + // ToDo: throw error in op functions + // ToDo: then catch and re-throw them + Expression_Obj rv; + try { + ParserState pstate(b->pstate()); + if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { + Number_Ptr l_n = Cast(lhs); + Number_Ptr r_n = Cast(rhs); + l_n->reduce(); r_n->reduce(); + rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); + } + else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { + Number_Ptr l_n = Cast(lhs); + Color_Ptr r_c = Cast(rhs); + rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { + Color_Ptr l_c = Cast(lhs); + Number_Ptr r_n = Cast(rhs); + rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { + Color_Ptr l_c = Cast(lhs); + Color_Ptr r_c = Cast(rhs); + rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); + } + else { + To_Value to_value(ctx); + // this will leak if perform does not return a value! + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); + bool interpolant = b->is_right_interpolant() || + b->is_left_interpolant() || + b->is_interpolant(); + if (op_type == Sass_OP::SUB) interpolant = false; + // if (op_type == Sass_OP::DIV) interpolant = true; + // check for type violations + if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { + traces.push_back(Backtrace(v_l->pstate())); + throw Exception::InvalidValue(traces, *v_l); + } + if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { + traces.push_back(Backtrace(v_r->pstate())); + throw Exception::InvalidValue(traces, *v_r); + } + Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress + if (String_Constant_Ptr str = Cast(ex)) + { + if (str->concrete_type() == Expression::STRING) + { + String_Constant_Ptr lstr = Cast(lhs); + String_Constant_Ptr rstr = Cast(rhs); + if (op_type != Sass_OP::SUB) { + if (String_Constant_Ptr org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } + } + } + ex->is_interpolant(b->is_interpolant()); + rv = ex; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b->pstate())); + // throw Exception::Base(b->pstate(), err.what()); + throw Exception::SassValueError(traces, b->pstate(), err); + } + + if (rv) { + if (schema_op) { + // XXX: this is never hit via spec tests + (*s2)[0] = rv; + rv = s2->perform(this); + } + } + + return rv.detach(); + + } + + Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) + { + Expression_Obj operand = u->operand()->perform(this); + if (u->optype() == Unary_Expression::NOT) { + Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); + result->value(!result->value()); + return result; + } + else if (Number_Obj nr = Cast(operand)) { + // negate value for minus unary expression + if (u->optype() == Unary_Expression::MINUS) { + Number_Obj cpy = SASS_MEMORY_COPY(nr); + cpy->value( - cpy->value() ); // negate value + return cpy.detach(); // return the copy + } + else if (u->optype() == Unary_Expression::SLASH) { + std::string str = '/' + nr->to_string(ctx.c_options); + return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); + } + // nothing for positive + return nr.detach(); + } + else { + // Special cases: +/- variables which evaluate to null ouput just +/-, + // but +/- null itself outputs the string + if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { + u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); + } + // Never apply unary opertions on colors @see #2140 + else if (Color_Ptr color = Cast(operand)) { + // Use the color name if this was eval with one + if (color->disp().length() > 0) { + operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); + u->operand(operand); + } + } + else { + u->operand(operand); + } + + return SASS_MEMORY_NEW(String_Quoted, + u->pstate(), + u->inspect()); + } + // unreachable + return u; + } + + Expression_Ptr Eval::operator()(Function_Call_Ptr c) + { + if (traces.size() > Constants::MaxCallStack) { + // XXX: this is never hit via spec tests + std::ostringstream stm; + stm << "Stack depth exceeded max of " << Constants::MaxCallStack; + error(stm.str(), c->pstate(), traces); + } + std::string name(Util::normalize_underscores(c->name())); + std::string full_name(name + "[f]"); + // we make a clone here, need to implement that further + Arguments_Obj args = c->arguments(); + + Env* env = environment(); + if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { + if (!env->has("*[f]")) { + for (Argument_Obj arg : args->elements()) { + if (List_Obj ls = Cast(arg->value())) { + if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); + } + } + args = Cast(args->perform(this)); + Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, + c->pstate(), + c->name(), + args); + if (args->has_named_arguments()) { + error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); + } + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, + c->pstate(), + lit->to_string(ctx.c_options)); + str->is_interpolant(c->is_interpolant()); + return str; + } else { + // call generic function + full_name = "*[f]"; + } + } + + // further delay for calls + if (full_name != "call[f]") { + args->set_delayed(false); // verified + } + if (full_name != "if[f]") { + args = Cast(args->perform(this)); + } + Definition_Ptr def = Cast((*env)[full_name]); + + if (c->func()) def = c->func()->definition(); + + if (def->is_overload_stub()) { + std::stringstream ss; + size_t L = args->length(); + // account for rest arguments + if (args->has_rest_argument() && args->length() > 0) { + // get the rest arguments list + List_Ptr rest = Cast(args->last()->value()); + // arguments before rest argument plus rest + if (rest) L += rest->length() - 1; + } + ss << full_name << L; + full_name = ss.str(); + std::string resolved_name(full_name); + if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); + def = Cast((*env)[resolved_name]); + } + + Expression_Obj result = c; + Block_Obj body = def->block(); + Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + + if (c->is_css()) return result.detach(); + + Parameters_Obj params = def->parameters(); + Env fn_env(def->environment()); + exp.env_stack.push_back(&fn_env); + + if (func || body) { + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + // eval the body if user-defined or special, invoke underlying CPP function if native + if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { + result = body->perform(this); + } + else if (func) { + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); + } + if (!result) { + error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); + } + ctx.callee_stack.pop_back(); + traces.pop_back(); + } + + // else if it's a user-defined c function + // convert call into C-API compatible form + else if (c_function) { + Sass_Function_Fn c_func = sass_function_get_function(c_function); + if (full_name == "*[f]") { + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); + Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); + new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); + new_args->concat(args); + args = new_args; + } + + // populates env with default values for params + std::string ff(c->name()); + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_C_FUNCTION, + { env } + }); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); + for(size_t i = 0; i < params->length(); i++) { + Parameter_Obj param = params->at(i); + std::string key = param->name(); + AST_Node_Obj node = fn_env.get_local(key); + Expression_Obj arg = Cast(node); + sass_list_set_value(c_args, i, arg->perform(&to_c)); + } + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + if (sass_value_get_tag(c_val) == SASS_ERROR) { + error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); + } else if (sass_value_get_tag(c_val) == SASS_WARNING) { + error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); + } + result = cval_to_astnode(c_val, traces, c->pstate()); + + ctx.callee_stack.pop_back(); + traces.pop_back(); + sass_delete_value(c_args); + if (c_val != c_args) + sass_delete_value(c_val); + } + + // link back to function definition + // only do this for custom functions + if (result->pstate().file == std::string::npos) + result->pstate(c->pstate()); + + result = result->perform(this); + result->is_interpolant(c->is_interpolant()); + exp.env_stack.pop_back(); + return result.detach(); + } + + Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) + { + Expression_Ptr evaluated_name = s->name()->perform(this); + Expression_Ptr evaluated_args = s->arguments()->perform(this); + String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); + ss->append(evaluated_name); + ss->append(evaluated_args); + return ss->perform(this); + } + + Expression_Ptr Eval::operator()(Variable_Ptr v) + { + Expression_Obj value = 0; + Env* env = environment(); + const std::string& name(v->name()); + EnvResult rv(env->find(name)); + if (rv.found) value = static_cast(rv.it->second.ptr()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); + if (Argument_Ptr arg = Cast(value)) value = arg->value(); + if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag + value->is_interpolant(v->is_interpolant()); + if (force) value->is_expanded(false); + value->set_delayed(false); // verified + value = value->perform(this); + if(!force) rv.it->second = value; + return value.detach(); + } + + Expression_Ptr Eval::operator()(Color_Ptr c) + { + return c; + } + + Expression_Ptr Eval::operator()(Number_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Boolean_Ptr b) + { + return b; + } + + void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { + + bool needs_closing_brace = false; + + if (Arguments_Ptr args = Cast(ex)) { + List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); + for(auto arg : args->elements()) { + ll->append(arg->value()); + } + ll->is_interpolant(args->is_interpolant()); + needs_closing_brace = true; + res += "("; + ex = ll; + } + if (Number_Ptr nr = Cast(ex)) { + Number reduced(nr); + reduced.reduce(); + if (!reduced.is_valid_css_unit()) { + traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue(traces, *nr); + } + } + if (Argument_Ptr arg = Cast(ex)) { + ex = arg->value(); + } + if (String_Quoted_Ptr sq = Cast(ex)) { + if (was_itpl) { + bool was_interpolant = ex->is_interpolant(); + ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); + ex->is_interpolant(was_interpolant); + } + } + + if (Cast(ex)) { return; } + + // parent selector needs another go + if (Cast(ex)) { + // XXX: this is never hit via spec tests + ex = ex->perform(this); + } + + if (List_Ptr l = Cast(ex)) { + List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); + // this fixes an issue with bourbon sample, not really sure why + // if (l->size() && Cast((*l)[0])) { res += ""; } + for(Expression_Obj item : *l) { + item->is_interpolant(l->is_interpolant()); + std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); + bool is_null = Cast(item) != 0; // rl != "" + if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); + } + // Check indicates that we probably should not get a list + // here. Normally single list items are already unwrapped. + if (l->size() > 1) { + // string_to_output would fail "#{'_\a' '_\a'}"; + std::string str(ll->to_string(ctx.c_options)); + str = read_hex_escapes(str); // read escapes + newline_to_space(str); // replace directly + res += str; // append to result string + } else { + res += (ll->to_string(ctx.c_options)); + } + ll->is_interpolant(l->is_interpolant()); + } + + // Value + // Function_Call + // Selector_List + // String_Quoted + // String_Constant + // Parent_Selector + // Binary_Expression + else { + // ex = ex->perform(this); + if (into_quotes && ex->is_interpolant()) { + res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); + } else { + std::string str(ex ? ex->to_string(ctx.c_options) : ""); + if (into_quotes) str = read_hex_escapes(str); + res += str; // append to result string + } + } + + if (needs_closing_brace) res += ")"; + + } + + Expression_Ptr Eval::operator()(String_Schema_Ptr s) + { + size_t L = s->length(); + bool into_quotes = false; + if (L > 1) { + if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { + if (String_Constant_Ptr l = Cast((*s)[0])) { + if (String_Constant_Ptr r = Cast((*s)[L - 1])) { + if (r->value().size() > 0) { + if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; + if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; + } + } + } + } + } + bool was_quoted = false; + bool was_interpolant = false; + std::string res(""); + for (size_t i = 0; i < L; ++i) { + bool is_quoted = Cast((*s)[i]) != NULL; + if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + Expression_Obj ex = (*s)[i]->perform(this); + interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); + was_quoted = Cast((*s)[i]) != NULL; + was_interpolant = (*s)[i]->is_interpolant(); + + } + if (!s->is_interpolant()) { + if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); + return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); + } + // string schema seems to have a special unquoting behavior (also handles "nested" quotes) + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); + // if (s->is_interpolant()) str->quote_mark(0); + // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + if (str->quote_mark()) str->quote_mark('*'); + else if (!is_in_comment) str->value(string_to_output(str->value())); + str->is_interpolant(s->is_interpolant()); + return str.detach(); + } + + + Expression_Ptr Eval::operator()(String_Constant_Ptr s) + { + return s; + } + + Expression_Ptr Eval::operator()(String_Quoted_Ptr s) + { + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); + str->value(s->value()); + str->quote_mark(s->quote_mark()); + str->is_interpolant(s->is_interpolant()); + return str; + } + + Expression_Ptr Eval::operator()(Supports_Operator_Ptr c) + { + Expression_Ptr left = c->left()->perform(this); + Expression_Ptr right = c->right()->perform(this); + Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, + c->pstate(), + Cast(left), + Cast(right), + c->operand()); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) + { + Expression_Ptr condition = c->condition()->perform(this); + Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, + c->pstate(), + Cast(condition)); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) + { + Expression_Ptr feature = c->feature()->perform(this); + Expression_Ptr value = c->value()->perform(this); + Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, + c->pstate(), + feature, + value); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) + { + Expression_Ptr value = c->value()->perform(this); + Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, + c->pstate(), + value); + return cc; + } + + Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, + e->pstate(), + Cast(feature), + value); + return ee; + } + + Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) + { + String_Obj t = q->media_type(); + t = static_cast(t.isNull() ? 0 : t->perform(this)); + Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, + q->pstate(), + t, + q->length(), + q->is_negated(), + q->is_restricted()); + for (size_t i = 0, L = q->length(); i < L; ++i) { + qq->append(static_cast((*q)[i]->perform(this))); + } + return qq.detach(); + } + + Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + if (feature && Cast(feature)) { + feature = SASS_MEMORY_NEW(String_Quoted, + feature->pstate(), + Cast(feature)->value()); + } + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + if (value && Cast(value)) { + // XXX: this is never hit via spec tests + value = SASS_MEMORY_NEW(String_Quoted, + value->pstate(), + Cast(value)->value()); + } + return SASS_MEMORY_NEW(Media_Query_Expression, + e->pstate(), + feature, + value, + e->is_interpolated()); + } + + Expression_Ptr Eval::operator()(Null_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Argument_Ptr a) + { + Expression_Obj val = a->value()->perform(this); + bool is_rest_argument = a->is_rest_argument(); + bool is_keyword_argument = a->is_keyword_argument(); + + if (a->is_rest_argument()) { + if (val->concrete_type() == Expression::MAP) { + is_rest_argument = false; + is_keyword_argument = true; + } + else if(val->concrete_type() != Expression::LIST) { + List_Obj wrapper = SASS_MEMORY_NEW(List, + val->pstate(), + 0, + SASS_COMMA, + true); + wrapper->append(val); + val = wrapper; + } + } + return SASS_MEMORY_NEW(Argument, + a->pstate(), + val, + a->name(), + is_rest_argument, + is_keyword_argument); + } + + Expression_Ptr Eval::operator()(Arguments_Ptr a) + { + Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); + if (a->length() == 0) return aa.detach(); + for (size_t i = 0, L = a->length(); i < L; ++i) { + Expression_Obj rv = (*a)[i]->perform(this); + Argument_Ptr arg = Cast(rv); + if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { + aa->append(arg); + } + } + + if (a->has_rest_argument()) { + Expression_Obj rest = a->get_rest_argument()->perform(this); + Expression_Obj splat = Cast(rest)->value()->perform(this); + + Sass_Separator separator = SASS_COMMA; + List_Ptr ls = Cast(splat); + Map_Ptr ms = Cast(splat); + + List_Obj arglist = SASS_MEMORY_NEW(List, + splat->pstate(), + 0, + ls ? ls->separator() : separator, + true); + + if (ls && ls->is_arglist()) { + arglist->concat(ls); + } else if (ms) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); + } else if (ls) { + arglist->concat(ls); + } else { + arglist->append(splat); + } + if (arglist->length()) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); + } + } + + if (a->has_keyword_argument()) { + Expression_Obj rv = a->get_keyword_argument()->perform(this); + Argument_Ptr rvarg = Cast(rv); + Expression_Obj kwarg = rvarg->value()->perform(this); + + aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); + } + return aa.detach(); + } + + Expression_Ptr Eval::operator()(Comment_Ptr c) + { + return 0; + } + + inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + // All the binary helpers. + + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) + { + using std::strlen; + using std::strcpy; + Expression_Ptr e = NULL; + switch (sass_value_get_tag(v)) { + case SASS_BOOLEAN: { + e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); + } break; + case SASS_NUMBER: { + e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); + } break; + case SASS_COLOR: { + e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); + } break; + case SASS_STRING: { + if (sass_string_is_quoted(v)) + e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); + else { + e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); + } + } break; + case SASS_LIST: { + List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); + for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { + l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); + } + l->is_bracketed(sass_list_get_is_bracketed(v)); + e = l; + } break; + case SASS_MAP: { + Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); + for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { + *m << std::make_pair( + cval_to_astnode(sass_map_get_key(v, i), traces, pstate), + cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); + } + e = m; + } break; + case SASS_NULL: { + e = SASS_MEMORY_NEW(Null, pstate); + } break; + case SASS_ERROR: { + error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); + } break; + case SASS_WARNING: { + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); + } break; + default: break; + } + return e; + } + + Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) + { + std::vector rv; + Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); + sl->is_optional(s->is_optional()); + sl->media_block(s->media_block()); + sl->is_optional(s->is_optional()); + for (size_t i = 0, iL = s->length(); i < iL; ++i) { + rv.push_back(operator()((*s)[i])); + } + + // we should actually permutate parent first + // but here we have permutated the selector first + size_t round = 0; + while (round != std::string::npos) { + bool abort = true; + for (size_t i = 0, iL = rv.size(); i < iL; ++i) { + if (rv[i]->length() > round) { + sl->append((*rv[i])[round]); + abort = false; + } + } + if (abort) { + round = std::string::npos; + } else { + ++ round; + } + + } + return sl.detach(); + } + + + Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) + { + bool implicit_parent = !exp.old_at_root_without_rule; + if (is_in_selector_schema) exp.selector_stack.push_back(0); + Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); + if (is_in_selector_schema) exp.selector_stack.pop_back(); + for (size_t i = 0; i < resolved->length(); i++) { + Complex_Selector_Ptr is = resolved->at(i)->first(); + while (is) { + if (is->head()) { + is->head(operator()(is->head())); + } + is = is->tail(); + } + } + return resolved.detach(); + } + + Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0; i < s->length(); i++) { + Simple_Selector_Ptr ss = s->at(i); + // skip parents here (called via resolve_parent_refs) + if (ss == NULL || Cast(ss)) continue; + s->at(i) = Cast(ss->perform(this)); + } + return s; + } + + Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) + { + LOCAL_FLAG(is_in_selector_schema, true); + // the parser will look for a brace to end the selector + ctx.c_options.in_selector = true; // do not compress colors + Expression_Obj sel = s->contents()->perform(this); + std::string result_str(sel->to_string(ctx.c_options)); + ctx.c_options.in_selector = false; // flag temporary only + result_str = unquote(Util::rtrim(result_str)); + char* temp_cstr = sass_copy_c_string(result_str.c_str()); + ctx.strings.push_back(temp_cstr); // attach to context + Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); + p.last_media_block = s->media_block(); + // a selector schema may or may not connect to parent? + bool chroot = s->connect_parent() == false; + Selector_List_Obj sl = p.parse_selector_list(chroot); + auto vec_str_rend = ctx.strings.rend(); + auto vec_str_rbegin = ctx.strings.rbegin(); + // remove the first item searching from the back + // we cannot assume our item is still the last one + // order is not important, so we can optimize this + auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); + // undefined behavior if not found! + if (it != vec_str_rend) { + // overwrite with last item + *it = ctx.strings.back(); + // remove last one from vector + ctx.strings.pop_back(); + // free temporary copy + free(temp_cstr); + } + flag_is_in_selector_schema.reset(); + return operator()(sl); + } + + Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) + { + if (Selector_List_Obj pr = selector()) { + exp.selector_stack.pop_back(); + Selector_List_Obj rv = operator()(pr); + exp.selector_stack.push_back(rv); + return rv.detach(); + } else { + return SASS_MEMORY_NEW(Null, p->pstate()); + } + } + + Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) + { + return s; + } + + // hotfix to avoid invalid nested `:not` selectors + // probably the wrong place, but this should ultimately + // be fixed by implement superselector correctly for `:not` + // first use of "find" (ATM only implemented for selectors) + bool hasNotSelector(AST_Node_Obj obj) { + if (Wrapped_Selector_Ptr w = Cast(obj)) { + return w->name() == ":not"; + } + return false; + } + + Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) + { + + if (s->name() == ":not") { + if (exp.selector_stack.back()) { + if (s->selector()->find(hasNotSelector)) { + s->selector()->clear(); + s->name(" "); + } else if (s->selector()->length() == 1) { + Complex_Selector_Ptr cs = s->selector()->at(0); + if (cs->tail()) { + s->selector()->clear(); + s->name(" "); + } + } else if (s->selector()->length() > 1) { + s->selector()->clear(); + s->name(" "); + } + } + } + return s; + }; + +} diff --git a/src/libsass/expand.cpp b/src/libsass/expand.cpp new file mode 100644 index 000000000..d8dc03f14 --- /dev/null +++ b/src/libsass/expand.cpp @@ -0,0 +1,817 @@ +#include "sass.hpp" +#include +#include + +#include "ast.hpp" +#include "expand.hpp" +#include "bind.hpp" +#include "eval.hpp" +#include "backtrace.hpp" +#include "context.hpp" +#include "parser.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // simple endless recursion protection + const size_t maxRecursion = 500; + + Expand::Expand(Context& ctx, Env* env, std::vector* stack) + : ctx(ctx), + traces(ctx.traces), + eval(Eval(*this)), + recursions(0), + in_keyframes(false), + at_root_without_rule(false), + old_at_root_without_rule(false), + env_stack(std::vector()), + block_stack(std::vector()), + call_stack(std::vector()), + selector_stack(std::vector()), + media_block_stack(std::vector()) + { + env_stack.push_back(0); + env_stack.push_back(env); + block_stack.push_back(0); + call_stack.push_back(0); + if (stack == NULL) { selector_stack.push_back(0); } + else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } + media_block_stack.push_back(0); + } + + Env* Expand::environment() + { + if (env_stack.size() > 0) + return env_stack.back(); + return 0; + } + + Selector_List_Obj Expand::selector() + { + if (selector_stack.size() > 0) + return selector_stack.back(); + return 0; + } + + // blocks create new variable scopes + Block_Ptr Expand::operator()(Block_Ptr b) + { + // create new local environment + // set the current env as parent + Env env(environment()); + // copy the block object (add items later) + Block_Obj bb = SASS_MEMORY_NEW(Block, + b->pstate(), + b->length(), + b->is_root()); + // setup block and env stack + this->block_stack.push_back(bb); + this->env_stack.push_back(&env); + // operate on block + // this may throw up! + this->append_block(b); + // revert block and env stack + this->block_stack.pop_back(); + this->env_stack.pop_back(); + // return copy + return bb.detach(); + } + + Statement_Ptr Expand::operator()(Ruleset_Ptr r) + { + LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); + + if (in_keyframes) { + Block_Ptr bb = operator()(r->block()); + Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); + if (r->selector()) { + if (Selector_List_Ptr s = r->selector()) { + selector_stack.push_back(0); + k->name(s->eval(eval)); + selector_stack.pop_back(); + } + } + return k.detach(); + } + + // reset when leaving scope + LOCAL_FLAG(at_root_without_rule, false); + + // `&` is allowed in `@at-root`! + bool has_parent_selector = false; + for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { + Selector_List_Obj ll = selector_stack.at(i); + has_parent_selector = ll != 0 && ll->length() > 0; + } + + Selector_List_Obj sel = r->selector(); + if (sel) sel = sel->eval(eval); + + // check for parent selectors in base level rules + if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { + if (Selector_List_Ptr selector_list = Cast(r->selector())) { + for (Complex_Selector_Obj complex_selector : selector_list->elements()) { + Complex_Selector_Ptr tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + Parent_Selector_Ptr ptr = Cast(header); + if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); + } + tail = tail->tail(); + } + } + } + } + else { + if (sel->length() == 0 || sel->has_parent_ref()) { + if (sel->has_real_parent_ref() && !has_parent_selector) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); + } + } + } + + // do not connect parent again + sel->remove_parent_selectors(); + selector_stack.push_back(sel); + Env env(environment()); + if (block_stack.back()->is_root()) { + env_stack.push_back(&env); + } + sel->set_media_block(media_block_stack.back()); + Block_Obj blk = 0; + if (r->block()) blk = operator()(r->block()); + Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + sel, + blk); + selector_stack.pop_back(); + if (block_stack.back()->is_root()) { + env_stack.pop_back(); + } + + rr->is_root(r->is_root()); + rr->tabs(r->tabs()); + + return rr; + } + + Statement_Ptr Expand::operator()(Supports_Block_Ptr f) + { + Expression_Obj condition = f->condition()->perform(&eval); + Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, + f->pstate(), + Cast(condition), + operator()(f->block())); + return ff.detach(); + } + + Statement_Ptr Expand::operator()(Media_Block_Ptr m) + { + Media_Block_Obj cpy = SASS_MEMORY_COPY(m); + // Media_Blocks are prone to have circular references + // Copy could leak memory if it does not get picked up + // Looks like we are able to reset block reference for copy + // Good as it will ensure a low memory overhead for this fix + // So this is a cheap solution with a minimal price + ctx.ast_gc.push_back(cpy); cpy->block(0); + Expression_Obj mq = eval(m->media_queries()); + std::string str_mq(mq->to_string(ctx.c_options)); + char* str = sass_copy_c_string(str_mq.c_str()); + ctx.strings.push_back(str); + Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); + mq = p.parse_media_queries(); // re-assign now + cpy->media_queries(mq); + media_block_stack.push_back(cpy); + Block_Obj blk = operator()(m->block()); + Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + mq, + blk); + media_block_stack.pop_back(); + mm->tabs(m->tabs()); + return mm; + } + + Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) + { + Block_Obj ab = a->block(); + Expression_Obj ae = a->expression(); + + if (ae) ae = ae->perform(&eval); + else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); + + LOCAL_FLAG(at_root_without_rule, true); + LOCAL_FLAG(in_keyframes, false); + + ; + + Block_Obj bb = ab ? operator()(ab) : NULL; + At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, + a->pstate(), + bb, + Cast(ae)); + return aa.detach(); + } + + Statement_Ptr Expand::operator()(Directive_Ptr a) + { + LOCAL_FLAG(in_keyframes, a->is_keyframes()); + Block_Ptr ab = a->block(); + Selector_List_Ptr as = a->selector(); + Expression_Ptr av = a->value(); + selector_stack.push_back(0); + if (av) av = av->perform(&eval); + if (as) as = eval(as); + selector_stack.pop_back(); + Block_Ptr bb = ab ? operator()(ab) : NULL; + Directive_Ptr aa = SASS_MEMORY_NEW(Directive, + a->pstate(), + a->keyword(), + as, + bb, + av); + return aa; + } + + Statement_Ptr Expand::operator()(Declaration_Ptr d) + { + Block_Obj ab = d->block(); + String_Obj old_p = d->property(); + Expression_Obj prop = old_p->perform(&eval); + String_Obj new_p = Cast(prop); + // we might get a color back + if (!new_p) { + std::string str(prop->to_string(ctx.c_options)); + new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); + } + Expression_Obj value = d->value(); + if (value) value = value->perform(&eval); + Block_Obj bb = ab ? operator()(ab) : NULL; + if (!bb) { + if (!value || (value->is_invisible() && !d->is_important())) return 0; + } + Declaration_Ptr decl = SASS_MEMORY_NEW(Declaration, + d->pstate(), + new_p, + value, + d->is_important(), + d->is_custom_property(), + bb); + decl->tabs(d->tabs()); + return decl; + } + + Statement_Ptr Expand::operator()(Assignment_Ptr a) + { + Env* env = environment(); + const std::string& var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Obj e = Cast(env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Obj e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(&eval)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Obj e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(&eval)); + } + else { + env->set_local(var, a->value()->perform(&eval)); + } + } + else { + env->set_lexical(var, a->value()->perform(&eval)); + } + return 0; + } + + Statement_Ptr Expand::operator()(Import_Ptr imp) + { + Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); + if (imp->import_queries() && imp->import_queries()->size()) { + Expression_Obj ex = imp->import_queries()->perform(&eval); + result->import_queries(Cast(ex)); + } + for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { + result->urls().push_back(imp->urls()[i]->perform(&eval)); + } + // all resources have been dropped for Input_Stubs + // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} + return result.detach(); + } + + Statement_Ptr Expand::operator()(Import_Stub_Ptr i) + { + traces.push_back(Backtrace(i->pstate())); + // get parent node from call stack + AST_Node_Obj parent = call_stack.back(); + if (Cast(parent) == NULL) { + error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); + } + // we don't seem to need that actually afterall + Sass_Import_Entry import = sass_make_import( + i->imp_path().c_str(), + i->abs_path().c_str(), + 0, 0 + ); + ctx.import_stack.push_back(import); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); + block_stack.back()->append(trace); + block_stack.push_back(trace_block); + + const std::string& abs_path(i->resource().abs_path); + append_block(ctx.sheets.at(abs_path).root); + sass_delete_import(ctx.import_stack.back()); + ctx.import_stack.pop_back(); + block_stack.pop_back(); + traces.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Warning_Ptr w) + { + // eval handles this too, because warnings may occur in functions + w->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Error_Ptr e) + { + // eval handles this too, because errors may occur in functions + e->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Debug_Ptr d) + { + // eval handles this too, because warnings may occur in functions + d->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Comment_Ptr c) + { + if (ctx.output_style() == COMPRESSED) { + // comments should not be evaluated in compact + // https://github.com/sass/libsass/issues/2359 + if (!c->is_important()) return NULL; + } + eval.is_in_comment = true; + Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); + eval.is_in_comment = false; + // TODO: eval the text, once we're parsing/storing it as a String_Schema + return rv; + } + + Statement_Ptr Expand::operator()(If_Ptr i) + { + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(i); + Expression_Obj rv = i->predicate()->perform(&eval); + if (*rv) { + append_block(i->block()); + } + else { + Block_Ptr alt = i->alternative(); + if (alt) append_block(alt); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(&eval); + if (low->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(&eval); + if (high->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); + } + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_start->unit() << "' and '" + << sass_end->unit() << "'."; + error(msg.str(), low->pstate(), traces); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(f); + Block_Ptr body = f->block(); + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + append_block(body); + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(&eval); + List_Obj list = 0; + Map_Obj map; + if (expr->concrete_type() == Expression::MAP) { + map = Cast(expr); + } + else if (Selector_List_Ptr ls = Cast(expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = Cast(rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = Cast(expr); + } + // remember variables and then reset them + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(e); + Block_Ptr body = e->block(); + + if (map) { + for (auto key : map->keys()) { + Expression_Obj k = key->perform(&eval); + Expression_Obj v = map->at(key)->perform(&eval); + + if (variables.size() == 1) { + List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(k); + variable->append(v); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], k); + env.set_local(variables[1], v); + } + append_block(body); + } + } + else { + // bool arglist = list->is_arglist(); + if (list->length() == 1 && Cast(list)) { + list = Cast(list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Obj item = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Obj arg = Cast(item)) item = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Obj scalars = Cast(item)) { + if (variables.size() == 1) { + List_Obj var = scalars; + // if (arglist) var = (*scalars)[0]; + env.set_local(variables[0], var); + } else { + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Obj res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : (*scalars)[j]->perform(&eval); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Ptr body = w->block(); + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(w); + Expression_Obj cond = pred->perform(&eval); + while (!cond->is_false()) { + append_block(body); + cond = pred->perform(&eval); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Return_Ptr r) + { + error("@return may only be used within a function", r->pstate(), traces); + return 0; + } + + + void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { + + if (Selector_List_Obj sl = Cast(s)) { + for (Complex_Selector_Obj complex_selector : sl->elements()) { + Complex_Selector_Obj tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + if (Cast(header) == NULL) continue; // skip all others + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); + } + tail = tail->tail(); + } + } + } + + + Selector_List_Obj contextualized = Cast(s->perform(&eval)); + if (contextualized == false) return; + for (auto complex_sel : contextualized->elements()) { + Complex_Selector_Obj c = complex_sel; + if (!c->head() || c->tail()) { + std::string sel_str(contextualized->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); + } + Compound_Selector_Obj target = c->head(); + if (contextualized->is_optional()) target->is_optional(true); + for (size_t i = 0, L = extender->length(); i < L; ++i) { + Complex_Selector_Obj sel = (*extender)[i]; + if (!(sel->head() && sel->head()->length() > 0 && + Cast((*sel->head())[0]))) + { + Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); + hh->media_block((*extender)[i]->media_block()); + Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); + ssel->media_block((*extender)[i]->media_block()); + if (sel->has_line_feed()) ssel->has_line_feed(true); + Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); + ps->media_block((*extender)[i]->media_block()); + hh->append(ps); + ssel->tail(sel); + ssel->head(hh); + sel = ssel; + } + // if (c->has_line_feed()) sel->has_line_feed(true); + ctx.subset_map.put(target, std::make_pair(sel, target)); + } + } + + } + + Statement* Expand::operator()(Extension_Ptr e) + { + if (Selector_List_Ptr extender = selector()) { + Selector_List_Ptr sl = e->selector(); + // abort on invalid selector + if (sl == NULL) return NULL; + if (Selector_Schema_Ptr schema = sl->schema()) { + if (schema->has_real_parent_ref()) { + // put root block on stack again (ignore parents) + // selector schema must not connect in eval! + block_stack.push_back(block_stack.at(1)); + sl = eval(sl->schema()); + block_stack.pop_back(); + } else { + selector_stack.push_back(0); + sl = eval(sl->schema()); + selector_stack.pop_back(); + } + } + for (Complex_Selector_Obj cs : sl->elements()) { + if (!cs.isNull() && !cs->head().isNull()) { + cs->head()->media_block(media_block_stack.back()); + } + } + selector_stack.push_back(0); + expand_selector_list(sl, extender); + selector_stack.pop_back(); + } + return 0; + } + + Statement_Ptr Expand::operator()(Definition_Ptr d) + { + Env* env = environment(); + Definition_Obj dd = SASS_MEMORY_COPY(d); + env->local_frame()[d->name() + + (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; + + if (d->type() == Definition::FUNCTION && ( + Prelexer::calc_fn_call(d->name().c_str()) || + d->name() == "element" || + d->name() == "expression" || + d->name() == "url" + )) { + deprecated( + "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", + "This name conflicts with an existing CSS function with special parse rules.", + false, d->pstate() + ); + } + + // set the static link so we can have lexical scoping + dd->environment(env); + return 0; + } + + Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) + { + if (recursions > maxRecursion) { + throw Exception::StackError(traces, *c); + } + + recursions ++; + + Env* env = environment(); + std::string full_name(c->name() + "[m]"); + if (!env->has(full_name)) { + error("no mixin named " + c->name(), c->pstate(), traces); + } + Definition_Obj def = Cast((*env)[full_name]); + Block_Obj body = def->block(); + Parameters_Obj params = def->parameters(); + + if (c->block() && c->name() != "@content" && !body->has_content()) { + error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); + } + Expression_Obj rv = c->arguments()->perform(&eval); + Arguments_Obj args = Cast(rv); + std::string msg(", in mixin `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_MIXIN, + { env } + }); + + Env new_env(def->environment()); + env_stack.push_back(&new_env); + if (c->block()) { + // represent mixin content blocks as thunks/closures + Definition_Obj thunk = SASS_MEMORY_NEW(Definition, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Parameters, c->pstate()), + c->block(), + Definition::MIXIN); + thunk->environment(env); + new_env.local_frame()["@content[m]"] = thunk; + } + + bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); + + env->set_global("is_in_mixin", bool_true); + if (Block_Ptr pr = block_stack.back()) { + trace_block->is_root(pr->is_root()); + } + block_stack.push_back(trace_block); + for (auto bb : body->elements()) { + if (Ruleset_Ptr r = Cast(bb)) { + r->is_root(trace_block->is_root()); + } + Statement_Obj ith = bb->perform(this); + if (ith) trace->block()->append(ith); + } + block_stack.pop_back(); + env->del_global("is_in_mixin"); + + ctx.callee_stack.pop_back(); + env_stack.pop_back(); + traces.pop_back(); + + recursions --; + return trace.detach(); + } + + Statement_Ptr Expand::operator()(Content_Ptr c) + { + Env* env = environment(); + // convert @content directives into mixin calls to the underlying thunk + if (!env->has("@content[m]")) return 0; + + if (block_stack.back()->is_root()) { + selector_stack.push_back(0); + } + + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Arguments, c->pstate())); + + Trace_Obj trace = Cast(call->perform(this)); + + if (block_stack.back()->is_root()) { + selector_stack.pop_back(); + } + + return trace.detach(); + } + + // produce an error if something is not implemented + inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) + { + std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); + String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); + error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); + return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); + } + + // process and add to last block on stack + inline void Expand::append_block(Block_Ptr b) + { + if (b->is_root()) call_stack.push_back(b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr stm = b->at(i); + Statement_Obj ith = stm->perform(this); + if (ith) block_stack.back()->append(ith); + } + if (b->is_root()) call_stack.pop_back(); + } + +} diff --git a/src/libsass/file.cpp b/src/libsass/file.cpp new file mode 100644 index 000000000..ab2065194 --- /dev/null +++ b/src/libsass/file.cpp @@ -0,0 +1,485 @@ +#include "sass.hpp" +#ifdef _WIN32 +# ifdef __MINGW32__ +# ifndef off64_t +# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ +# endif +# endif +# include +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#else +# include +#endif +#include +#include +#include +#include +#include +#include +#include "file.hpp" +#include "context.hpp" +#include "prelexer.hpp" +#include "utf8_string.hpp" +#include "sass_functions.hpp" +#include "sass2scss.h" + +#ifdef _WIN32 +# include + +# ifdef _MSC_VER +# include +inline static std::string wstring_to_string(const std::wstring& wstr) +{ + std::wstring_convert, wchar_t> wchar_converter; + return wchar_converter.to_bytes(wstr); +} +# else // mingw(/gcc) does not support C++11's codecvt yet. +inline static std::string wstring_to_string(const std::wstring &wstr) +{ + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} +# endif +#endif + +namespace Sass { + namespace File { + + // return the current directory + // always with forward slashes + // always with trailing slash + std::string get_cwd() + { + const size_t wd_len = 4096; + #ifndef _WIN32 + char wd[wd_len]; + char* pwd = getcwd(wd, wd_len); + // we should check error for more detailed info (e.g. ENOENT) + // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = pwd; + #else + wchar_t wd[wd_len]; + wchar_t* pwd = _wgetcwd(wd, wd_len); + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = wstring_to_string(pwd); + //convert backslashes to forward slashes + replace(cwd.begin(), cwd.end(), '\\', '/'); + #endif + if (cwd[cwd.length() - 1] != '/') cwd += '/'; + return cwd; + } + + // test if path exists and is a file + bool file_exists(const std::string& path) + { + #ifdef _WIN32 + wchar_t resolved[32768]; + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + DWORD dwAttrib = GetFileAttributesW(resolved); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + #else + struct stat st_buf; + return (stat (path.c_str(), &st_buf) == 0) && + (!S_ISDIR (st_buf.st_mode)); + #endif + } + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path) + { + #ifdef _WIN32 + if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; + #endif + size_t i = 0; + // check if we have a protocol + if (path[i] && Prelexer::is_alpha(path[i])) { + // skip over all alphanumeric characters + while (path[i] && Prelexer::is_alnum(path[i])) ++i; + i = i && path[i] == ':' ? i + 1 : 0; + } + return path[i] == '/'; + } + + // helper function to find the last directory seperator + inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) + { + size_t pos; + size_t pos_p = path.find_last_of('/', limit); + #ifdef _WIN32 + size_t pos_w = path.find_last_of('\\', limit); + #else + size_t pos_w = std::string::npos; + #endif + if (pos_p != std::string::npos && pos_w != std::string::npos) { + pos = std::max(pos_p, pos_w); + } + else if (pos_p != std::string::npos) { + pos = pos_p; + } + else { + pos = pos_w; + } + return pos; + } + + // return only the directory part of path + std::string dir_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return ""; + else return path.substr(0, pos+1); + } + + // return only the filename part of path + std::string base_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return path; + else return path.substr(pos+1); + } + + // do a logical clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path) + { + + // declarations + size_t pos; + + #ifdef _WIN32 + //convert backslashes to forward slashes + replace(path.begin(), path.end(), '\\', '/'); + #endif + + pos = 0; // remove all self references inside the path string + while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); + + // remove all leading and trailing self references + while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); + while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); + + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // then skip over start slashes + while (path[proto++] == '/') {} + + pos = proto; // collapse multiple delimiters into a single one + while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1); + + return path; + + } + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string l, std::string r) + { + + #ifdef _WIN32 + // convert Windows backslashes to URL forward slashes + replace(l.begin(), l.end(), '\\', '/'); + replace(r.begin(), r.end(), '\\', '/'); + #endif + + if (l.empty()) return r; + if (r.empty()) return l; + + if (is_absolute_path(r)) return r; + if (l[l.length()-1] != '/') l += '/'; + + // this does a logical cleanup of the right hand path + // Note that this does collapse x/../y sections into y. + // This is by design. If /foo on your system is a symlink + // to /bar/baz, then /foo/../cd is actually /bar/cd, + // not /cd as a naive ../ removal would give you. + // will only work on leading double dot dirs on rhs + // therefore it is safe if lhs is already resolved cwd + while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { + size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); + bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); + bool is_self = pos + 3 == L && (l[pos+1] == '.'); + if (!is_self && !is_slash) r = r.substr(3); + else if (pos == std::string::npos) break; + l = l.substr(0, pos == std::string::npos ? pos : pos + 1); + } + + return l + r; + } + + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path) + { + // magic algorith goes here!! + + // if the file is outside this directory show the absolute path + if (rel_path.substr(0, 3) == "../") { + return orig_path; + } + // this seems to work most of the time + return abs_path == orig_path ? abs_path : rel_path; + } + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd) + { + return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); + } + + // create a path that is relative to the given base directory + // path and base will first be resolved against cwd to make them absolute + std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd) + { + + std::string abs_path = rel2abs(path, cwd); + std::string abs_base = rel2abs(base, cwd); + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // distinguish between windows absolute paths and valid protocols + // we assume that protocols must at least have two chars to be valid + if (proto && path[proto++] == '/' && proto > 3) return path; + + #ifdef _WIN32 + // absolute link must have a drive letter, and we know that we + // can only create relative links if both are on the same drive + if (abs_base[0] != abs_path[0]) return abs_path; + #endif + + std::string stripped_uri = ""; + std::string stripped_base = ""; + + size_t index = 0; + size_t minSize = std::min(abs_path.size(), abs_base.size()); + for (size_t i = 0; i < minSize; ++i) { + #ifdef FS_CASE_SENSITIVE + if (abs_path[i] != abs_base[i]) break; + #else + // compare the charactes in a case insensitive manner + // windows fs is only case insensitive in ascii ranges + if (tolower(abs_path[i]) != tolower(abs_base[i])) break; + #endif + if (abs_path[i] == '/') index = i + 1; + } + for (size_t i = index; i < abs_path.size(); ++i) { + stripped_uri += abs_path[i]; + } + for (size_t i = index; i < abs_base.size(); ++i) { + stripped_base += abs_base[i]; + } + + size_t left = 0; + size_t directories = 0; + for (size_t right = 0; right < stripped_base.size(); ++right) { + if (stripped_base[right] == '/') { + if (stripped_base.substr(left, 2) != "..") { + ++directories; + } + else if (directories > 1) { + --directories; + } + else { + directories = 0; + } + left = right + 1; + } + } + + std::string result = ""; + for (size_t i = 0; i < directories; ++i) { + result += "../"; + } + result += stripped_uri; + + return result; + } + + // Resolution order for ambiguous imports: + // (1) filename as given + // (2) underscore + given + // (3) underscore + given + extension + // (4) given + extension + std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) + { + std::string filename = join_paths(root, file); + // split the filename + std::string base(dir_name(file)); + std::string name(base_name(file)); + std::vector includes; + // create full path (maybe relative) + std::string rel_path(join_paths(base, name)); + std::string abs_path(join_paths(root, rel_path)); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test variation with underscore + rel_path = join_paths(base, "_" + name); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test exts plus underscore + for(auto ext : exts) { + rel_path = join_paths(base, "_" + name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); + } + // next test plain name with exts + for(auto ext : exts) { + rel_path = join_paths(base, name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); + } + // nothing found + return includes; + } + + std::vector find_files(const std::string& file, const std::vector paths) + { + std::vector includes; + for (std::string path : paths) { + std::string abs_path(join_paths(path, file)); + if (file_exists(abs_path)) includes.push_back(abs_path); + } + return includes; + } + + std::vector find_files(const std::string& file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + // struct Sass_Options* options = sass_compiler_get_options(compiler); + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(dir_name(import->abs_path)); + paths.insert(paths.end(), incs.begin(), incs.end()); + // dispatch to find files in paths + return find_files(file, paths); + } + + // helper function to search one file in all include paths + // this is normally not used internally by libsass (C-API sugar) + std::string find_file(const std::string& file, const std::vector paths) + { + if (file.empty()) return file; + auto res = find_files(file, paths); + return res.empty() ? "" : res.front(); + } + + // helper function to resolve a filename + std::string find_include(const std::string& file, const std::vector paths) + { + // search in every include path for a match + for (size_t i = 0, S = paths.size(); i < S; ++i) + { + std::vector resolved(resolve_includes(paths[i], file)); + if (resolved.size()) return resolved[0].abs_path; + } + // nothing found + return std::string(""); + } + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& path) + { + #ifdef _WIN32 + BYTE* pBuffer; + DWORD dwBytes; + wchar_t resolved[32768]; + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + DWORD dwFileLength = GetFileSize(hFile, NULL); + if (dwFileLength == INVALID_FILE_SIZE) return 0; + // allocate an extra byte for the null char + // and another one for edge-cases in lexer + pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); + ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); + pBuffer[dwFileLength+0] = '\0'; + pBuffer[dwFileLength+1] = '\0'; + CloseHandle(hFile); + // just convert from unsigned char* + char* contents = (char*) pBuffer; + #else + struct stat st; + if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0; + std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + char* contents = 0; + if (file.is_open()) { + size_t size = file.tellg(); + // allocate an extra byte for the null char + // and another one for edge-cases in lexer + contents = (char*) malloc((size+2)*sizeof(char)); + file.seekg(0, std::ios::beg); + file.read(contents, size); + contents[size+0] = '\0'; + contents[size+1] = '\0'; + file.close(); + } + #endif + std::string extension; + if (path.length() > 5) { + extension = path.substr(path.length() - 5, 5); + } + for(size_t i=0; i split_path_list(const char* str) + { + std::vector paths; + if (str == NULL) return paths; + // find delimiter via prelexer (return zero at end) + const char* end = Prelexer::find_first(str); + // search until null delimiter + while (end) { + // add path from current position to delimiter + paths.push_back(std::string(str, end - str)); + str = end + 1; // skip delimiter + end = Prelexer::find_first(str); + } + // add path from current position to end + paths.push_back(std::string(str)); + // return back + return paths; + } + + } +} diff --git a/src/libsass/file.hpp b/src/libsass/file.hpp new file mode 100644 index 000000000..a043bea7a --- /dev/null +++ b/src/libsass/file.hpp @@ -0,0 +1,139 @@ +#ifndef SASS_FILE_H +#define SASS_FILE_H + +#include +#include + +#include "sass/context.h" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + namespace File { + + // return the current directory + // always with forward slashes + std::string get_cwd(); + + // test if path exists and is a file + bool file_exists(const std::string& file); + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path); + + // return only the directory part of path + std::string dir_name(const std::string& path); + + // return only the filename part of path + std::string base_name(const std::string&); + + // do a locigal clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path); + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string root, std::string name); + + // if the relative path is outside of the cwd we want want to + // show the absolute path in console messages + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path); + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // create a path that is relative to the given base directory + // path and base will first be resolved against cwd to make them absolute + std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // helper function to resolve a filename + // searching without variations in all paths + std::string find_file(const std::string& file, struct Sass_Compiler* options); + std::string find_file(const std::string& file, const std::vector paths); + + // helper function to resolve a include filename + // this has the original resolve logic for sass include + std::string find_include(const std::string& file, const std::vector paths); + + // split a path string delimited by semicolons or colons (OS dependent) + std::vector split_path_list(const char* paths); + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& file); + + } + + // requested import + class Importer { + public: + // requested import path + std::string imp_path; + // parent context path + std::string ctx_path; + // base derived from context path + // this really just acts as a cache + std::string base_path; + public: + Importer(std::string imp_path, std::string ctx_path) + : imp_path(File::make_canonical_path(imp_path)), + ctx_path(File::make_canonical_path(ctx_path)), + base_path(File::dir_name(ctx_path)) + { } + }; + + // a resolved include (final import) + class Include : public Importer { + public: + // resolved absolute path + std::string abs_path; + // is a deprecated file type + bool deprecated; + public: + Include(const Importer& imp, std::string abs_path, bool deprecated) + : Importer(imp), abs_path(abs_path), deprecated(deprecated) + { } + Include(const Importer& imp, std::string abs_path) + : Importer(imp), abs_path(abs_path), deprecated(false) + { } + }; + + // a loaded resource + class Resource { + public: + // the file contents + char* contents; + // conected sourcemap + char* srcmap; + public: + Resource(char* contents, char* srcmap) + : contents(contents), srcmap(srcmap) + { } + }; + + // parsed stylesheet from loaded resource + class StyleSheet : public Resource { + public: + // parsed root block + Block_Obj root; + public: + StyleSheet(const Resource& res, Block_Obj root) + : Resource(res), root(root) + { } + }; + + namespace File { + + static std::vector defaultExtensions = { ".scss", ".sass" }; + + std::vector resolve_includes(const std::string& root, const std::string& file, + const std::vector& exts = defaultExtensions); + + + } + +} + +#endif diff --git a/src/libsass/functions.cpp b/src/libsass/functions.cpp new file mode 100644 index 000000000..c9999fc3a --- /dev/null +++ b/src/libsass/functions.cpp @@ -0,0 +1,2234 @@ +#include "sass.hpp" +#include "functions.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "parser.hpp" +#include "constants.hpp" +#include "inspect.hpp" +#include "extend.hpp" +#include "eval.hpp" +#include "util.hpp" +#include "expand.hpp" +#include "operators.hpp" +#include "utf8_string.hpp" +#include "sass/base.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) +#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) + +// return a number object (copied since we want to have reduced units) +#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy + +// special function for weird hsla percent (10px == 10% == 10 != 0.1) +#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double + +// macros for common ranges (u mean unsigned or upper, r for full range) +#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double +#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double +#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double +#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double +#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double +#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double + +// macros for color related inputs (rbg and alpha/opacity values) +#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double +#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double + +namespace Sass { + using std::stringstream; + using std::endl; + + Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) + { + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); + sig_parser.lex(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + sig, + name, + params, + func, + false); + } + + Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) + { + using namespace Prelexer; + + const char* sig = sass_function_get_signature(c_func); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); + // allow to overload generic callback plus @warn, @error and @debug with custom functions + sig_parser.lex < alternatives < identifier, exactly <'*'>, + exactly < Constants::warn_kwd >, + exactly < Constants::error_kwd >, + exactly < Constants::debug_kwd > + > >(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[c function]"), + sig, + name, + params, + c_func, + false, true); + } + + std::string function_name(Signature sig) + { + std::string str(sig); + return str.substr(0, str.find('(')); + } + + namespace Functions { + + inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) + { + try { + throw; + } + catch (utf8::invalid_code_point) { + std::string msg("utf8::invalid_code_point"); + error(msg, pstate, traces); + } + catch (utf8::not_enough_room) { + std::string msg("utf8::not_enough_room"); + error(msg, pstate, traces); + } + catch (utf8::invalid_utf8) { + std::string msg("utf8::invalid_utf8"); + error(msg, pstate, traces); + } + catch (...) { throw; } + } + + template + T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + T* val = Cast(env[argname]); + if (!val) { + std::string msg("argument `"); + msg += argname; + msg += "` of `"; + msg += sig; + msg += "` must be a "; + msg += T::type_name(); + error(msg, pstate, traces); + } + return val; + } + + Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Map_Ptr val = Cast(env[argname]); + if (val) return val; + + List_Ptr lval = Cast(env[argname]); + if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); + + // fallback on get_arg for error handling + val = get_arg(argname, env, sig, pstate, traces); + return val; + } + + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + double v = tmpnr.value(); + if (!(lo <= v && v <= hi)) { + std::stringstream msg; + msg << "argument `" << argname << "` of `" << sig << "` must be between "; + msg << lo << " and " << hi; + error(msg.str(), pstate, traces); + } + return v; + } + + Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + val = SASS_MEMORY_COPY(val); + val->reduce(); + return val; + } + + double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + /* + if (tmpnr.unit() == "%") { + tmpnr.value(tmpnr.value() / 100); + tmpnr.numerators.clear(); + } else { + if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); + } + */ + return tmpnr.value(); + } + + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + return tmpnr.value(); + } + + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 255.0); + } + } + + + inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value(), 0.0), 100.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 1.0); + } + } + + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) + + template + T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); + + template <> + Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + return Parser::parse_selector(exp_src.c_str(), ctx, traces); + } + + template <> + Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a string for `" << function_name(sig) << "'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); + if (sel_list->length() == 0) return NULL; + Complex_Selector_Obj first = sel_list->first(); + if (!first->tail()) return first->head(); + return first->tail()->head(); + } + + #ifdef __MINGW32__ + uint64_t GetSeed() + { + HCRYPTPROV hp = 0; + BYTE rb[8]; + CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hp, sizeof(rb), rb); + CryptReleaseContext(hp, 0); + + uint64_t seed; + memcpy(&seed, &rb[0], sizeof(seed)); + + return seed; + } + #else + uint64_t GetSeed() + { + std::random_device rd; + return rd(); + } + #endif + + // note: the performance of many implementations of + // random_device degrades sharply once the entropy pool + // is exhausted. For practical use, random_device is + // generally only used to seed a PRNG such as mt19937. + static std::mt19937 rand(static_cast(GetSeed())); + + // features + static std::set features { + "global-variable-shadowing", + "extend-selector-pseudoclass", + "at-error", + "units-level-3", + "custom-property" + }; + + //////////////// + // RGB FUNCTIONS + //////////////// + + inline bool special_number(String_Constant_Ptr s) { + if (s) { + std::string calc("calc("); + std::string var("var("); + std::string ss(s->value()); + return std::equal(calc.begin(), calc.end(), ss.begin()) || + std::equal(var.begin(), var.end(), ss.begin()); + } + return false; + } + + Signature rgb_sig = "rgb($red, $green, $blue)"; + BUILT_IN(rgb) + { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ")" + ); + } + + return SASS_MEMORY_NEW(Color, + pstate, + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue")); + } + + Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; + BUILT_IN(rgba_4) + { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return SASS_MEMORY_NEW(Color, + pstate, + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue"), + ALPHA_NUM("$alpha")); + } + + Signature rgba_2_sig = "rgba($color, $alpha)"; + BUILT_IN(rgba_2) + { + if ( + special_number(Cast(env["$color"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$color"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + Color_Ptr c_arg = ARG("$color", Color); + + if ( + special_number(Cast(env["$alpha"])) + ) { + std::stringstream strm; + strm << "rgba(" + << (int)c_arg->r() << ", " + << (int)c_arg->g() << ", " + << (int)c_arg->b() << ", " + << env["$alpha"]->to_string() + << ")"; + return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); + } + + Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); + new_c->a(ALPHA_NUM("$alpha")); + new_c->disp(""); + return new_c; + } + + Signature red_sig = "red($color)"; + BUILT_IN(red) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } + + Signature green_sig = "green($color)"; + BUILT_IN(green) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } + + Signature blue_sig = "blue($color)"; + BUILT_IN(blue) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } + + Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { + double p = weight/100; + double w = 2*p - 1; + double a = color1->a() - color2->a(); + + double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; + double w2 = 1 - w1; + + return SASS_MEMORY_NEW(Color, + pstate, + Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), + Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), + Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), + color1->a()*p + color2->a()*(1-p)); + } + + Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; + BUILT_IN(mix) + { + Color_Obj color1 = ARG("$color-1", Color); + Color_Obj color2 = ARG("$color-2", Color); + double weight = DARG_U_PRCT("$weight"); + return colormix(ctx, pstate, color1, color2, weight); + + } + + //////////////// + // HSL FUNCTIONS + //////////////// + + // RGB to HSL helper function + struct HSL { double h; double s; double l; }; + HSL rgb_to_hsl(double r, double g, double b) + { + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + r /= 255.0; g /= 255.0; b /= 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0; + double s; + double l = (max + min) / 2.0; + + if (NEAR_EQUAL(max, min)) { + h = s = 0; // achromatic + } + else { + if (l < 0.5) s = delta / (max + min); + else s = delta / (2.0 - max - min); + + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; + } + + HSL hsl_struct; + hsl_struct.h = h / 6 * 360; + hsl_struct.s = s * 100; + hsl_struct.l = l * 100; + + return hsl_struct; + } + + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) { + while (h < 0) h += 1; + while (h > 1) h -= 1; + if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; + if (h*2.0 < 1) return m2; + if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; + return m1; + } + + Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) + { + h /= 360.0; + s /= 100.0; + l /= 100.0; + + if (l < 0) l = 0; + if (s < 0) s = 0; + if (l > 1) l = 1; + if (s > 1) s = 1; + while (h < 0) h += 1; + while (h > 1) h -= 1; + + // if saturation is exacly zero, we loose + // information for hue, since it will evaluate + // to zero if converted back from rgb. Setting + // saturation to a very tiny number solves this. + if (s == 0) s = 1e-10; + + // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + double m2; + if (l <= 0.5) m2 = l*(s+1.0); + else m2 = (l+s)-(l*s); + double m1 = (l*2.0)-m2; + // round the results -- consider moving this into the Color constructor + double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); + double g = (h_to_rgb(m1, m2, h) * 255.0); + double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); + + return SASS_MEMORY_NEW(Color, pstate, r, g, b, a); + } + + Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; + BUILT_IN(hsl) + { + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + 1.0, + ctx, + pstate); + } + + Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; + BUILT_IN(hsla) + { + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + ARGVAL("$alpha"), + ctx, + pstate); + } + + Signature hue_sig = "hue($color)"; + BUILT_IN(hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); + } + + Signature saturation_sig = "saturation($color)"; + BUILT_IN(saturation) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); + } + + Signature lightness_sig = "lightness($color)"; + BUILT_IN(lightness) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); + } + + Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; + BUILT_IN(adjust_hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + double degrees = ARGVAL("$degrees"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h + degrees, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature lighten_sig = "lighten($color, $amount)"; + BUILT_IN(lighten) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + //Check lightness is not negative before lighten it + double hslcolorL = hsl_color.l; + if (hslcolorL < 0) { + hslcolorL = 0; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL + amount, + rgb_color->a(), + ctx, + pstate); + } + + Signature darken_sig = "darken($color, $amount)"; + BUILT_IN(darken) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + //Check lightness if not over 100, before darken it + double hslcolorL = hsl_color.l; + if (hslcolorL > 100) { + hslcolorL = 100; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL - amount, + rgb_color->a(), + ctx, + pstate); + } + + Signature saturate_sig = "saturate($color, $amount: false)"; + BUILT_IN(saturate) + { + // CSS3 filter function overload: pass literal through directly + if (!Cast(env["$amount"])) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); + } + + double amount = DARG_U_PRCT("$amount"); + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s + amount; + + // Saturation cannot be below 0 or above 100 + if (hslcolorS < 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature desaturate_sig = "desaturate($color, $amount)"; + BUILT_IN(desaturate) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s - amount; + + // Saturation cannot be below 0 or above 100 + if (hslcolorS <= 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature grayscale_sig = "grayscale($color)"; + BUILT_IN(grayscale) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); + } + + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h, + 0.0, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature complement_sig = "complement($color)"; + BUILT_IN(complement) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h - 180.0, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature invert_sig = "invert($color, $weight: 100%)"; + BUILT_IN(invert) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); + } + + double weight = DARG_U_PRCT("$weight"); + Color_Ptr rgb_color = ARG("$color", Color); + Color_Obj inv = SASS_MEMORY_NEW(Color, + pstate, + 255 - rgb_color->r(), + 255 - rgb_color->g(), + 255 - rgb_color->b(), + rgb_color->a()); + return colormix(ctx, pstate, inv, rgb_color, weight); + } + + //////////////////// + // OPACITY FUNCTIONS + //////////////////// + Signature alpha_sig = "alpha($color)"; + Signature opacity_sig = "opacity($color)"; + BUILT_IN(alpha) + { + String_Constant_Ptr ie_kwd = Cast(env["$color"]); + if (ie_kwd) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); + } + + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); + } + + return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); + } + + Signature opacify_sig = "opacify($color, $amount)"; + Signature fade_in_sig = "fade-in($color, $amount)"; + BUILT_IN(opacify) + { + Color_Ptr color = ARG("$color", Color); + double amount = DARG_U_FACT("$amount"); + double alpha = std::min(color->a() + amount, 1.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + Signature transparentize_sig = "transparentize($color, $amount)"; + Signature fade_out_sig = "fade-out($color, $amount)"; + BUILT_IN(transparentize) + { + Color_Ptr color = ARG("$color", Color); + double amount = DARG_U_FACT("$amount"); + double alpha = std::max(color->a() - amount, 0.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + //////////////////////// + // OTHER COLOR FUNCTIONS + //////////////////////// + + Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(adjust_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); + } + if (rgb) { + double rr = r ? DARG_R_BYTE("$red") : 0; + double gg = g ? DARG_R_BYTE("$green") : 0; + double bb = b ? DARG_R_BYTE("$blue") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rr, + color->g() + gg, + color->b() + bb, + color->a() + aa); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + double ss = s ? DARG_R_PRCT("$saturation") : 0; + double ll = l ? DARG_R_PRCT("$lightness") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; + return hsla_impl(hsl_struct.h + (h ? h->value() : 0), + hsl_struct.s + ss, + hsl_struct.l + ll, + color->a() + aa, + ctx, + pstate); + } + if (a) { + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + (a ? a->value() : 0)); + } + error("not enough arguments for `adjust-color'", pstate, traces); + // unreachable + return color; + } + + Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(scale_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); + } + if (rgb) { + double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; + double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; + double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), + color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), + color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + if (hsl) { + double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; + double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; + double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); + hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); + hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); + double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double ascale = (DARG_R_PRCT("$alpha")) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + error("not enough arguments for `scale-color'", pstate, traces); + // unreachable + return color; + } + + Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(change_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); + } + if (rgb) { + return SASS_MEMORY_NEW(Color, + pstate, + r ? DARG_U_BYTE("$red") : color->r(), + g ? DARG_U_BYTE("$green") : color->g(), + b ? DARG_U_BYTE("$blue") : color->b(), + a ? DARG_U_BYTE("$alpha") : color->a()); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + if (h) hsl_struct.h = std::fmod(h->value(), 360.0); + if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); + if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); + double alpha = a ? DARG_U_FACT("$alpha") : color->a(); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double alpha = DARG_U_FACT("$alpha"); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + error("not enough arguments for `change-color'", pstate, traces); + // unreachable + return color; + } + + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + Signature ie_hex_str_sig = "ie-hex-str($color)"; + BUILT_IN(ie_hex_str) + { + Color_Ptr c = ARG("$color", Color); + double r = cap_channel<0xff>(c->r()); + double g = cap_channel<0xff>(c->g()); + double b = cap_channel<0xff>(c->b()); + double a = cap_channel<1> (c->a()) * 255; + + std::stringstream ss; + ss << '#' << std::setw(2) << std::setfill('0'); + ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); + + std::string result(ss.str()); + for (size_t i = 0, L = result.length(); i < L; ++i) { + result[i] = std::toupper(result[i]); + } + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + /////////////////// + // STRING FUNCTIONS + /////////////////// + + Signature unquote_sig = "unquote($string)"; + BUILT_IN(sass_unquote) + { + AST_Node_Obj arg = env["$string"]; + if (String_Quoted_Ptr string_quoted = Cast(arg)) { + String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); + // remember if the string was quoted (color tokens) + result->is_delayed(true); // delay colors + return result; + } + else if (String_Constant_Ptr str = Cast(arg)) { + return str; + } + else if (Expression_Ptr ex = Cast(arg)) { + Sass_Output_Style oldstyle = ctx.c_options.output_style; + ctx.c_options.output_style = SASS_STYLE_NESTED; + std::string val(arg->to_string(ctx.c_options)); + val = Cast(arg) ? "null" : val; + ctx.c_options.output_style = oldstyle; + + deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); + return ex; + } + throw std::runtime_error("Invalid Data Type for unquote"); + } + + Signature quote_sig = "quote($string)"; + BUILT_IN(sass_quote) + { + AST_Node_Obj arg = env["$string"]; + // only set quote mark to true if already a string + if (String_Quoted_Ptr qstr = Cast(arg)) { + qstr->quote_mark('*'); + return qstr; + } + // all other nodes must be converted to a string node + std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); + String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); + result->quote_mark('*'); + return result; + } + + + Signature str_length_sig = "str-length($string)"; + BUILT_IN(str_length) + { + size_t len = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + len = UTF_8::code_point_count(s->value(), 0, s->value().size()); + + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)len); + } + + Signature str_insert_sig = "str-insert($string, $insert, $index)"; + BUILT_IN(str_insert) + { + std::string str; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + str = s->value(); + str = unquote(str); + String_Constant_Ptr i = ARG("$insert", String_Constant); + std::string ins = i->value(); + ins = unquote(ins); + double index = ARGVAL("$index"); + size_t len = UTF_8::code_point_count(str, 0, str.size()); + + if (index > 0 && index <= len) { + // positive and within string length + str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); + } + else if (index > len) { + // positive and past string length + str += ins; + } + else if (index == 0) { + str = ins + str; + } + else if (std::abs(index) <= len) { + // negative and within string length + index += len + 1; + str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); + } + else { + // negative and past string length + str = ins + str; + } + + if (String_Quoted_Ptr ss = Cast(s)) { + if (ss->quote_mark()) str = quote(str); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature str_index_sig = "str-index($string, $substring)"; + BUILT_IN(str_index) + { + size_t index = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + String_Constant_Ptr t = ARG("$substring", String_Constant); + std::string str = s->value(); + str = unquote(str); + std::string substr = t->value(); + substr = unquote(substr); + + size_t c_index = str.find(substr); + if(c_index == std::string::npos) { + return SASS_MEMORY_NEW(Null, pstate); + } + index = UTF_8::code_point_count(str, 0, c_index) + 1; + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)index); + } + + Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; + BUILT_IN(str_slice) + { + std::string newstr; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + double start_at = ARGVAL("$start-at"); + double end_at = ARGVAL("$end-at"); + String_Quoted_Ptr ss = Cast(s); + + std::string str = unquote(s->value()); + + size_t size = utf8::distance(str.begin(), str.end()); + + if (!Cast(env["$end-at"])) { + end_at = -1; + } + + if (end_at == 0 || (end_at + size) < 0) { + if (ss && ss->quote_mark()) newstr = quote(""); + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + if (end_at < 0) { + end_at += size + 1; + if (end_at == 0) end_at = 1; + } + if (end_at > size) { end_at = (double)size; } + if (start_at < 0) { + start_at += size + 1; + if (start_at < 0) start_at = 0; + } + else if (start_at == 0) { ++ start_at; } + + if (start_at <= end_at) + { + std::string::iterator start = str.begin(); + utf8::advance(start, start_at - 1, str.end()); + std::string::iterator end = start; + utf8::advance(end, end_at - start_at + 1, str.end()); + newstr = std::string(start, end); + } + if (ss) { + if(ss->quote_mark()) newstr = quote(newstr); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + Signature to_upper_case_sig = "to-upper-case($string)"; + BUILT_IN(to_upper_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::toupper(str[i]); + } + } + + if (String_Quoted_Ptr ss = Cast(s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + Signature to_lower_case_sig = "to-lower-case($string)"; + BUILT_IN(to_lower_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::tolower(str[i]); + } + } + + if (String_Quoted_Ptr ss = Cast(s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + /////////////////// + // NUMBER FUNCTIONS + /////////////////// + + Signature percentage_sig = "percentage($number)"; + BUILT_IN(percentage) + { + Number_Obj n = ARGN("$number"); + if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); + return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); + } + + Signature round_sig = "round($number)"; + BUILT_IN(round) + { + Number_Obj r = ARGN("$number"); + r->value(Sass::round(r->value(), ctx.c_options.precision)); + r->pstate(pstate); + return r.detach(); + } + + Signature ceil_sig = "ceil($number)"; + BUILT_IN(ceil) + { + Number_Obj r = ARGN("$number"); + r->value(std::ceil(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature floor_sig = "floor($number)"; + BUILT_IN(floor) + { + Number_Obj r = ARGN("$number"); + r->value(std::floor(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature abs_sig = "abs($number)"; + BUILT_IN(abs) + { + Number_Obj r = ARGN("$number"); + r->value(std::abs(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature min_sig = "min($numbers...)"; + BUILT_IN(min) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj least = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = Cast(val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); + } + if (least) { + if (*xi < *least) least = xi; + } else least = xi; + } + return least.detach(); + } + + Signature max_sig = "max($numbers...)"; + BUILT_IN(max) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj greatest = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = Cast(val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); + } + if (greatest) { + if (*greatest < *xi) greatest = xi; + } else greatest = xi; + } + return greatest.detach(); + } + + Signature random_sig = "random($limit:false)"; + BUILT_IN(random) + { + AST_Node_Obj arg = env["$limit"]; + Value_Ptr v = Cast(arg); + Number_Ptr l = Cast(arg); + Boolean_Ptr b = Cast(arg); + if (l) { + double lv = l->value(); + if (lv < 1) { + stringstream err; + err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; + error(err.str(), pstate, traces); + } + bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; + if (!eq_int) { + stringstream err; + err << "Expected $limit to be an integer but got " << lv << " for `random'"; + error(err.str(), pstate, traces); + } + std::uniform_real_distribution<> distributor(1, lv + 1); + uint_fast32_t distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, (double)distributed); + } + else if (b) { + std::uniform_real_distribution<> distributor(0, 1); + double distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, distributed); + } else if (v) { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); + } else { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); + } + } + + ///////////////// + // LIST FUNCTIONS + ///////////////// + + Signature length_sig = "length($list)"; + BUILT_IN(length) + { + if (Selector_List_Ptr sl = Cast(env["$list"])) { + return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); + } + Expression_Ptr v = ARG("$list", Expression); + if (v->concrete_type() == Expression::MAP) { + Map_Ptr map = Cast(env["$list"]); + return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); + } + if (v->concrete_type() == Expression::SELECTOR) { + if (Compound_Selector_Ptr h = Cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); + } else if (Selector_List_Ptr ls = Cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); + } else { + return SASS_MEMORY_NEW(Number, pstate, 1); + } + } + + List_Ptr list = Cast(env["$list"]); + return SASS_MEMORY_NEW(Number, + pstate, + (double)(list ? list->size() : 1)); + } + + Signature nth_sig = "nth($list, $n)"; + BUILT_IN(nth) + { + double nr = ARGVAL("$n"); + Map_Ptr m = Cast(env["$list"]); + if (Selector_List_Ptr sl = Cast(env["$list"])) { + size_t len = m ? m->length() : sl->length(); + bool empty = m ? m->empty() : sl->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + // return (*sl)[static_cast(index)]; + Listize listize; + return (*sl)[static_cast(index)]->perform(&listize); + } + List_Obj l = Cast(env["$list"]); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); + // if the argument isn't a list, then wrap it in a singleton list + if (!m && !l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + size_t len = m ? m->length() : l->length(); + bool empty = m ? m->empty() : l->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + + if (m) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(m->keys()[static_cast(index)]); + l->append(m->at(m->keys()[static_cast(index)])); + return l.detach(); + } + else { + Expression_Obj rv = l->value_at_index(static_cast(index)); + rv->set_delayed(false); + return rv.detach(); + } + } + + Signature set_nth_sig = "set-nth($list, $n, $value)"; + BUILT_IN(set_nth) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Number_Obj n = ARG("$n", Number); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); + if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + result->append(((i == index) ? v : (*l)[i])); + } + return result; + } + + Signature index_sig = "index($list, $value)"; + BUILT_IN(index) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + for (size_t i = 0, L = l->length(); i < L; ++i) { + if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); + } + return SASS_MEMORY_NEW(Null, pstate); + } + + Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; + BUILT_IN(join) + { + Map_Obj m1 = Cast(env["$list1"]); + Map_Obj m2 = Cast(env["$list2"]); + List_Obj l1 = Cast(env["$list1"]); + List_Obj l2 = Cast(env["$list2"]); + String_Constant_Obj sep = ARG("$separator", String_Constant); + enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); + Value* bracketed = ARG("$bracketed", Value); + bool is_bracketed = (l1 ? l1->is_bracketed() : false); + if (!l1) { + l1 = SASS_MEMORY_NEW(List, pstate, 1); + l1->append(ARG("$list1", Expression)); + sep_val = (l2 ? l2->separator() : SASS_SPACE); + is_bracketed = (l2 ? l2->is_bracketed() : false); + } + if (!l2) { + l2 = SASS_MEMORY_NEW(List, pstate, 1); + l2->append(ARG("$list2", Expression)); + } + if (m1) { + l1 = m1->to_list(pstate); + sep_val = SASS_COMMA; + } + if (m2) { + l2 = m2->to_list(pstate); + } + size_t len = l1->length() + l2->length(); + std::string sep_str = unquote(sep->value()); + if (sep_str == "space") sep_val = SASS_SPACE; + else if (sep_str == "comma") sep_val = SASS_COMMA; + else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + String_Constant_Obj bracketed_as_str = Cast(bracketed); + bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; + if (!bracketed_is_auto) { + is_bracketed = !bracketed->is_false(); + } + List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); + result->concat(l1); + result->concat(l2); + return result.detach(); + } + + Signature append_sig = "append($list, $val, $separator: auto)"; + BUILT_IN(append) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj v = ARG("$val", Expression); + if (Selector_List_Ptr sl = Cast(env["$list"])) { + Listize listize; + l = Cast(sl->perform(&listize)); + } + String_Constant_Obj sep = ARG("$separator", String_Constant); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + List_Ptr result = SASS_MEMORY_COPY(l); + std::string sep_str(unquote(sep->value())); + if (sep_str != "auto") { // check default first + if (sep_str == "space") result->separator(SASS_SPACE); + else if (sep_str == "comma") result->separator(SASS_COMMA); + else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + } + if (l->is_arglist()) { + result->append(SASS_MEMORY_NEW(Argument, + v->pstate(), + v, + "", + false, + false)); + + } else { + result->append(v); + } + return result; + } + + Signature zip_sig = "zip($lists...)"; + BUILT_IN(zip) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); + size_t shortest = 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + List_Obj ith = Cast(arglist->value_at_index(i)); + Map_Obj mith = Cast(arglist->value_at_index(i)); + if (!ith) { + if (mith) { + ith = mith->to_list(pstate); + } else { + ith = SASS_MEMORY_NEW(List, pstate, 1); + ith->append(arglist->value_at_index(i)); + } + if (arglist->is_arglist()) { + Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX + arg->value(ith); + } else { + (*arglist)[i] = ith; + } + } + shortest = (i ? std::min(shortest, ith->length()) : ith->length()); + } + List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); + size_t L = arglist->length(); + for (size_t i = 0; i < shortest; ++i) { + List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); + for (size_t j = 0; j < L; ++j) { + zipper->append(Cast(arglist->value_at_index(j))->at(i)); + } + zippers->append(zipper); + } + return zippers; + } + + Signature list_separator_sig = "list_separator($list)"; + BUILT_IN(list_separator) + { + List_Obj l = Cast(env["$list"]); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + return SASS_MEMORY_NEW(String_Quoted, + pstate, + l->separator() == SASS_COMMA ? "comma" : "space"); + } + + ///////////////// + // MAP FUNCTIONS + ///////////////// + + Signature map_get_sig = "map-get($map, $key)"; + BUILT_IN(map_get) + { + // leaks for "map-get((), foo)" if not Obj + // investigate why this is (unexpected) + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + try { + Expression_Obj val = m->at(v); + if (!val) return SASS_MEMORY_NEW(Null, pstate); + val->set_delayed(false); + return val.detach(); + } catch (const std::out_of_range&) { + return SASS_MEMORY_NEW(Null, pstate); + } + catch (...) { throw; } + } + + Signature map_has_key_sig = "map-has-key($map, $key)"; + BUILT_IN(map_has_key) + { + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); + } + + Signature map_keys_sig = "map-keys($map)"; + BUILT_IN(map_keys) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(key); + } + return result; + } + + Signature map_values_sig = "map-values($map)"; + BUILT_IN(map_values) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(m->at(key)); + } + return result; + } + + Signature map_merge_sig = "map-merge($map1, $map2)"; + BUILT_IN(map_merge) + { + Map_Obj m1 = ARGM("$map1", Map, ctx); + Map_Obj m2 = ARGM("$map2", Map, ctx); + + size_t len = m1->length() + m2->length(); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); + // concat not implemented for maps + *result += m1; + *result += m2; + return result; + } + + Signature map_remove_sig = "map-remove($map, $keys...)"; + BUILT_IN(map_remove) + { + bool remove; + Map_Obj m = ARGM("$map", Map, ctx); + List_Obj arglist = ARG("$keys", List); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, 1); + for (auto key : m->keys()) { + remove = false; + for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { + remove = Operators::eq(key, arglist->value_at_index(j)); + } + if (!remove) *result << std::make_pair(key, m->at(key)); + } + return result; + } + + Signature keywords_sig = "keywords($args)"; + BUILT_IN(keywords) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy + Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); + for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX + std::string name = std::string(arg->name()); + name = name.erase(0, 1); // sanitize name (remove dollar sign) + *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, + pstate, name), + arg->value()); + } + return result.detach(); + } + + ////////////////////////// + // INTROSPECTION FUNCTIONS + ////////////////////////// + + Signature type_of_sig = "type-of($value)"; + BUILT_IN(type_of) + { + Expression_Ptr v = ARG("$value", Expression); + return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); + } + + Signature unit_sig = "unit($number)"; + BUILT_IN(unit) + { + Number_Obj arg = ARGN("$number"); + std::string str(quote(arg->unit(), '"')); + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature unitless_sig = "unitless($number)"; + BUILT_IN(unitless) + { + Number_Obj arg = ARGN("$number"); + bool unitless = arg->is_unitless(); + return SASS_MEMORY_NEW(Boolean, pstate, unitless); + } + + Signature comparable_sig = "comparable($number-1, $number-2)"; + BUILT_IN(comparable) + { + Number_Obj n1 = ARGN("$number-1"); + Number_Obj n2 = ARGN("$number-2"); + if (n1->is_unitless() || n2->is_unitless()) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + // normalize into main units + n1->normalize(); n2->normalize(); + Units &lhs_unit = *n1, &rhs_unit = *n2; + bool is_comparable = (lhs_unit == rhs_unit); + return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); + } + + Signature variable_exists_sig = "variable-exists($name)"; + BUILT_IN(variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature global_variable_exists_sig = "global-variable-exists($name)"; + BUILT_IN(global_variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature function_exists_sig = "function-exists($name)"; + BUILT_IN(function_exists) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + + if(d_env.has_global(name+"[f]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature mixin_exists_sig = "mixin-exists($name)"; + BUILT_IN(mixin_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global(s+"[m]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature feature_exists_sig = "feature-exists($name)"; + BUILT_IN(feature_exists) + { + std::string s = unquote(ARG("$name", String_Constant)->value()); + + if(features.find(s) == features.end()) { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + } + + Signature call_sig = "call($name, $args...)"; + BUILT_IN(call) + { + std::string name; + Function_Ptr ff = Cast(env["$name"]); + String_Constant_Ptr ss = Cast(env["$name"]); + + if (ss) { + name = Util::normalize_underscores(unquote(ss->value())); + std::cerr << "DEPRECATION WARNING: "; + std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; + std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl; + std::cerr << std::endl; + } else if (ff) { + name = ff->name(); + } + + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); + + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + // std::string full_name(name + "[f]"); + // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; + // Parameters_Ptr params = def ? def->parameters() : 0; + // size_t param_size = params ? params->length() : 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj expr = arglist->value_at_index(i); + // if (params && params->has_rest_parameter()) { + // Parameter_Obj p = param_size > i ? (*params)[i] : 0; + // List_Ptr list = Cast(expr); + // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; + // } + if (arglist->is_arglist()) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX + args->append(SASS_MEMORY_NEW(Argument, + pstate, + expr, + arg ? arg->name() : "", + arg ? arg->is_rest_argument() : false, + arg ? arg->is_keyword_argument() : false)); + } else { + args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); + } + } + Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); + Expand expand(ctx, &d_env, &selector_stack); + func->via_call(true); // calc invoke is allowed + if (ff) func->func(ff); + return func->perform(&expand.eval); + } + + //////////////////// + // BOOLEAN FUNCTIONS + //////////////////// + + Signature not_sig = "not($value)"; + BUILT_IN(sass_not) + { + return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); + } + + Signature if_sig = "if($condition, $if-true, $if-false)"; + // BUILT_IN(sass_if) + // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } + BUILT_IN(sass_if) + { + Expand expand(ctx, &d_env, &selector_stack); + Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); + bool is_true = !cond->is_false(); + Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); + res = res->perform(&expand.eval); + res->set_delayed(false); // clone? + return res.detach(); + } + + ////////////////////////// + // MISCELLANEOUS FUNCTIONS + ////////////////////////// + + // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) + // unquoted_string(value.to_sass) + + Signature inspect_sig = "inspect($value)"; + BUILT_IN(inspect) + { + Expression_Ptr v = ARG("$value", Expression); + if (v->concrete_type() == Expression::NULL_VAL) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); + } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); + } else if (v->concrete_type() == Expression::STRING) { + return v; + } else { + // ToDo: fix to_sass for nested parentheses + Sass_Output_Style old_style; + old_style = ctx.c_options.output_style; + ctx.c_options.output_style = TO_SASS; + Emitter emitter(ctx.c_options); + Inspect i(emitter); + i.in_declaration = false; + v->perform(&i); + ctx.c_options.output_style = old_style; + return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); + } + // return v; + } + Signature selector_nest_sig = "selector-nest($selectors...)"; + BUILT_IN(selector_nest) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Obj str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector exploded; + selector_stack.push_back(result); + Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); + selector_stack.pop_back(); + for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { + exploded.push_back((*rv)[m]); + } + result->elements(exploded); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_append_sig = "selector-append($selectors...)"; + BUILT_IN(selector_append) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-append'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector newElements; + + // For every COMPLEX_SELECTOR in `result` + // For every COMPLEX_SELECTOR in `child` + // let parentSeqClone equal a copy of result->elements[i] + // let childSeq equal child->elements[j] + // Append all of childSeq head elements into parentSeqClone + // Set the innermost tail of parentSeqClone, to childSeq's tail + // Replace result->elements with newElements + for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { + for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { + Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); + Complex_Selector_Obj childSeq = (*child)[j]; + Complex_Selector_Obj base = childSeq->tail(); + + // Must be a simple sequence + if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, traces); + } + + // Cannot be a Universal selector + Element_Selector_Obj pType = Cast(childSeq->head()->first()); + if(pType && pType->name() == "*") { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, traces); + } + + // TODO: Add check for namespace stuff + + // append any selectors in childSeq's head + parentSeqClone->innermost()->head()->concat(base->head()); + + // Set parentSeqClone new tail + parentSeqClone->innermost()->tail( base->tail() ); + + newElements.push_back(parentSeqClone); + } + } + + result->elements(newElements); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; + BUILT_IN(selector_unify) + { + Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); + Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); + + Selector_List_Obj result = selector1->unify_with(selector2); + Listize listize; + return result->perform(&listize); + } + + Signature simple_selectors_sig = "simple-selectors($selector)"; + BUILT_IN(simple_selectors) + { + Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); + + List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Simple_Selector_Obj ss = (*sel)[i]; + std::string ss_string = ss->to_string() ; + + l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + } + + return l; + } + + Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; + BUILT_IN(selector_extend) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); + Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); + + Subset_Map subset_map; + extender->populate_extends(extendee, subset_map); + Extend extend(subset_map); + + Selector_List_Obj result = extend.extendSelectorList(selector, false); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; + BUILT_IN(selector_replace) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); + Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); + Subset_Map subset_map; + replacement->populate_extends(original, subset_map); + Extend extend(subset_map); + + Selector_List_Obj result = extend.extendSelectorList(selector, true); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_parse_sig = "selector-parse($selector)"; + BUILT_IN(selector_parse) + { + Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + + Listize listize; + return sel->perform(&listize); + } + + Signature is_superselector_sig = "is-superselector($super, $sub)"; + BUILT_IN(is_superselector) + { + Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); + Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); + bool result = sel_sup->is_superselector_of(sel_sub); + return SASS_MEMORY_NEW(Boolean, pstate, result); + } + + Signature unique_id_sig = "unique-id()"; + BUILT_IN(unique_id) + { + std::stringstream ss; + std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 + uint_fast32_t distributed = static_cast(distributor(rand)); + ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; + return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); + } + + Signature is_bracketed_sig = "is-bracketed($list)"; + BUILT_IN(is_bracketed) + { + Value_Obj value = ARG("$list", Value); + List_Obj list = Cast(value); + return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); + } + + Signature content_exists_sig = "content-exists()"; + BUILT_IN(content_exists) + { + if (!d_env.has_global("is_in_mixin")) { + error("Cannot call content-exists() except within a mixin.", pstate, traces); + } + return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); + } + + Signature get_function_sig = "get-function($name, $css: false)"; + BUILT_IN(get_function) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + std::string full_name = name + "[f]"; + + Boolean_Obj css = ARG("$css", Boolean); + if (!css->is_false()) { + Definition_Ptr def = SASS_MEMORY_NEW(Definition, + pstate, + name, + SASS_MEMORY_NEW(Parameters, pstate), + SASS_MEMORY_NEW(Block, pstate, 0, false), + Definition::FUNCTION); + return SASS_MEMORY_NEW(Function, pstate, def, true); + } + + + if (!d_env.has_global(full_name)) { + error("Function not found: " + name, pstate, traces); + } + + Definition_Ptr def = Cast(d_env[full_name]); + return SASS_MEMORY_NEW(Function, pstate, def, false); + } + } +} diff --git a/src/libsass/implementations.md b/src/libsass/implementations.md new file mode 100644 index 000000000..5239adcde --- /dev/null +++ b/src/libsass/implementations.md @@ -0,0 +1,65 @@ +There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. + +### C +* [sassc](https://github.com/hcatlin/sassc) + +### Crystal +* [sass.cr](https://github.com/straight-shoota/sass.cr) + +### Elixir +* [sass.ex](https://github.com/scottdavis/sass.ex) + +### Go +* [go-libsass](https://github.com/wellington/go-libsass) +* [go_sass](https://github.com/suapapa/go_sass) +* [go-sass](https://github.com/SamWhited/go-sass) + +### Haskell +* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) +* [hSass](https://github.com/jakubfijalkowski/hsass) + +### Java +* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) +* [jsass](https://github.com/bit3/jsass) + +### JavaScript +* [sass.js](https://github.com/medialize/sass.js) + +### Lua +* [lua-sass](https://github.com/craigbarnes/lua-sass) + +### .NET +* [libsass-net](https://github.com/darrenkopp/libsass-net) +* [NSass](https://github.com/TBAPI-0KA/NSass) +* [Sass.Net](https://github.com/andyalm/Sass.Net) +* [SharpScss](https://github.com/xoofx/SharpScss) +* [LibSassHost](https://github.com/Taritsyn/LibSassHost) + +### Nim +* [nim-sass](https://github.com/zacharycarter/nim-sass) + +### node.js +* [node-sass](https://github.com/sass/node-sass) + +### Perl +* [CSS::Sass](https://github.com/caldwell/CSS-Sass) +* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) + +### PHP +* [sassphp](https://github.com/sensational/sassphp) +* [php-sass](https://github.com/lesstif/php-sass) + +### Python +* [libsass-python](https://github.com/dahlia/libsass-python) +* [SassPython](https://github.com/marianoguerra/SassPython) +* [pylibsass](https://github.com/rsenk330/pylibsass) +* [python-scss](https://github.com/pistolero/python-scss) + +### Ruby +* [sassruby](https://github.com/hcatlin/sassruby) + +### Scala +* [Sass-Scala](https://github.com/kkung/Sass-Scala) + +### Tcl +* [tclsass](https://github.com/flightaware/tclsass) diff --git a/src/libsass/inspect.cpp b/src/libsass/inspect.cpp new file mode 100644 index 000000000..b4a66fab8 --- /dev/null +++ b/src/libsass/inspect.cpp @@ -0,0 +1,1138 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "inspect.hpp" +#include "context.hpp" +#include "listize.hpp" +#include "color_maps.hpp" +#include "utf8/checked.h" + +namespace Sass { + + Inspect::Inspect(const Emitter& emi) + : Emitter(emi) + { } + Inspect::~Inspect() { } + + // statements + void Inspect::operator()(Block_Ptr block) + { + if (!block->is_root()) { + add_open_mapping(block); + append_scope_opener(); + } + if (output_style() == NESTED) indentation += block->tabs(); + for (size_t i = 0, L = block->length(); i < L; ++i) { + (*block)[i]->perform(this); + } + if (output_style() == NESTED) indentation -= block->tabs(); + if (!block->is_root()) { + append_scope_closer(); + add_close_mapping(block); + } + + } + + void Inspect::operator()(Ruleset_Ptr ruleset) + { + if (ruleset->selector()) { + opt.in_selector = true; + ruleset->selector()->perform(this); + opt.in_selector = false; + } + if (ruleset->block()) { + ruleset->block()->perform(this); + } + } + + void Inspect::operator()(Keyframe_Rule_Ptr rule) + { + if (rule->name()) rule->name()->perform(this); + if (rule->block()) rule->block()->perform(this); + } + + void Inspect::operator()(Bubble_Ptr bubble) + { + append_indentation(); + append_token("::BUBBLE", bubble); + append_scope_opener(); + bubble->node()->perform(this); + append_scope_closer(); + } + + void Inspect::operator()(Media_Block_Ptr media_block) + { + append_indentation(); + append_token("@media", media_block); + append_mandatory_space(); + in_media_block = true; + media_block->media_queries()->perform(this); + in_media_block = false; + media_block->block()->perform(this); + } + + void Inspect::operator()(Supports_Block_Ptr feature_block) + { + append_indentation(); + append_token("@supports", feature_block); + append_mandatory_space(); + feature_block->condition()->perform(this); + feature_block->block()->perform(this); + } + + void Inspect::operator()(At_Root_Block_Ptr at_root_block) + { + append_indentation(); + append_token("@at-root ", at_root_block); + append_mandatory_space(); + if(at_root_block->expression()) at_root_block->expression()->perform(this); + if(at_root_block->block()) at_root_block->block()->perform(this); + } + + void Inspect::operator()(Directive_Ptr at_rule) + { + append_indentation(); + append_token(at_rule->keyword(), at_rule); + if (at_rule->selector()) { + append_mandatory_space(); + bool was_wrapped = in_wrapped; + in_wrapped = true; + at_rule->selector()->perform(this); + in_wrapped = was_wrapped; + } + if (at_rule->value()) { + append_mandatory_space(); + at_rule->value()->perform(this); + } + if (at_rule->block()) { + at_rule->block()->perform(this); + } + else { + append_delimiter(); + } + } + + void Inspect::operator()(Declaration_Ptr dec) + { + if (dec->value()->concrete_type() == Expression::NULL_VAL) return; + bool was_decl = in_declaration; + in_declaration = true; + LOCAL_FLAG(in_custom_property, dec->is_custom_property()); + + if (output_style() == NESTED) + indentation += dec->tabs(); + append_indentation(); + if (dec->property()) + dec->property()->perform(this); + append_colon_separator(); + + if (dec->value()->concrete_type() == Expression::SELECTOR) { + Listize listize; + Expression_Obj ls = dec->value()->perform(&listize); + ls->perform(this); + } else { + dec->value()->perform(this); + } + + if (dec->is_important()) { + append_optional_space(); + append_string("!important"); + } + append_delimiter(); + if (output_style() == NESTED) + indentation -= dec->tabs(); + in_declaration = was_decl; + } + + void Inspect::operator()(Assignment_Ptr assn) + { + append_token(assn->variable(), assn); + append_colon_separator(); + assn->value()->perform(this); + if (assn->is_default()) { + append_optional_space(); + append_string("!default"); + } + append_delimiter(); + } + + void Inspect::operator()(Import_Ptr import) + { + if (!import->urls().empty()) { + append_token("@import", import); + append_mandatory_space(); + + import->urls().front()->perform(this); + if (import->urls().size() == 1) { + if (import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + for (size_t i = 1, S = import->urls().size(); i < S; ++i) { + append_mandatory_linefeed(); + append_token("@import", import); + append_mandatory_space(); + + import->urls()[i]->perform(this); + if (import->urls().size() - 1 == i) { + if (import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + } + } + } + + void Inspect::operator()(Import_Stub_Ptr import) + { + append_indentation(); + append_token("@import", import); + append_mandatory_space(); + append_string(import->imp_path()); + append_delimiter(); + } + + void Inspect::operator()(Warning_Ptr warning) + { + append_indentation(); + append_token("@warn", warning); + append_mandatory_space(); + warning->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Error_Ptr error) + { + append_indentation(); + append_token("@error", error); + append_mandatory_space(); + error->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Debug_Ptr debug) + { + append_indentation(); + append_token("@debug", debug); + append_mandatory_space(); + debug->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Comment_Ptr comment) + { + in_comment = true; + comment->text()->perform(this); + in_comment = false; + } + + void Inspect::operator()(If_Ptr cond) + { + append_indentation(); + append_token("@if", cond); + append_mandatory_space(); + cond->predicate()->perform(this); + cond->block()->perform(this); + if (cond->alternative()) { + append_optional_linefeed(); + append_indentation(); + append_string("else"); + cond->alternative()->perform(this); + } + } + + void Inspect::operator()(For_Ptr loop) + { + append_indentation(); + append_token("@for", loop); + append_mandatory_space(); + append_string(loop->variable()); + append_string(" from "); + loop->lower_bound()->perform(this); + append_string(loop->is_inclusive() ? " through " : " to "); + loop->upper_bound()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Each_Ptr loop) + { + append_indentation(); + append_token("@each", loop); + append_mandatory_space(); + append_string(loop->variables()[0]); + for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { + append_comma_separator(); + append_string(loop->variables()[i]); + } + append_string(" in "); + loop->list()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(While_Ptr loop) + { + append_indentation(); + append_token("@while", loop); + append_mandatory_space(); + loop->predicate()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Return_Ptr ret) + { + append_indentation(); + append_token("@return", ret); + append_mandatory_space(); + ret->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Extension_Ptr extend) + { + append_indentation(); + append_token("@extend", extend); + append_mandatory_space(); + extend->selector()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Definition_Ptr def) + { + append_indentation(); + if (def->type() == Definition::MIXIN) { + append_token("@mixin", def); + append_mandatory_space(); + } else { + append_token("@function", def); + append_mandatory_space(); + } + append_string(def->name()); + def->parameters()->perform(this); + def->block()->perform(this); + } + + void Inspect::operator()(Mixin_Call_Ptr call) + { + append_indentation(); + append_token("@include", call); + append_mandatory_space(); + append_string(call->name()); + if (call->arguments()) { + call->arguments()->perform(this); + } + if (call->block()) { + append_optional_space(); + call->block()->perform(this); + } + if (!call->block()) append_delimiter(); + } + + void Inspect::operator()(Content_Ptr content) + { + append_indentation(); + append_token("@content", content); + append_delimiter(); + } + + void Inspect::operator()(Map_Ptr map) + { + if (output_style() == TO_SASS && map->empty()) { + append_string("()"); + return; + } + if (map->empty()) return; + if (map->is_invisible()) return; + bool items_output = false; + append_string("("); + for (auto key : map->keys()) { + if (items_output) append_comma_separator(); + key->perform(this); + append_colon_separator(); + LOCAL_FLAG(in_space_array, true); + LOCAL_FLAG(in_comma_array, true); + map->at(key)->perform(this); + items_output = true; + } + append_string(")"); + } + + std::string Inspect::lbracket(List_Ptr list) { + return list->is_bracketed() ? "[" : "("; + } + + std::string Inspect::rbracket(List_Ptr list) { + return list->is_bracketed() ? "]" : ")"; + } + + void Inspect::operator()(List_Ptr list) + { + if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { + append_string(lbracket(list)); + append_string(rbracket(list)); + return; + } + std::string sep(list->separator() == SASS_SPACE ? " " : ","); + if ((output_style() != COMPRESSED) && sep == ",") sep += " "; + else if (in_media_block && sep != " ") sep += " "; // verified + if (list->empty()) return; + bool items_output = false; + + bool was_space_array = in_space_array; + bool was_comma_array = in_comma_array; + // if the list is bracketed, always include the left bracket + if (list->is_bracketed()) { + append_string(lbracket(list)); + } + // probably ruby sass eqivalent of element_needs_parens + else if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(lbracket(list)); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(lbracket(list)); + } + + if (list->separator() == SASS_SPACE) in_space_array = true; + else if (list->separator() == SASS_COMMA) in_comma_array = true; + + for (size_t i = 0, L = list->size(); i < L; ++i) { + if (list->separator() == SASS_HASH) + { sep[0] = i % 2 ? ':' : ','; } + Expression_Obj list_item = list->at(i); + if (output_style() != TO_SASS) { + if (list_item->is_invisible()) { + // this fixes an issue with "" in a list + if (!Cast(list_item)) { + continue; + } + } + } + if (items_output) { + append_string(sep); + } + if (items_output && sep != " ") + append_optional_space(); + list_item->perform(this); + items_output = true; + } + + in_comma_array = was_comma_array; + in_space_array = was_space_array; + + // if the list is bracketed, always include the right bracket + if (list->is_bracketed()) { + if (list->separator() == SASS_COMMA && list->size() == 1) { + append_string(","); + } + append_string(rbracket(list)); + } + // probably ruby sass eqivalent of element_needs_parens + else if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(","); + append_string(rbracket(list)); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(rbracket(list)); + } + + } + + void Inspect::operator()(Binary_Expression_Ptr expr) + { + expr->left()->perform(this); + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_before + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + + )) append_string(" "); + switch (expr->optype()) { + case Sass_OP::AND: append_string("&&"); break; + case Sass_OP::OR: append_string("||"); break; + case Sass_OP::EQ: append_string("=="); break; + case Sass_OP::NEQ: append_string("!="); break; + case Sass_OP::GT: append_string(">"); break; + case Sass_OP::GTE: append_string(">="); break; + case Sass_OP::LT: append_string("<"); break; + case Sass_OP::LTE: append_string("<="); break; + case Sass_OP::ADD: append_string("+"); break; + case Sass_OP::SUB: append_string("-"); break; + case Sass_OP::MUL: append_string("*"); break; + case Sass_OP::DIV: append_string("/"); break; + case Sass_OP::MOD: append_string("%"); break; + default: break; // shouldn't get here + } + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_after + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + )) append_string(" "); + expr->right()->perform(this); + } + + void Inspect::operator()(Unary_Expression_Ptr expr) + { + if (expr->optype() == Unary_Expression::PLUS) append_string("+"); + else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); + else append_string("-"); + expr->operand()->perform(this); + } + + void Inspect::operator()(Function_Call_Ptr call) + { + append_token(call->name(), call); + call->arguments()->perform(this); + } + + void Inspect::operator()(Function_Call_Schema_Ptr call) + { + call->name()->perform(this); + call->arguments()->perform(this); + } + + void Inspect::operator()(Variable_Ptr var) + { + append_token(var->name(), var); + } + + void Inspect::operator()(Number_Ptr n) + { + + std::string res; + + // reduce units + n->reduce(); + + // check if the fractional part of the value equals to zero + // neat trick from http://stackoverflow.com/a/1521682/1550314 + // double int_part; bool is_int = modf(value, &int_part) == 0.0; + + // this all cannot be done with one run only, since fixed + // output differs from normal output and regular output + // can contain scientific notation which we do not want! + + // first sample + std::stringstream ss; + ss.precision(12); + ss << n->value(); + + // check if we got scientific notation in result + if (ss.str().find_first_of("e") != std::string::npos) { + ss.clear(); ss.str(std::string()); + ss.precision(std::max(12, opt.precision)); + ss << std::fixed << n->value(); + } + + std::string tmp = ss.str(); + size_t pos_point = tmp.find_first_of(".,"); + size_t pos_fract = tmp.find_last_not_of("0"); + bool is_int = pos_point == pos_fract || + pos_point == std::string::npos; + + // reset stream for another run + ss.clear(); ss.str(std::string()); + + // take a shortcut for integers + if (is_int) + { + ss.precision(0); + ss << std::fixed << n->value(); + res = std::string(ss.str()); + } + // process floats + else + { + // do we have have too much precision? + if (pos_fract < opt.precision + pos_point) + { ss.precision((int)(pos_fract - pos_point)); } + else { ss.precision(opt.precision); } + // round value again + ss << std::fixed << n->value(); + res = std::string(ss.str()); + // maybe we truncated up to decimal point + size_t pos = res.find_last_not_of("0"); + // handle case where we have a "0" + if (pos == std::string::npos) { + res = "0.0"; + } else { + bool at_dec_point = res[pos] == '.' || + res[pos] == ','; + // don't leave a blank point + if (at_dec_point) ++ pos; + res.resize (pos + 1); + } + } + + // some final cosmetics + if (res == "0.0") res = "0"; + else if (res == "") res = "0"; + else if (res == "-0") res = "0"; + else if (res == "-0.0") res = "0"; + else if (opt.output_style == COMPRESSED) + { + // check if handling negative nr + size_t off = res[0] == '-' ? 1 : 0; + // remove leading zero from floating point in compressed mode + if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + } + + // add unit now + res += n->unit(); + + // output the final token + append_token(res, n); + } + + // helper function for serializing colors + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + void Inspect::operator()(Color_Ptr c) + { + // output the final token + std::stringstream ss; + + // original color name + // maybe an unknown token + std::string name = c->disp(); + + if (opt.in_selector && name != "") { + append_token(name, c); + return; + } + + // resolved color + std::string res_name = name; + + double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); + double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); + double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); + double a = cap_channel<1> (c->a()); + + // get color from given name (if one was given at all) + if (name != "" && name_to_color(name)) { + Color_Ptr_Const n = name_to_color(name); + r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); + g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); + b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); + a = cap_channel<1> (n->a()); + } + // otherwise get the possible resolved color name + else { + double numval = r * 0x10000 + g * 0x100 + b; + if (color_to_name(numval)) + res_name = color_to_name(numval); + } + + std::stringstream hexlet; + // dart sass compressed all colors in regular css always + // ruby sass and libsass does it only when not delayed + // since color math is going to be removed, this can go too + bool compressed = opt.output_style == COMPRESSED; + hexlet << '#' << std::setw(1) << std::setfill('0'); + // create a short color hexlet if there is any need for it + if (compressed && is_color_doublet(r, g, b) && a == 1) { + hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); + } else { + hexlet << std::hex << std::setw(2) << static_cast(r); + hexlet << std::hex << std::setw(2) << static_cast(g); + hexlet << std::hex << std::setw(2) << static_cast(b); + } + + if (compressed && !c->is_delayed()) name = ""; + if (opt.output_style == INSPECT && a >= 1) { + append_token(hexlet.str(), c); + return; + } + + // retain the originally specified color definition if unchanged + if (name != "") { + ss << name; + } + else if (a >= 1) { + if (res_name != "") { + if (compressed && hexlet.str().size() < res_name.size()) { + ss << hexlet.str(); + } else { + ss << res_name; + } + } + else { + ss << hexlet.str(); + } + } + else { + ss << "rgba("; + ss << static_cast(r) << ","; + if (!compressed) ss << " "; + ss << static_cast(g) << ","; + if (!compressed) ss << " "; + ss << static_cast(b) << ","; + if (!compressed) ss << " "; + ss << a << ')'; + } + + append_token(ss.str(), c); + + } + + void Inspect::operator()(Boolean_Ptr b) + { + // output the final token + append_token(b->value() ? "true" : "false", b); + } + + void Inspect::operator()(String_Schema_Ptr ss) + { + // Evaluation should turn these into String_Constants, + // so this method is only for inspection purposes. + for (size_t i = 0, L = ss->length(); i < L; ++i) { + if ((*ss)[i]->is_interpolant()) append_string("#{"); + (*ss)[i]->perform(this); + if ((*ss)[i]->is_interpolant()) append_string("}"); + } + } + + void Inspect::operator()(String_Constant_Ptr s) + { + append_token(s->value(), s); + } + + void Inspect::operator()(String_Quoted_Ptr s) + { + if (const char q = s->quote_mark()) { + append_token(quote(s->value(), q), s); + } else { + append_token(s->value(), s); + } + } + + void Inspect::operator()(Custom_Error_Ptr e) + { + append_token(e->message(), e); + } + + void Inspect::operator()(Custom_Warning_Ptr w) + { + append_token(w->message(), w); + } + + void Inspect::operator()(Supports_Operator_Ptr so) + { + + if (so->needs_parens(so->left())) append_string("("); + so->left()->perform(this); + if (so->needs_parens(so->left())) append_string(")"); + + if (so->operand() == Supports_Operator::AND) { + append_mandatory_space(); + append_token("and", so); + append_mandatory_space(); + } else if (so->operand() == Supports_Operator::OR) { + append_mandatory_space(); + append_token("or", so); + append_mandatory_space(); + } + + if (so->needs_parens(so->right())) append_string("("); + so->right()->perform(this); + if (so->needs_parens(so->right())) append_string(")"); + } + + void Inspect::operator()(Supports_Negation_Ptr sn) + { + append_token("not", sn); + append_mandatory_space(); + if (sn->needs_parens(sn->condition())) append_string("("); + sn->condition()->perform(this); + if (sn->needs_parens(sn->condition())) append_string(")"); + } + + void Inspect::operator()(Supports_Declaration_Ptr sd) + { + append_string("("); + sd->feature()->perform(this); + append_string(": "); + sd->value()->perform(this); + append_string(")"); + } + + void Inspect::operator()(Supports_Interpolation_Ptr sd) + { + sd->value()->perform(this); + } + + void Inspect::operator()(Media_Query_Ptr mq) + { + size_t i = 0; + if (mq->media_type()) { + if (mq->is_negated()) append_string("not "); + else if (mq->is_restricted()) append_string("only "); + mq->media_type()->perform(this); + } + else { + (*mq)[i++]->perform(this); + } + for (size_t L = mq->length(); i < L; ++i) { + append_string(" and "); + (*mq)[i]->perform(this); + } + } + + void Inspect::operator()(Media_Query_Expression_Ptr mqe) + { + if (mqe->is_interpolated()) { + mqe->feature()->perform(this); + } + else { + append_string("("); + mqe->feature()->perform(this); + if (mqe->value()) { + append_string(": "); // verified + mqe->value()->perform(this); + } + append_string(")"); + } + } + + void Inspect::operator()(At_Root_Query_Ptr ae) + { + if (ae->feature()) { + append_string("("); + ae->feature()->perform(this); + if (ae->value()) { + append_colon_separator(); + ae->value()->perform(this); + } + append_string(")"); + } + } + + void Inspect::operator()(Function_Ptr f) + { + append_token("get-function", f); + append_string("("); + append_string(quote(f->name())); + append_string(")"); + } + + void Inspect::operator()(Null_Ptr n) + { + // output the final token + append_token("null", n); + } + + // parameters and arguments + void Inspect::operator()(Parameter_Ptr p) + { + append_token(p->name(), p); + if (p->default_value()) { + append_colon_separator(); + p->default_value()->perform(this); + } + else if (p->is_rest_parameter()) { + append_string("..."); + } + } + + void Inspect::operator()(Parameters_Ptr p) + { + append_string("("); + if (!p->empty()) { + (*p)[0]->perform(this); + for (size_t i = 1, L = p->length(); i < L; ++i) { + append_comma_separator(); + (*p)[i]->perform(this); + } + } + append_string(")"); + } + + void Inspect::operator()(Argument_Ptr a) + { + if (!a->name().empty()) { + append_token(a->name(), a); + append_colon_separator(); + } + if (!a->value()) return; + // Special case: argument nulls can be ignored + if (a->value()->concrete_type() == Expression::NULL_VAL) { + return; + } + if (a->value()->concrete_type() == Expression::STRING) { + String_Constant_Ptr s = Cast(a->value()); + if (s) s->perform(this); + } else { + a->value()->perform(this); + } + if (a->is_rest_argument()) { + append_string("..."); + } + } + + void Inspect::operator()(Arguments_Ptr a) + { + append_string("("); + if (!a->empty()) { + (*a)[0]->perform(this); + for (size_t i = 1, L = a->length(); i < L; ++i) { + append_string(", "); // verified + // Sass Bug? append_comma_separator(); + (*a)[i]->perform(this); + } + } + append_string(")"); + } + + void Inspect::operator()(Selector_Schema_Ptr s) + { + opt.in_selector = true; + s->contents()->perform(this); + opt.in_selector = false; + } + + void Inspect::operator()(Parent_Selector_Ptr p) + { + if (p->is_real_parent_ref()) append_string("&"); + } + + void Inspect::operator()(Placeholder_Selector_Ptr s) + { + append_token(s->name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + + } + + void Inspect::operator()(Element_Selector_Ptr s) + { + append_token(s->ns_name(), s); + } + + void Inspect::operator()(Class_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Id_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Attribute_Selector_Ptr s) + { + append_string("["); + add_open_mapping(s); + append_token(s->ns_name(), s); + if (!s->matcher().empty()) { + append_string(s->matcher()); + if (s->value() && *s->value()) { + s->value()->perform(this); + } + } + add_close_mapping(s); + if (s->modifier() != 0) { + append_mandatory_space(); + append_char(s->modifier()); + } + append_string("]"); + } + + void Inspect::operator()(Pseudo_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->expression()) { + append_string("("); + s->expression()->perform(this); + append_string(")"); + } + } + + void Inspect::operator()(Wrapped_Selector_Ptr s) + { + if (s->name() == " ") { + append_string(""); + } else { + bool was = in_wrapped; + in_wrapped = true; + append_token(s->name(), s); + append_string("("); + bool was_comma_array = in_comma_array; + in_comma_array = false; + s->selector()->perform(this); + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; + } + } + + void Inspect::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0, L = s->length(); i < L; ++i) { + (*s)[i]->perform(this); + } + if (s->has_line_break()) { + if (output_style() != COMPACT) { + append_optional_linefeed(); + } + } + } + + void Inspect::operator()(Complex_Selector_Ptr c) + { + Compound_Selector_Obj head = c->head(); + Complex_Selector_Obj tail = c->tail(); + Complex_Selector::Combinator comb = c->combinator(); + + if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { + if (tail) tail->perform(this); + return; + } + + if (c->has_line_feed()) { + if (!(c->has_parent_ref())) { + append_optional_linefeed(); + append_indentation(); + } + } + + if (head && head->length() != 0) head->perform(this); + bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); + bool is_tail = head && !head->is_empty_reference() && tail; + if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; + + switch (comb) { + case Complex_Selector::ANCESTOR_OF: + if (is_tail) append_mandatory_space(); + break; + case Complex_Selector::PARENT_OF: + append_optional_space(); + append_string(">"); + append_optional_space(); + break; + case Complex_Selector::ADJACENT_TO: + append_optional_space(); + append_string("+"); + append_optional_space(); + break; + case Complex_Selector::REFERENCE: + append_mandatory_space(); + append_string("/"); + c->reference()->perform(this); + append_string("/"); + append_mandatory_space(); + break; + case Complex_Selector::PRECEDES: + if (is_empty) append_optional_space(); + else append_mandatory_space(); + append_string("~"); + if (tail) append_mandatory_space(); + else append_optional_space(); + break; + default: break; + } + if (tail && comb != Complex_Selector::ANCESTOR_OF) { + if (c->has_line_break()) append_optional_linefeed(); + } + if (tail) tail->perform(this); + if (!tail && c->has_line_break()) { + if (output_style() == COMPACT) { + append_mandatory_space(); + } + } + } + + void Inspect::operator()(Selector_List_Ptr g) + { + + if (g->empty()) { + if (output_style() == TO_SASS) { + append_token("()", g); + } + return; + } + + + bool was_comma_array = in_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { + append_string("("); + } + else if (!in_declaration && in_comma_array) { + append_string("("); + } + + if (in_declaration) in_comma_array = true; + + for (size_t i = 0, L = g->length(); i < L; ++i) { + if (!in_wrapped && i == 0) append_indentation(); + if ((*g)[i] == 0) continue; + schedule_mapping(g->at(i)->last()); + // add_open_mapping((*g)[i]->last()); + (*g)[i]->perform(this); + // add_close_mapping((*g)[i]->last()); + if (i < L - 1) { + scheduled_space = 0; + append_comma_separator(); + } + } + + in_comma_array = was_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { + append_string(",)"); + } + else if (!in_declaration && in_comma_array) { + append_string(")"); + } + + } + + void Inspect::fallback_impl(AST_Node_Ptr n) + { + } + +} diff --git a/src/libsass/operators.cpp b/src/libsass/operators.cpp new file mode 100644 index 000000000..02e303738 --- /dev/null +++ b/src/libsass/operators.cpp @@ -0,0 +1,240 @@ +#include "sass.hpp" +#include "operators.hpp" + +namespace Sass { + + namespace Operators { + + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } // x/0 checked by caller + + inline double mod(double x, double y) { // x/0 checked by caller + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } else { + return std::fmod(x, y); + } + } + + typedef double (*bop)(double, double); + bop ops[Sass_OP::NUM_OPS] = { + 0, 0, // and, or + 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte + add, sub, mul, div, mod + }; + + /* static function, has no pstate or traces */ + bool eq(Expression_Obj lhs, Expression_Obj rhs) + { + // operation is undefined if one is not a number + if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); + // use compare operator from ast node + return *lhs == *rhs; + } + + /* static function, throws OperationError, has no pstate or traces */ + bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) + { + // can only compare numbers!? + Number_Obj l = Cast(lhs); + Number_Obj r = Cast(rhs); + // operation is undefined if one is not a number + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); + // use compare operator from ast node + return *l < *r; + } + + /* static functions, throws OperationError, has no pstate or traces */ + bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } + bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } + bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } + bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } + bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + enum Sass_OP op = operand.operand; + + String_Quoted_Ptr lqstr = Cast(&lhs); + String_Quoted_Ptr rqstr = Cast(&rhs); + + std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); + std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); + + if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + + std::string sep; + switch (op) { + case Sass_OP::ADD: sep = ""; break; + case Sass_OP::SUB: sep = "-"; break; + case Sass_OP::DIV: sep = "/"; break; + case Sass_OP::EQ: sep = "=="; break; + case Sass_OP::NEQ: sep = "!="; break; + case Sass_OP::LT: sep = "<"; break; + case Sass_OP::GT: sep = ">"; break; + case Sass_OP::LTE: sep = "<="; break; + case Sass_OP::GTE: sep = ">="; break; + default: + throw Exception::UndefinedOperation(&lhs, &rhs, op); + break; + } + + if (op == Sass_OP::ADD) { + // create string that might be quoted on output (but do not unquote what we pass) + return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); + } + + // add whitespace around operator + // but only if result is not delayed + if (sep != "" && delayed == false) { + if (operand.ws_before) sep = " " + sep; + if (operand.ws_after) sep = sep + " "; + } + + if (op == Sass_OP::SUB || op == Sass_OP::DIV) { + if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); + if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); + } + + return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + if (lhs.a() != rhs.a()) { + throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); + } + if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rhs.r()), + ops[op](lhs.g(), rhs.g()), + ops[op](lhs.b(), rhs.b()), + lhs.a()); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + double rval = rhs.value(); + + if (op == Sass_OP::MOD && rval == 0) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); + } + + if (op == Sass_OP::DIV && rval == 0) { + std::string result(lval ? "Infinity" : "NaN"); + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + size_t l_n_units = lhs.numerators.size(); + size_t l_d_units = lhs.numerators.size(); + size_t r_n_units = rhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + // optimize out the most common and simplest case + if (l_n_units == r_n_units && l_d_units == r_d_units) { + if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { + if (lhs.numerators == rhs.numerators) { + if (lhs.denominators == rhs.denominators) { + Number_Ptr v = SASS_MEMORY_COPY(&lhs); + v->value(ops[op](lval, rval)); + return v; + } + } + } + } + + Number_Obj v = SASS_MEMORY_COPY(&lhs); + + if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerators = rhs.numerators; + v->denominators = rhs.denominators; + } + + if (op == Sass_OP::MUL) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->reduce(); + } + else if (op == Sass_OP::DIV) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->reduce(); + } + else { + Number ln(lhs), rn(rhs); + ln.reduce(); rn.reduce(); + double f(rn.convert_factor(ln)); + v->value(ops[op](lval, rn.value() * f)); + } + + v->pstate(pstate); + return v.detach(); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + switch (op) { + case Sass_OP::ADD: + case Sass_OP::MUL: { + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lval, rhs.r()), + ops[op](lval, rhs.g()), + ops[op](lval, rhs.b()), + rhs.a()); + } + case Sass_OP::SUB: + case Sass_OP::DIV: { + std::string color(rhs.to_string(opt)); + return SASS_MEMORY_NEW(String_Quoted, + pstate, + lhs.to_string(opt) + + sass_op_separator(op) + + color); + } + default: break; + } + throw Exception::UndefinedOperation(&lhs, &rhs, op); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double rval = rhs.value(); + if (op == Sass_OP::DIV && rval == 0) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rval), + ops[op](lhs.g(), rval), + ops[op](lhs.b(), rval), + lhs.a()); + } + + } + +} diff --git a/src/libsass/parser.cpp b/src/libsass/parser.cpp new file mode 100644 index 000000000..ee51d56b6 --- /dev/null +++ b/src/libsass/parser.cpp @@ -0,0 +1,3137 @@ +#include "sass.hpp" +#include "parser.hpp" +#include "file.hpp" +#include "inspect.hpp" +#include "constants.hpp" +#include "util.hpp" +#include "prelexer.hpp" +#include "color_maps.hpp" +#include "sass/functions.h" +#include "error_handling.hpp" + +// Notes about delayed: some ast nodes can have delayed evaluation so +// they can preserve their original semantics if needed. This is most +// prominently exhibited by the division operation, since it is not +// only a valid operation, but also a valid css statement (i.e. for +// fonts, as in `16px/24px`). When parsing lists and expression we +// unwrap single items from lists and other operations. A nested list +// must not be delayed, only the items of the first level sometimes +// are delayed (as with argument lists). To achieve this we need to +// pass status to the list parser, so this can be set correctly. +// Another case with delayed values are colors. In compressed mode +// only processed values get compressed (other are left as written). + +#include +#include +#include +#include + +namespace Sass { + using namespace Constants; + using namespace Prelexer; + + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate, traces); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate, traces); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = end ? end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + void Parser::advanceToNextToken() { + lex < css_comments >(false); + // advance to position + pstate += pstate.offset; + pstate.offset.column = 0; + pstate.offset.line = 0; + } + + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); + // ToDo: ruby sass errors on parent references + // ToDo: remap the source-map entries somehow + return p.parse_selector_list(false); + } + + bool Parser::peek_newline(const char* start) + { + return peek_linefeed(start ? start : position) + && ! peek_css>(start); + } + + Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + Parser p(ctx, pstate, traces); + p.source = source ? source : t.begin; + p.position = t.begin ? t.begin : p.source; + p.end = t.end ? t.end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + /* main entry point to parse root block */ + Block_Obj Parser::parse() + { + + // consume unicode BOM + read_bom(); + + // scan the input to find invalid utf8 sequences + const char* it = utf8::find_invalid(position, end); + + // report invalid utf8 + if (it != end) { + pstate += Offset::init(position, it); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); + } + + // create a block AST node to hold children + Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // check seems a bit esoteric but works + if (ctx.resources.size() == 1) { + // apply headers only on very first include + ctx.apply_custom_headers(root, path, pstate); + } + + // parse children nodes + block_stack.push_back(root); + parse_block_nodes(true); + block_stack.pop_back(); + + // update final position + root->update_pstate(pstate); + + if (position != end) { + css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); + } + + return root; + } + + + // convenience function for block parsing + // will create a new block ad-hoc for you + // this is the base block parsing function + Block_Obj Parser::parse_css_block(bool is_root) + { + + // parse comments before block + // lex < optional_css_comments >(); + + // lex mandatory opener or error out + if (!lex_css < exactly<'{'> >()) { + css_error("Invalid CSS", " after ", ": expected \"{\", was "); + } + // create new block and push to the selector stack + Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); + block_stack.push_back(block); + + if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + + if (!lex_css < exactly<'}'> >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + + // update for end position + // this seems to be done somewhere else + // but that fixed selector schema issue + // block->update_pstate(pstate); + + // parse comments after block + // lex < optional_css_comments >(); + + block_stack.pop_back(); + + return block; + } + + // convenience function for block parsing + // will create a new block ad-hoc for you + // also updates the `in_at_root` flag + Block_Obj Parser::parse_block(bool is_root) + { + return parse_css_block(is_root); + } + + // the main block parsing function + // parses stuff between `{` and `}` + bool Parser::parse_block_nodes(bool is_root) + { + + // loop until end of string + while (position < end) { + + // we should be able to refactor this + parse_block_comments(); + lex < css_whitespace >(); + + if (lex < exactly<';'> >()) continue; + if (peek < end_of_file >()) return true; + if (peek < exactly<'}'> >()) return true; + + if (parse_block_node(is_root)) continue; + + parse_block_comments(); + + if (lex_css < exactly<';'> >()) continue; + if (peek_css < end_of_file >()) return true; + if (peek_css < exactly<'}'> >()) return true; + + // illegal sass + return false; + } + // return success + return true; + } + + // parser for a single node in a block + // semicolons must be lexed beforehand + bool Parser::parse_block_node(bool is_root) { + + Block_Obj block = block_stack.back(); + + parse_block_comments(); + + // throw away white-space + // includes line comments + lex < css_whitespace >(); + + Lookahead lookahead_result; + + // also parse block comments + + // first parse everything that is allowed in functions + if (lex < variable >(true)) { block->append(parse_assignment()); } + else if (lex < kwd_err >(true)) { block->append(parse_error()); } + else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } + else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } + else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } + else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } + else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } + else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } + else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } + + // parse imports to process later + else if (lex < kwd_import >(true)) { + Scope parent = stack.empty() ? Scope::Rules : stack.back(); + if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { + if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 + error("Import directives may not be used within control directives or mixins."); + } + } + // this puts the parsed doc into sheets + // import stub will fetch this in expand + Import_Obj imp = parse_import(); + // if it is a url, we only add the statement + if (!imp->urls().empty()) block->append(imp); + // process all resources now (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + else if (lex < kwd_extend >(true)) { + Lookahead lookahead = lookahead_for_include(position); + if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); + Selector_List_Obj target; + if (!lookahead.has_interpolants) { + target = parse_selector_list(true); + } + else { + target = SASS_MEMORY_NEW(Selector_List, pstate); + target->schema(parse_selector_schema(lookahead.found, true)); + } + + block->append(SASS_MEMORY_NEW(Extension, pstate, target)); + } + + // selector may contain interpolations which need delayed evaluation + else if ( + !(lookahead_result = lookahead_for_selector(position)).error && + !lookahead_result.is_custom_property + ) + { + block->append(parse_ruleset(lookahead_result)); + } + + // parse multiple specific keyword directives + else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } + else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } + else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } + else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } + else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } + else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } + + // ignore the @charset directive for now + else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } + + // generic at keyword (keep last) + else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } + else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } + else if (lex< at_keyword >(true)) { block->append(parse_directive()); } + + else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { + lex< css_whitespace >(); + if (position >= end) return true; + css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); + } + // parse a declaration + else + { + // ToDo: how does it handle parse errors? + // maybe we are expected to parse something? + Declaration_Obj decl = parse_declaration(); + decl->tabs(indentation); + block->append(decl); + // maybe we have a "sub-block" + if (peek< exactly<'{'> >()) { + if (decl->is_indented()) ++ indentation; + // parse a propset that rides on the declaration's property + stack.push_back(Scope::Properties); + decl->block(parse_block()); + stack.pop_back(); + if (decl->is_indented()) -- indentation; + } + } + // something matched + return true; + } + // EO parse_block_nodes + + // parse imports inside the + Import_Obj Parser::parse_import() + { + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + std::vector> to_import; + bool first = true; + do { + while (lex< block_comment >()); + if (lex< quoted_string >()) { + to_import.push_back(std::pair(std::string(lexed), 0)); + } + else if (lex< uri_prefix >()) { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); + + if (lex< quoted_string >()) { + Expression_Obj quoted_url = parse_string(); + args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); + } + else if (String_Obj string_url = parse_url_function_argument()) { + args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); + } + else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { + Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); + args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); + } + else { + error("malformed URL"); + } + if (!lex< exactly<')'> >()) error("URI is missing ')'"); + to_import.push_back(std::pair("", result)); + } + else { + if (first) error("@import directive requires a url or quoted path"); + else error("expecting another url or quoted path in @import list"); + } + first = false; + } while (lex_css< exactly<','> >()); + + if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { + List_Obj import_queries = parse_media_queries(); + imp->import_queries(import_queries); + } + + for(auto location : to_import) { + if (location.second) { + imp->urls().push_back(location.second); + } + // check if custom importers want to take over the handling + else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { + // nobody wants it, so we do our import + ctx.import_url(imp, location.first, path); + } + } + + return imp; + } + + Definition_Obj Parser::parse_definition(Definition::Type which_type) + { + std::string which_str(lexed); + if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); + std::string name(Util::normalize_underscores(lexed)); + if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) + { error("Invalid function name \"" + name + "\"."); } + ParserState source_position_of_def = pstate; + Parameters_Obj params = parse_parameters(); + if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); + else stack.push_back(Scope::Function); + Block_Obj body = parse_block(); + stack.pop_back(); + return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); + } + + Parameters_Obj Parser::parse_parameters() + { + Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do { + if (peek< exactly<')'> >()) break; + params->append(parse_parameter()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + } + return params; + } + + Parameter_Obj Parser::parse_parameter() + { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); + } + while (lex< alternatives < spaces, block_comment > >()); + lex < variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState pos = pstate; + Expression_Obj val; + bool is_rest = false; + while (lex< alternatives < spaces, block_comment > >()); + if (lex< exactly<':'> >()) { // there's a default value + while (lex< block_comment >()); + val = parse_space_list(); + } + else if (lex< exactly< ellipsis > >()) { + is_rest = true; + } + return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); + } + + Arguments_Obj Parser::parse_arguments() + { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do { + if (peek< exactly<')'> >()) break; + args->append(parse_argument()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + return args; + } + + Argument_Obj Parser::parse_argument() + { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { + position += 2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + Argument_Obj arg; + if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { + lex_css< variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState p = pstate; + lex_css< exactly<':'> >(); + Expression_Obj val = parse_space_list(); + arg = SASS_MEMORY_NEW(Argument, p, val, name); + } + else { + bool is_arglist = false; + bool is_keyword = false; + Expression_Obj val = parse_space_list(); + List_Ptr l = Cast(val); + if (lex_css< exactly< ellipsis > >()) { + if (val->concrete_type() == Expression::MAP || ( + (l != NULL && l->separator() == SASS_HASH) + )) is_keyword = true; + else is_arglist = true; + } + arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); + } + return arg; + } + + Assignment_Obj Parser::parse_assignment() + { + std::string name(Util::normalize_underscores(lexed)); + ParserState var_source_position = pstate; + if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); + if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj val; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.has_interpolants && lookahead.found) { + val = parse_value_schema(lookahead.found); + } else { + val = parse_list(); + } + bool is_default = false; + bool is_global = false; + while (peek< alternatives < default_flag, global_flag > >()) { + if (lex< default_flag >()) is_default = true; + else if (lex< global_flag >()) is_global = true; + } + return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); + } + + // a ruleset connects a selector and a block + Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) + { + NESTING_GUARD(nestings); + // inherit is_root from parent block + Block_Obj parent = block_stack.back(); + bool is_root = parent && parent->is_root(); + // make sure to move up the the last position + lex < optional_css_whitespace >(false, true); + // create the connector object (add parts later) + Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); + // parse selector static or as schema to be evaluated later + if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + else { + Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); + list->schema(parse_selector_schema(lookahead.position, false)); + ruleset->selector(list); + } + // then parse the inner block + stack.push_back(Scope::Rules); + ruleset->block(parse_block()); + stack.pop_back(); + // update for end position + ruleset->update_pstate(pstate); + ruleset->block()->update_pstate(pstate); + // need this info for sanity checks + ruleset->is_root(is_root); + // return AST Node + return ruleset; + } + + // parse a selector schema that will be evaluated in the eval stage + // uses a string schema internally to do the actual schema handling + // in the eval stage we will be re-parse it into an actual selector + Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) + { + NESTING_GUARD(nestings); + // move up to the start + lex< optional_spaces >(); + const char* i = position; + // selector schema re-uses string schema implementation + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + // the selector schema is pretty much just a wrapper for the string schema + Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + selector_schema->connect_parent(chroot == false); + selector_schema->media_block(last_media_block); + + // process until end + while (i < end_of_selector) { + // try to parse mutliple interpolants + if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { + // accumulate the preceding segment if the position has advanced + if (i < p) { + std::string parsed(i, p); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + schema->append(str); + } + + // skip over all nested inner interpolations up to our own delimiter + const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); + // check if the interpolation never ends of only contains white-space (error out) + if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { + position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + // pass inner expression to the parser to resolve nested interpolations + pstate.add(p, p+2); + Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); + // set status on the list expression + interpolant->is_interpolant(true); + // schema->has_interpolants(true); + // add to the string schema + schema->append(interpolant); + // advance parser state + pstate.add(p+2, j); + // advance position + i = j; + } + // no more interpolants have been found + // add the last segment if there is one + else { + // make sure to add the last bits of the string up to the end (if any) + if (i < end_of_selector) { + std::string parsed(i, end_of_selector); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + i = end_of_selector; + schema->append(str); + } + // exit loop + } + } + // EO until eos + + // update position + position = i; + + // update for end position + selector_schema->update_pstate(pstate); + schema->update_pstate(pstate); + + after_token = before_token = pstate; + + // return parsed result + return selector_schema.detach(); + } + // EO parse_selector_schema + + void Parser::parse_charset_directive() + { + lex < + sequence < + quoted_string, + optional_spaces, + exactly <';'> + > + >(); + } + + // called after parsing `kwd_include_directive` + Mixin_Call_Obj Parser::parse_include_directive() + { + // lex identifier into `lexed` var + lex_identifier(); // may error out + // normalize underscores to hyphens + std::string name(Util::normalize_underscores(lexed)); + // create the initial mixin call object + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); + // parse mandatory arguments + call->arguments(parse_arguments()); + // parse optional block + if (peek < exactly <'{'> >()) { + call->block(parse_block()); + } + // return ast node + return call.detach(); + } + // EO parse_include_directive + + // parse a list of complex selectors + // this is the main entry point for most + Selector_List_Obj Parser::parse_selector_list(bool chroot) + { + bool reloop; + bool had_linefeed = false; + NESTING_GUARD(nestings); + Complex_Selector_Obj sel; + Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); + group->media_block(last_media_block); + + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + + do { + reloop = false; + + had_linefeed = had_linefeed || peek_newline(); + + if (peek_css< alternatives < class_char < selector_list_delims > > >()) + break; // in case there are superfluous commas at the end + + // now parse the complex selector + sel = parse_complex_selector(chroot); + + if (!sel) return group.detach(); + + sel->has_line_feed(had_linefeed); + + had_linefeed = false; + + while (peek_css< exactly<','> >()) + { + lex< css_comments >(false); + // consume everything up and including the comma separator + reloop = lex< exactly<','> >() != 0; + // remember line break (also between some commas) + had_linefeed = had_linefeed || peek_newline(); + // remember line break (also between some commas) + } + group->append(sel); + } + while (reloop); + while (lex_css< kwd_optional >()) { + group->is_optional(true); + } + // update for end position + group->update_pstate(pstate); + if (sel) sel->last()->has_line_break(false); + return group.detach(); + } + // EO parse_selector_list + + // a complex selector combines a compound selector with another + // complex selector, with one of four combinator operations. + // the compound selector (head) is optional, since the combinator + // can come first in the whole selector sequence (like `> DIV'). + Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) + { + + NESTING_GUARD(nestings); + String_Obj reference = 0; + lex < block_comment >(); + advanceToNextToken(); + Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); + + if (peek < end_of_file >()) return 0; + + // parse the left hand side + Compound_Selector_Obj lhs; + // special case if it starts with combinator ([+~>]) + if (!peek_css< class_char < selector_combinator_ops > >()) { + // parse the left hand side + lhs = parse_compound_selector(); + } + + + // parse combinator between lhs and rhs + Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; + if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; + else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; + else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; + else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { + // comments are allowed, but not spaces? + combinator = Complex_Selector::REFERENCE; + if (!lex < re_reference_combinator >()) return 0; + reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? + } + + if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; + + // lex < block_comment >(); + sel->head(lhs); + sel->combinator(combinator); + sel->media_block(last_media_block); + + if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); + // has linfeed after combinator? + sel->has_line_break(peek_newline()); + // sel->has_line_feed(has_line_feed); + + // check if we got the abort condition (ToDo: optimize) + if (!peek_css< class_char < complex_selector_delims > >()) { + // parse next selector in sequence + sel->tail(parse_complex_selector(true)); + } + + // add a parent selector if we are not in a root + // also skip adding parent ref if we only have refs + if (!sel->has_parent_ref() && !chroot) { + // create the objects to wrap parent selector reference + Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); + Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); + parent->media_block(last_media_block); + head->media_block(last_media_block); + // add simple selector + head->append(parent); + // selector may not have any head yet + if (!sel->head()) { sel->head(head); } + // otherwise we need to create a new complex selector and set the old one as its tail + else { + sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); + sel->media_block(last_media_block); + } + // peek for linefeed and remember result on head + // if (peek_newline()) head->has_line_break(true); + } + + sel->update_pstate(pstate); + // complex selector + return sel; + } + // EO parse_complex_selector + + // parse one compound selector, which is basically + // a list of simple selectors (directly adjacent) + // lex them exactly (without skipping white-space) + Compound_Selector_Obj Parser::parse_compound_selector() + { + // init an empty compound selector wrapper + Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); + seq->media_block(last_media_block); + + // skip initial white-space + lex< css_whitespace >(); + + // parse list + while (true) + { + // remove all block comments (don't skip white-space) + lex< delimited_by< slash_star, star_slash, false > >(false); + // parse functional + if (match < re_pseudo_selector >()) + { + seq->append(parse_simple_selector()); + } + // parse parent selector + else if (lex< exactly<'&'> >(false)) + { + // this produces a linefeed!? + seq->has_parent_reference(true); + seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); + // parent selector only allowed at start + // upcoming Sass may allow also trailing + if (seq->length() > 1) { + ParserState state(pstate); + Simple_Selector_Obj cur = (*seq)[seq->length()-1]; + Simple_Selector_Obj prev = (*seq)[seq->length()-2]; + std::string sel(prev->to_string({ NESTED, 5 })); + std::string found(cur->to_string({ NESTED, 5 })); + if (lex < identifier >()) { found += std::string(lexed); } + error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" + "\"" + found + "\" may only be used at the beginning of a compound selector.", state); + } + } + // parse type selector + else if (lex< re_type_selector >(false)) + { + seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); + } + // peek for abort conditions + else if (peek< spaces >()) break; + else if (peek< end_of_file >()) { break; } + else if (peek_css < class_char < selector_combinator_ops > >()) break; + else if (peek_css < class_char < complex_selector_delims > >()) break; + // otherwise parse another simple selector + else { + Simple_Selector_Obj sel = parse_simple_selector(); + if (!sel) return 0; + seq->append(sel); + } + } + + if (seq && !peek_css>>()) { + seq->has_line_break(peek_newline()); + } + + // EO while true + return seq; + + } + // EO parse_compound_selector + + Simple_Selector_Obj Parser::parse_simple_selector() + { + lex < css_comments >(false); + if (lex< class_name >()) { + return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); + } + else if (lex< id_name >()) { + return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); + } + else if (lex< alternatives < variable, number, static_reference_combinator > >()) { + return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); + } + else if (peek< pseudo_not >()) { + return parse_negated_selector(); + } + else if (peek< re_pseudo_selector >()) { + return parse_pseudo_selector(); + } + else if (peek< exactly<':'> >()) { + return parse_pseudo_selector(); + } + else if (lex < exactly<'['> >()) { + return parse_attribute_selector(); + } + else if (lex< placeholder >()) { + Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); + sel->media_block(last_media_block); + return sel; + } + else { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + // failed + return 0; + } + + Wrapped_Selector_Obj Parser::parse_negated_selector() + { + lex< pseudo_not >(); + std::string name(lexed); + ParserState nsource_position = pstate; + Selector_List_Obj negated = parse_selector_list(true); + if (!lex< exactly<')'> >()) { + error("negated selector is missing ')'"); + } + name.erase(name.size() - 1); + return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); + } + + // a pseudo selector often starts with one or two colons + // it can contain more selectors inside parentheses + Simple_Selector_Obj Parser::parse_pseudo_selector() { + if (lex< sequence< + optional < pseudo_prefix >, + // we keep the space within the name, strange enough + // ToDo: refactor output to schedule the space for it + // or do we really want to keep the real white-space? + sequence< identifier, optional < block_comment >, exactly<'('> > + > >()) + { + + std::string name(lexed); + name.erase(name.size() - 1); + ParserState p = pstate; + + // specially parse static stuff + // ToDo: really everything static? + if (peek_css < + sequence < + alternatives < + static_value, + binomial + >, + optional_css_whitespace, + exactly<')'> + > + >() + ) { + lex_css< alternatives < static_value, binomial > >(); + String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (lex_css< exactly<')'> >()) { + expr->can_compress_whitespace(true); + return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); + } + } + else if (Selector_List_Obj wrapped = parse_selector_list(true)) { + if (wrapped && lex_css< exactly<')'> >()) { + return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); + } + } + + } + // EO if pseudo selector + + else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { + return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); + } + else if(lex < pseudo_prefix >()) { + css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + } + + css_error("Invalid CSS", " after ", ": expected \")\", was "); + + // unreachable statement + return 0; + } + + const char* Parser::re_attr_sensitive_close(const char* src) + { + return alternatives < exactly<']'>, exactly<'/'> >(src); + } + + const char* Parser::re_attr_insensitive_close(const char* src) + { + return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + } + + Attribute_Selector_Obj Parser::parse_attribute_selector() + { + ParserState p = pstate; + if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); + std::string name(lexed); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); + } + if (!lex_css< alternatives< exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match > >()) { + error("invalid operator in attribute selector for " + name); + } + std::string matcher(lexed); + + String_Obj value = 0; + if (lex_css< identifier >()) { + value = SASS_MEMORY_NEW(String_Constant, p, lexed); + } + else if (lex_css< quoted_string >()) { + value = parse_interpolated_chunk(lexed, true); // needed! + } + else { + error("expected a string constant or identifier in attribute selector for " + name); + } + + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); + } + error("unterminated attribute selector for " + name); + return NULL; // to satisfy compilers (error must not return) + } + + /* parse block comment and add to block */ + void Parser::parse_block_comments() + { + Block_Obj block = block_stack.back(); + + while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; + // flag on second param is to skip loosely over comments + String_Obj contents = parse_interpolated_chunk(lexed, true, false); + block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); + } + } + + Declaration_Obj Parser::parse_declaration() { + String_Obj prop; + bool is_custom_property = false; + if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; + prop = parse_identifier_schema(); + } + else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; + prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + else { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + bool is_indented = true; + const std::string property(lexed); + if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); + if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); + if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty + if (is_custom_property) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); + } + lex < css_comments >(false); + if (peek_css< static_value >()) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); + } + else { + Expression_Obj value; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.found) { + if (lookahead.has_interpolants) { + value = parse_value_schema(lookahead.found); + } else { + value = parse_list(DELAYED); + } + } + else { + value = parse_list(DELAYED); + if (List_Ptr list = Cast(value)) { + if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + } + lex < css_comments >(false); + Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); + decl->is_indented(is_indented); + decl->update_pstate(pstate); + return decl; + } + } + + // parse +/- and return false if negative + // this is never hit via spec tests + bool Parser::parse_number_prefix() + { + bool positive = true; + while(true) { + if (lex < block_comment >()) continue; + if (lex < number_prefix >()) continue; + if (lex < exactly < '-' > >()) { + positive = !positive; + continue; + } + break; + } + return positive; + } + + Expression_Obj Parser::parse_map() + { + NESTING_GUARD(nestings); + Expression_Obj key = parse_list(); + List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); + + // it's not a map so return the lexed value as a list value + if (!lex_css< exactly<':'> >()) + { return key; } + + List_Obj l = Cast(key); + if (l && l->separator() == SASS_COMMA) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + + Expression_Obj value = parse_space_list(); + + map->append(key); + map->append(value); + + while (lex_css< exactly<','> >()) + { + // allow trailing commas - #495 + if (peek_css< exactly<')'> >(position)) + { break; } + + key = parse_space_list(); + + if (!(lex< exactly<':'> >())) + { css_error("Invalid CSS", " after ", ": expected \":\", was "); } + + value = parse_space_list(); + + map->append(key); + map->append(value); + } + + ParserState ps = map->pstate(); + ps.offset = pstate - ps + pstate.offset; + map->pstate(ps); + + return map; + } + + Expression_Obj Parser::parse_bracket_list() + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); + } + + bool has_paren = peek_css< exactly<'('> >() != NULL; + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + List_Obj l = Cast(list); + if (!l || l->is_bracketed() || has_paren) { + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); + bracketed_list->append(list); + return bracketed_list; + } + l->is_bracketed(true); + return l; + } + + // if we got so far, we actually do have a comma list + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); + // wrap the first expression + bracketed_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + bracketed_list->append(parse_space_list()); + } + // return the list + return bracketed_list; + } + + // parse list returns either a space separated list, + // a comma separated list or any bare expression found. + // so to speak: we unwrap items from lists if possible here! + Expression_Obj Parser::parse_list(bool delayed) + { + NESTING_GUARD(nestings); + return parse_comma_list(delayed); + } + + // will return singletons unwrapped + Expression_Obj Parser::parse_comma_list(bool delayed) + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0); + } + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + // set_delay doesn't apply to list children + // so this will only undelay single values + if (!delayed) list->set_delayed(false); + return list; + } + + // if we got so far, we actually do have a comma list + List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); + // wrap the first expression + comma_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + comma_list->append(parse_space_list()); + } + // return the list + return comma_list; + } + // EO parse_comma_list + + // will return singletons unwrapped + Expression_Obj Parser::parse_space_list() + { + NESTING_GUARD(nestings); + Expression_Obj disj1 = parse_disjunction(); + // if it's a singleton, return it (don't wrap it) + if (peek_css< space_list_terminator >(position) + ) { + return disj1; } + + List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); + space_list->append(disj1); + + while ( + !(peek_css< space_list_terminator >(position)) && + peek_css< optional_css_whitespace >() != end + ) { + // the space is parsed implicitly? + space_list->append(parse_disjunction()); + } + // return the list + return space_list; + } + // EO parse_space_list + + // parse logical OR operation + Expression_Obj Parser::parse_disjunction() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side conjunction + Expression_Obj conj = parse_conjunction(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_or >()) + operands.push_back(parse_conjunction()); + // if it's a singleton, return it directly + if (operands.size() == 0) return conj; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_disjunction + + // parse logical AND operation + Expression_Obj Parser::parse_conjunction() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side relation + Expression_Obj rel = parse_relation(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_and >()) { + operands.push_back(parse_relation()); + } + // if it's a singleton, return it directly + if (operands.size() == 0) return rel; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_conjunction + + // parse comparison operations + Expression_Obj Parser::parse_relation() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side expression + Expression_Obj lhs = parse_expression(); + std::vector operands; + std::vector operators; + // if it's a singleton, return it (don't wrap it) + while (peek< alternatives < + kwd_eq, + kwd_neq, + kwd_gte, + kwd_gt, + kwd_lte, + kwd_lt + > >(position)) + { + // is directly adjancent to expression? + bool left_ws = peek < css_comments >() != NULL; + // parse the operator + enum Sass_OP op + = lex() ? Sass_OP::EQ + : lex() ? Sass_OP::NEQ + : lex() ? Sass_OP::GTE + : lex() ? Sass_OP::LTE + : lex() ? Sass_OP::GT + : lex() ? Sass_OP::LT + // we checked the possibilities on top of fn + : Sass_OP::EQ; + // is directly adjacent to expression? + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ op, left_ws, right_ws }); + operands.push_back(parse_expression()); + } + // we are called recursively for list, so we first + // fold inner binary expression which has delayed + // correctly set to zero. After folding we also unwrap + // single nested items. So we cannot set delay on the + // returned result here, as we have lost nestings ... + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // parse_relation + + // parse expression valid for operations + // called from parse_relation + // called from parse_for_directive + // called from parse_media_expression + // parse addition and subtraction operations + Expression_Obj Parser::parse_expression() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parses multiple add and subtract operations + // NOTE: make sure that identifiers starting with + // NOTE: dashes do NOT count as subtract operation + Expression_Obj lhs = parse_operators(); + // if it's a singleton, return it (don't wrap it) + if (!(peek_css< exactly<'+'> >(position) || + // condition is a bit misterious, but some combinations should not be counted as operations + (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || + (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || + peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) + { return lhs; } + + std::vector operands; + std::vector operators; + bool left_ws = peek < css_comments >() != NULL; + while ( + lex_css< exactly<'+'> >() || + + ( + ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) + && lex_css< sequence< negate< digit >, exactly<'-'> > >() + ) + + ) { + + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); + operands.push_back(parse_operators()); + left_ws = peek < css_comments >() != NULL; + } + + if (operands.size() == 0) return lhs; + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + + // parse addition and subtraction operations + Expression_Obj Parser::parse_operators() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + Expression_Obj factor = parse_factor(); + // if it's a singleton, return it (don't wrap it) + std::vector operands; // factors + std::vector operators; // ops + // lex operations to apply to lhs + const char* left_ws = peek < css_comments >(); + while (lex_css< class_char< static_ops > >()) { + const char* right_ws = peek < css_comments >(); + switch(*lexed.begin) { + case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; + case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; + case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; + default: throw std::runtime_error("unknown static op parsed"); + } + operands.push_back(parse_factor()); + left_ws = peek < css_comments >(); + } + // operands and operators to binary expression + Expression_Obj ex = fold_operands(factor, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_operators + + + // called from parse_operators + // called from parse_value_schema + Expression_Obj Parser::parse_factor() + { + NESTING_GUARD(nestings); + lex < css_comments >(false); + if (lex_css< exactly<'('> >()) { + // parse_map may return a list + Expression_Obj value = parse_map(); + // lex the expected closing parenthesis + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); + // expression can be evaluated + return value; + } + else if (lex_css< exactly<'['> >()) { + // explicit bracketed + Expression_Obj value = parse_bracket_list(); + // lex the expected closing square bracket + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); + return value; + } + // string may be interpolated + // if (lex< quoted_string >()) { + // return &parse_string(); + // } + else if (peek< ie_property >()) { + return parse_ie_property(); + } + else if (peek< ie_keyword_arg >()) { + return parse_ie_keyword_arg(); + } + else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { + return parse_calc_function(); + } + else if (lex < functional_schema >()) { + return parse_function_call_schema(); + } + else if (lex< identifier_schema >()) { + String_Obj string = parse_identifier_schema(); + if (String_Schema_Ptr schema = Cast(string)) { + if (lex < exactly < '(' > >()) { + schema->append(parse_list()); + lex < exactly < ')' > >(); + } + } + return string; + } + else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { + return parse_url_function_string(); + } + else if (peek< re_functional >()) { + return parse_function_call(); + } + else if (lex< exactly<'+'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'-'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'/'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< sequence< kwd_not > >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + // this whole branch is never hit via spec tests + else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { + if (parse_number_prefix()) return parse_value(); // prefix is positive + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); + if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else { + return parse_value(); + } + } + + bool number_has_zero(const std::string& parsed) + { + size_t L = parsed.length(); + return !( (L > 0 && parsed.substr(0, 1) == ".") || + (L > 1 && parsed.substr(0, 2) == "0.") || + (L > 1 && parsed.substr(0, 2) == "-.") || + (L > 2 && parsed.substr(0, 3) == "-0.") ); + } + + Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "", + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "%", + true); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) + { + size_t L = parsed.length(); + size_t num_pos = parsed.find_first_not_of(" \n\r\t"); + if (num_pos == std::string::npos) num_pos = L; + size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); + if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { + unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); + } + if (unit_pos == std::string::npos) unit_pos = L; + const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(num.c_str()), + Token(number(parsed.c_str())), + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + { + Color_Ptr color = NULL; + if (parsed[0] != '#') { + return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); + } + // chop off the '#' + std::string hext(parsed.substr(1)); + if (parsed.length() == 4) { + std::string r(2, parsed[1]); + std::string g(2, parsed[2]); + std::string b(2, parsed[3]); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 7) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 9) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + std::string a(parsed.substr(7,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + static_cast(strtol(a.c_str(), NULL, 16)) / 255, + parsed); + } + color->is_interpolant(false); + color->is_delayed(false); + return color; + } + + Value_Ptr Parser::color_or_string(const std::string& lexed) const + { + if (auto color = name_to_color(lexed)) { + auto c = SASS_MEMORY_NEW(Color, color); + c->is_delayed(true); + c->pstate(pstate); + c->disp(lexed); + return c; + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + + // parse one value for a list + Expression_Obj Parser::parse_value() + { + lex< css_comments >(false); + if (lex< ampersand >()) + { + if (match< ampersand >()) { + warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); + } + return SASS_MEMORY_NEW(Parent_Selector, pstate); } + + if (lex< kwd_important >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } + + // parse `10%4px` into separated items and not a schema + if (lex< sequence < percentage, lookahead < number > > >()) + { return lexed_percentage(lexed); } + + if (lex< sequence < number, lookahead< sequence < op, number > > > >()) + { return lexed_number(lexed); } + + // string may be interpolated + if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) + { return parse_string(); } + + if (const char* stop = peek< value_schema >()) + { return parse_value_schema(stop); } + + // string may be interpolated + if (lex< quoted_string >()) + { return parse_string(); } + + if (lex< kwd_true >()) + { return SASS_MEMORY_NEW(Boolean, pstate, true); } + + if (lex< kwd_false >()) + { return SASS_MEMORY_NEW(Boolean, pstate, false); } + + if (lex< kwd_null >()) + { return SASS_MEMORY_NEW(Null, pstate); } + + if (lex< identifier >()) { + return color_or_string(lexed); + } + + if (lex< percentage >()) + { return lexed_percentage(lexed); } + + // match hex number first because 0x000 looks like a number followed by an identifier + if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) + { return lexed_hex_color(lexed); } + + if (lex< hexa >()) + { + std::string s = lexed.to_string(); + + deprecated( + "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in", + "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.", + true, pstate + ); + + return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); + } + + if (lex< sequence < exactly <'#'>, identifier > >()) + { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } + + // also handle the 10em- foo special case + // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression + if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) + { return lexed_dimension(lexed); } + + if (lex< sequence< static_component, one_plus< strict_identifier > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + if (lex< number >()) + { return lexed_number(lexed); } + + if (lex< variable >()) + { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } + + // Special case handling for `%` proceeding an interpolant. + if (lex< sequence< exactly<'%'>, optional< percentage > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + + // unreachable statement + return 0; + } + + // this parses interpolation inside other strings + // means the result should later be quoted again + String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) + { + const char* i = chunk.begin; + // see if there any interpolants + const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + + if (!p) { + String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); + if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); + return str_quoted; + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); + schema->is_interpolant(true); + while (i < chunk.end) { + p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace + if (j) { --j; + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside string constant " + chunk.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + // check if we need quotes here (was not sure after merge) + if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); + break; + } + ++ i; + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj tok; + if (!(tok = parse_css_variable_value_token(top_level))) { + return NULL; + } + + schema->concat(tok); + while ((tok = parse_css_variable_value_token(top_level))) { + schema->concat(tok); + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if ( + (top_level && lex< css_variable_top_level_value >(false)) || + (!top_level && lex< css_variable_value >(false)) + ) { + Token str(lexed); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); + } + else if (Expression_Obj tok = lex_interpolation()) { + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else if (lex< quoted_string >()) { + Expression_Obj tok = parse_string(); + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else { + if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { + if (lex< exactly<'('> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); + } + else if (lex< exactly<'['> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); + } + else if (lex< exactly<'{'> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); + } + } + } + + return schema->length() > 0 ? schema.detach() : NULL; + } + + Value_Obj Parser::parse_static_value() + { + lex< static_value >(); + Token str(lexed); + // static values always have trailing white- + // space and end delimiter (\s*[;]$) included + --pstate.offset.column; + --after_token.column; + --str.end; + --position; + + return color_or_string(str.time_wspace());; + } + + String_Obj Parser::parse_string() + { + return parse_interpolated_chunk(Token(lexed)); + } + + String_Obj Parser::parse_ie_property() + { + lex< ie_property >(); + Token str(lexed); + const char* i = str.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); + if (!p) { + return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); + } + + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < str.end) { + p = find_first_in_interval< exactly, block_comment >(i, str.end); + if (p) { + if (i < p) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty + } + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside IE function " + str.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < str.end) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); + } + break; + } + } + return schema; + } + + String_Obj Parser::parse_ie_keyword_arg() + { + String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); + if (lex< variable >()) { + kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); + } else { + lex< alternatives< identifier_schema, identifier > >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + lex< exactly<'='> >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (peek< variable >()) kwd_arg->append(parse_list()); + else if (lex< number >()) { + std::string parsed(lexed); + Util::normalize_decimals(parsed); + kwd_arg->append(lexed_number(parsed)); + } + else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } + return kwd_arg; + } + + String_Schema_Obj Parser::parse_value_schema(const char* stop) + { + // initialize the string schema object to add tokens + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + + if (peek>()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + const char* e; + const char* ee = end; + end = stop; + size_t num_items = 0; + bool need_space = false; + while (position < stop) { + // parse space between tokens + if (lex< spaces >() && num_items) { + need_space = true; + } + if (need_space) { + need_space = false; + // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + } + if ((e = peek< re_functional >()) && e < stop) { + schema->append(parse_function_call()); + } + // lex an interpolant /#{...}/ + else if (lex< exactly < hash_lbrace > >()) { + // Try to lex static expression first + if (peek< exactly< rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj ex; + if (lex< re_static_expression >()) { + ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } else { + ex = parse_list(true); + } + ex->is_interpolant(true); + schema->append(ex); + if (!lex < exactly < rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + } + // lex some string constants or other valid token + // Note: [-+] chars are left over from i.e. `#{3}+3` + else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + // lex a quoted string + else if (lex< quoted_string >()) { + // need_space = true; + // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + // else need_space = true; + schema->append(parse_string()); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + if (peek < exactly < '-' > >()) break; + } + else if (lex< identifier >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + } + // lex (normalized) variable + else if (lex< variable >()) { + std::string name(Util::normalize_underscores(lexed)); + schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); + } + // lex percentage value + else if (lex< percentage >()) { + schema->append(lexed_percentage(lexed)); + } + // lex dimension value + else if (lex< dimension >()) { + schema->append(lexed_dimension(lexed)); + } + // lex number value + else if (lex< number >()) { + schema->append(lexed_number(lexed)); + } + // lex hex color value + else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { + schema->append(lexed_hex_color(lexed)); + } + else if (lex< sequence < exactly <'#'>, identifier > >()) { + schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); + } + // lex a value in parentheses + else if (peek< parenthese_scope >()) { + schema->append(parse_factor()); + } + else { + break; + } + ++num_items; + } + if (position != stop) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); + position = stop; + } + end = ee; + return schema; + } + + // this parses interpolation outside other strings + // means the result must not be quoted again later + String_Obj Parser::parse_identifier_schema() + { + Token id(lexed); + const char* i = id.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); + if (!p) { + return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < id.end) { + p = find_first_in_interval< exactly, block_comment >(i, id.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + const char* o = position; position = i; + schema->append(parse_value_schema(p)); + position = o; + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); + interp_node->is_interpolant(true); + schema->append(interp_node); + // schema->has_interpolants(true); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside interpolated identifier " + id.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < end) { + const char* o = position; position = i; + schema->append(parse_value_schema(id.end)); + position = o; + } + break; + } + } + return schema ? schema.detach() : 0; + } + + // calc functions should preserve arguments + Function_Call_Obj Parser::parse_calc_function() + { + lex< identifier >(); + std::string name(lexed); + ParserState call_pos = pstate; + lex< exactly<'('> >(); + ParserState arg_pos = pstate; + const char* arg_beg = position; + parse_list(); + const char* arg_end = position; + lex< skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > >(); + + Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); + args->append(arg); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + String_Obj Parser::parse_url_function_string() + { + std::string prefix(""); + if (lex< uri_prefix >()) { + prefix = std::string(lexed); + } + + lex < optional_spaces >(); + String_Obj url_string = parse_url_function_argument(); + + std::string suffix(""); + if (lex< real_uri_suffix >()) { + suffix = std::string(lexed); + } + + std::string uri(""); + if (url_string) { + uri = url_string->to_string({ NESTED, 5 }); + } + + if (String_Schema_Ptr schema = Cast(url_string)) { + String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); + res->append(schema); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); + return res; + } else { + std::string res = prefix + uri + suffix; + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + } + + String_Obj Parser::parse_url_function_argument() + { + const char* p = position; + + std::string uri(""); + if (lex< real_uri_value >(false)) { + uri = lexed.to_string(); + } + + if (peek< exactly< hash_lbrace > >()) { + const char* pp = position; + // TODO: error checking for unclosed interpolants + while (pp && peek< exactly< hash_lbrace > >(pp)) { + pp = sequence< interpolant, real_uri_value >(pp); + } + position = pp; + return parse_interpolated_chunk(Token(p, position)); + } + else if (uri != "") { + std::string res = Util::rtrim(uri); + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + + return 0; + } + + Function_Call_Obj Parser::parse_function_call() + { + lex< identifier >(); + std::string name(lexed); + + if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) + { error("Cannot call content-exists() except within a mixin."); } + + ParserState call_pos = pstate; + Arguments_Obj args = parse_arguments(); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + Function_Call_Schema_Obj Parser::parse_function_call_schema() + { + String_Obj name = parse_identifier_schema(); + ParserState source_position_of_call = pstate; + Arguments_Obj args = parse_arguments(); + + return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); + } + + Content_Obj Parser::parse_content_directive() + { + return SASS_MEMORY_NEW(Content, pstate); + } + + If_Obj Parser::parse_if_directive(bool else_if) + { + stack.push_back(Scope::Control); + ParserState if_source_position = pstate; + bool root = block_stack.back()->is_root(); + Expression_Obj predicate = parse_list(); + Block_Obj block = parse_block(root); + Block_Obj alternative = NULL; + + // only throw away comment if we parse a case + // we want all other comments to be parsed + if (lex_css< elseif_directive >()) { + alternative = SASS_MEMORY_NEW(Block, pstate); + alternative->append(parse_if_directive(true)); + } + else if (lex_css< kwd_else_directive >()) { + alternative = parse_block(root); + } + stack.pop_back(); + return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); + } + + For_Obj Parser::parse_for_directive() + { + stack.push_back(Scope::Control); + ParserState for_source_position = pstate; + bool root = block_stack.back()->is_root(); + lex_variable(); + std::string var(Util::normalize_underscores(lexed)); + if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); + Expression_Obj lower_bound = parse_expression(); + bool inclusive = false; + if (lex< kwd_through >()) inclusive = true; + else if (lex< kwd_to >()) inclusive = false; + else error("expected 'through' or 'to' keyword in @for directive"); + Expression_Obj upper_bound = parse_expression(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); + } + + // helper to parse a var token + Token Parser::lex_variable() + { + // peek for dollar sign first + if (!peek< exactly <'$'> >()) { + css_error("Invalid CSS", " after ", ": expected \"$\", was "); + } + // we expect a simple identifier as the call name + if (!lex< sequence < exactly <'$'>, identifier > >()) { + lex< exactly <'$'> >(); // move pstate and position up + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + // helper to parse identifier + Token Parser::lex_identifier() + { + // we expect a simple identifier as the call name + if (!lex< identifier >()) { // ToDo: pstate wrong? + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + + Each_Obj Parser::parse_each_directive() + { + stack.push_back(Scope::Control); + ParserState each_source_position = pstate; + bool root = block_stack.back()->is_root(); + std::vector vars; + lex_variable(); + vars.push_back(Util::normalize_underscores(lexed)); + while (lex< exactly<','> >()) { + if (!lex< variable >()) error("@each directive requires an iteration variable"); + vars.push_back(Util::normalize_underscores(lexed)); + } + if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); + Expression_Obj list = parse_list(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); + } + + // called after parsing `kwd_while_directive` + While_Obj Parser::parse_while_directive() + { + stack.push_back(Scope::Control); + bool root = block_stack.back()->is_root(); + // create the initial while call object + While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); + // parse mandatory predicate + Expression_Obj predicate = parse_list(); + List_Obj l = Cast(predicate); + if (!predicate || (l && !l->length())) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); + } + call->predicate(predicate); + // parse mandatory block + call->block(parse_block(root)); + // return ast node + stack.pop_back(); + // return ast node + return call.detach(); + } + + // EO parse_while_directive + Media_Block_Obj Parser::parse_media_block() + { + stack.push_back(Scope::Media); + Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); + + media_block->media_queries(parse_media_queries()); + + Media_Block_Obj prev_media_block = last_media_block; + last_media_block = media_block; + media_block->block(parse_css_block()); + last_media_block = prev_media_block; + stack.pop_back(); + return media_block.detach(); + } + + List_Obj Parser::parse_media_queries() + { + advanceToNextToken(); + List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); + if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); + while (lex_css < exactly <','> >()) queries->append(parse_media_query()); + queries->update_pstate(pstate); + return queries.detach(); + } + + // Expression_Ptr Parser::parse_media_query() + Media_Query_Obj Parser::parse_media_query() + { + advanceToNextToken(); + Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); + if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } + else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } + + if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); + else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); + else media_query->append(parse_media_expression()); + + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); + if (lex < identifier_schema >()) { + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + schema->append(media_query->media_type()); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + schema->append(parse_identifier_schema()); + media_query->media_type(schema); + } + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); + + media_query->update_pstate(pstate); + + return media_query; + } + + Media_Query_Expression_Obj Parser::parse_media_expression() + { + if (lex < identifier_schema >()) { + String_Obj ss = parse_identifier_schema(); + return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); + } + if (!lex_css< exactly<'('> >()) { + error("media query expression must begin with '('"); + } + Expression_Obj feature; + if (peek_css< exactly<')'> >()) { + error("media feature required in media query expression"); + } + feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!lex_css< exactly<')'> >()) { + error("unclosed parenthesis in media query expression"); + } + return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); + } + + // lexed after `kwd_supports_directive` + // these are very similar to media blocks + Supports_Block_Obj Parser::parse_supports_directive() + { + Supports_Condition_Obj cond = parse_supports_condition(); + if (!cond) { + css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); + } + // create the ast node object for the support queries + Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); + // additional block is mandatory + // parse inner block + query->block(parse_block()); + // return ast node + return query; + } + + // parse one query operation + // may encounter nested queries + Supports_Condition_Obj Parser::parse_supports_condition() + { + lex < css_whitespace >(); + Supports_Condition_Obj cond; + if ((cond = parse_supports_negation())) return cond; + if ((cond = parse_supports_operator())) return cond; + if ((cond = parse_supports_interpolation())) return cond; + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_negation() + { + if (!lex < kwd_not >()) return 0; + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); + } + + Supports_Condition_Obj Parser::parse_supports_operator() + { + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + if (cond.isNull()) return 0; + + while (true) { + Supports_Operator::Operand op = Supports_Operator::OR; + if (lex < kwd_and >()) { op = Supports_Operator::AND; } + else if(!lex < kwd_or >()) { break; } + + lex < css_whitespace >(); + Supports_Condition_Obj right = parse_supports_condition_in_parens(); + + // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); + cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); + } + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_interpolation() + { + if (!lex < interpolant >()) return 0; + + String_Obj interp = parse_interpolated_chunk(lexed); + if (!interp) return 0; + + return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); + } + + // TODO: This needs some major work. Although feature conditions + // look like declarations their semantics differ significantly + Supports_Condition_Obj Parser::parse_supports_declaration() + { + Supports_Condition_Ptr cond; + // parse something declaration like + Expression_Obj feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!feature || !expression) error("@supports condition expected declaration"); + cond = SASS_MEMORY_NEW(Supports_Declaration, + feature->pstate(), + feature, + expression); + // ToDo: maybe we need an additional error condition? + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_condition_in_parens() + { + Supports_Condition_Obj interp = parse_supports_interpolation(); + if (interp != 0) return interp; + + if (!lex < exactly <'('> >()) return 0; + lex < css_whitespace >(); + + Supports_Condition_Obj cond = parse_supports_condition(); + if (cond != 0) { + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); + } else { + cond = parse_supports_declaration(); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); + } + lex < css_whitespace >(); + return cond; + } + + At_Root_Block_Obj Parser::parse_at_root_block() + { + stack.push_back(Scope::AtRoot); + ParserState at_source_position = pstate; + Block_Obj body = 0; + At_Root_Query_Obj expr; + Lookahead lookahead_result; + if (lex_css< exactly<'('> >()) { + expr = parse_at_root_query(); + } + if (peek_css < exactly<'{'> >()) { + lex (); + body = parse_block(true); + } + else if ((lookahead_result = lookahead_for_selector(position)).found) { + Ruleset_Obj r = parse_ruleset(lookahead_result); + body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); + body->append(r); + } + At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); + if (!expr.isNull()) at_root->expression(expr); + stack.pop_back(); + return at_root; + } + + At_Root_Query_Obj Parser::parse_at_root_query() + { + if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); + + if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { + css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); + } + + Expression_Obj feature = parse_list(); + if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); + Expression_Obj expression = parse_list(); + List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); + + if (expression->concrete_type() == Expression::LIST) { + value = Cast(expression); + } + else value->append(expression); + + At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, + value->pstate(), + feature, + value); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); + return cond; + } + + Directive_Obj Parser::parse_special_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); + + // this whole branch is never hit via spec tests + + Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(parse_selector_list(false)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + // this whole branch is never hit via spec tests + Directive_Obj Parser::parse_prefixed_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); + + Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(parse_selector_list(false)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + + Directive_Obj Parser::parse_directive() + { + Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); + String_Schema_Obj val = parse_almost_any_value(); + // strip left and right if they are of type string + directive->value(val); + if (peek< exactly<'{'> >()) { + directive->block(parse_block()); + } + return directive; + } + + Expression_Obj Parser::lex_interpolation() + { + if (lex < interpolant >(true) != NULL) { + return parse_interpolated_chunk(lexed, true); + } + return 0; + } + + Expression_Obj Parser::lex_interp_uri() + { + // create a string schema by lexing optional interpolations + return lex_interp< re_string_uri_open, re_string_uri_close >(); + } + + Expression_Obj Parser::lex_interp_string() + { + Expression_Obj rv; + if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; + if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; + return rv; + } + + Expression_Obj Parser::lex_almost_any_value_chars() + { + const char* match = + lex < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + sequence < + exactly < url_kwd >, + exactly <'('> + > + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + > + >(false); + if (match) { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + return NULL; + } + + Expression_Obj Parser::lex_almost_any_value_token() + { + Expression_Obj rv; + if (*position == 0) return 0; + if ((rv = lex_almost_any_value_chars())) return rv; + // if ((rv = lex_block_comment())) return rv; + // if ((rv = lex_single_line_comment())) return rv; + if ((rv = lex_interp_string())) return rv; + if ((rv = lex_interp_uri())) return rv; + if ((rv = lex_interpolation())) return rv; + if (lex< alternatives< hex, hex0 > >()) + { return lexed_hex_color(lexed); } + return rv; + } + + String_Schema_Obj Parser::parse_almost_any_value() + { + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if (*position == 0) return 0; + lex < spaces >(false); + Expression_Obj token = lex_almost_any_value_token(); + if (!token) return 0; + schema->append(token); + if (*position == 0) { + schema->rtrim(); + return schema.detach(); + } + + while ((token = lex_almost_any_value_token())) { + schema->append(token); + } + + lex < css_whitespace >(); + + schema->rtrim(); + + return schema.detach(); + } + + Warning_Obj Parser::parse_warning() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); + } + + Error_Obj Parser::parse_error() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); + } + + Debug_Obj Parser::parse_debug() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); + } + + Return_Obj Parser::parse_return_directive() + { + // check that we do not have an empty list (ToDo: check if we got all cases) + if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) + { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } + return SASS_MEMORY_NEW(Return, pstate, parse_list()); + } + + Lookahead Parser::lookahead_for_selector(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + rv.error = p; + if (const char* q = + peek < + re_selector_list + >(p) + ) { + bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; + bool could_be_escaped = false; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + // A property that's ambiguous with a nested selector is interpreted as a + // custom property. + if (*p == ':' && !could_be_escaped) { + rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); + } + could_be_escaped = *p == '\\'; + ++ p; + } + // store anyway } + + + // ToDo: remove + rv.error = q; + rv.position = q; + // check expected opening bracket + // only after successfull matching + if (peek < exactly<'{'> >(q)) rv.found = q; + // else if (peek < end_of_file >(q)) rv.found = q; + else if (peek < exactly<'('> >(q)) rv.found = q; + // else if (peek < exactly<';'> >(q)) rv.found = q; + // else if (peek < exactly<'}'> >(q)) rv.found = q; + if (rv.found || *p == 0) rv.error = 0; + } + + rv.parsable = ! rv.has_interpolants; + + // return result + return rv; + + } + // EO lookahead_for_selector + + // used in parse_block_nodes and parse_special_directive + // ToDo: actual usage is still not really clear to me? + Lookahead Parser::lookahead_for_include(const char* start) + { + // we actually just lookahead for a selector + Lookahead rv = lookahead_for_selector(start); + // but the "found" rules are different + if (const char* p = rv.position) { + // check for additional abort condition + if (peek < exactly<';'> >(p)) rv.found = p; + else if (peek < exactly<'}'> >(p)) rv.found = p; + } + // return result + return rv; + } + // EO lookahead_for_include + + // look ahead for a token with interpolation in it + // we mostly use the result if there is an interpolation + // everything that passes here gets parsed as one schema + // meaning it will not be parsed as a space separated list + Lookahead Parser::lookahead_for_value(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + if (const char* q = + peek < + non_greedy < + alternatives < + // consume whitespace + block_comment, // spaces, + // main tokens + sequence < + interpolant, + optional < + quoted_string + > + >, + identifier, + variable, + // issue #442 + sequence < + parenthese_scope, + interpolant, + optional < + quoted_string + > + > + >, + sequence < + // optional_spaces, + alternatives < + // end_of_file, + exactly<'{'>, + exactly<'}'>, + exactly<';'> + > + > + > + >(p) + ) { + if (p == q) return rv; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + ++ p; + } + // store anyway + // ToDo: remove + rv.position = q; + // check expected opening bracket + // only after successful matching + if (peek < exactly<'{'> >(q)) rv.found = q; + else if (peek < exactly<';'> >(q)) rv.found = q; + else if (peek < exactly<'}'> >(q)) rv.found = q; + } + + // return result + return rv; + } + // EO lookahead_for_value + + void Parser::read_bom() + { + size_t skip = 0; + std::string encoding; + bool utf_8 = false; + switch ((unsigned char) source[0]) { + case 0xEF: + skip = check_bom_chars(source, end, utf_8_bom, 3); + encoding = "UTF-8"; + utf_8 = true; + break; + case 0xFE: + skip = check_bom_chars(source, end, utf_16_bom_be, 2); + encoding = "UTF-16 (big endian)"; + break; + case 0xFF: + skip = check_bom_chars(source, end, utf_16_bom_le, 2); + skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); + encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); + break; + case 0x00: + skip = check_bom_chars(source, end, utf_32_bom_be, 4); + encoding = "UTF-32 (big endian)"; + break; + case 0x2B: + skip = check_bom_chars(source, end, utf_7_bom_1, 4) + | check_bom_chars(source, end, utf_7_bom_2, 4) + | check_bom_chars(source, end, utf_7_bom_3, 4) + | check_bom_chars(source, end, utf_7_bom_4, 4) + | check_bom_chars(source, end, utf_7_bom_5, 5); + encoding = "UTF-7"; + break; + case 0xF7: + skip = check_bom_chars(source, end, utf_1_bom, 3); + encoding = "UTF-1"; + break; + case 0xDD: + skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); + encoding = "UTF-EBCDIC"; + break; + case 0x0E: + skip = check_bom_chars(source, end, scsu_bom, 3); + encoding = "SCSU"; + break; + case 0xFB: + skip = check_bom_chars(source, end, bocu_1_bom, 3); + encoding = "BOCU-1"; + break; + case 0x84: + skip = check_bom_chars(source, end, gb_18030_bom, 4); + encoding = "GB-18030"; + break; + default: break; + } + if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); + position += skip; + } + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) + { + size_t skip = 0; + if (src + len > end) return 0; + for (size_t i = 0; i < len; ++i, ++skip) { + if ((unsigned char) src[i] != bom[i]) return 0; + } + return skip; + } + + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) + { + for (size_t i = 0, S = operands.size(); i < S; ++i) { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); + } + return base; + } + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) + { + if (String_Schema_Ptr schema = Cast(base)) { + // return schema; + if (schema->has_interpolants()) { + if (i + 1 < operands.size() && ( + (ops[0].operand == Sass_OP::EQ) + || (ops[0].operand == Sass_OP::ADD) + || (ops[0].operand == Sass_OP::DIV) + || (ops[0].operand == Sass_OP::MUL) + || (ops[0].operand == Sass_OP::NEQ) + || (ops[0].operand == Sass_OP::LT) + || (ops[0].operand == Sass_OP::GT) + || (ops[0].operand == Sass_OP::LTE) + || (ops[0].operand == Sass_OP::GTE) + )) { + Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); + return rhs; + } + // return schema; + } + } + + for (size_t S = operands.size(); i < S; ++i) { + if (String_Schema_Ptr schema = Cast(operands[i])) { + if (schema->has_interpolants()) { + if (i + 1 < S) { + // this whole branch is never hit via spec tests + Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); + return base; + } + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + return base; + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + Binary_Expression_Ptr b = Cast(base.ptr()); + if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { + base->is_delayed(true); + } + } + // nested binary expression are never to be delayed + if (Binary_Expression_Ptr b = Cast(base)) { + if (Cast(b->left())) base->set_delayed(false); + if (Cast(b->right())) base->set_delayed(false); + } + return base; + } + + void Parser::error(std::string msg, Position pos) + { + Position p(pos.line ? pos : before_token); + ParserState pstate(path, source, p, Offset(0, 0)); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, msg); + } + + void Parser::error(std::string msg) + { + error(msg, pstate); + } + + // print a css parsing error with actual context information from parsed source + void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) + { + int max_len = 18; + const char* end = this->end; + while (*end != 0) ++ end; + const char* pos = peek < optional_spaces >(); + if (!pos) pos = position; + + const char* last_pos(pos); + if (last_pos > source) { + utf8::prior(last_pos, source); + } + // backup position to last significant char + while (trim && last_pos > source && last_pos < end) { + if (!Prelexer::is_space(*last_pos)) break; + utf8::prior(last_pos, source); + } + + bool ellipsis_left = false; + const char* pos_left(last_pos); + const char* end_left(last_pos); + + if (*pos_left) utf8::next(pos_left, end); + if (*end_left) utf8::next(end_left, end); + while (pos_left > source) { + if (utf8::distance(pos_left, end_left) >= max_len) { + utf8::prior(pos_left, source); + ellipsis_left = *(pos_left) != '\n' && + *(pos_left) != '\r'; + utf8::next(pos_left, end); + break; + } + + const char* prev = pos_left; + utf8::prior(prev, source); + if (*prev == '\r') break; + if (*prev == '\n') break; + pos_left = prev; + } + if (pos_left < source) { + pos_left = source; + } + + bool ellipsis_right = false; + const char* end_right(pos); + const char* pos_right(pos); + while (end_right < end) { + if (utf8::distance(pos_right, end_right) > max_len) { + ellipsis_left = *(pos_right) != '\n' && + *(pos_right) != '\r'; + break; + } + if (*end_right == '\r') break; + if (*end_right == '\n') break; + utf8::next(end_right, end); + } + // if (*end_right == 0) end_right ++; + + std::string left(pos_left, end_left); + std::string right(pos_right, end_right); + size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; + size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; + if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); + if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; + // Hotfix when source is null, probably due to interpolation parsing!? + if (source == NULL || *source == 0) source = pstate.src; + // now pass new message to the more generic error function + error(msg + prefix + quote(left) + middle + quote(right)); + } + +} diff --git a/src/libsass/parser.hpp b/src/libsass/parser.hpp new file mode 100644 index 000000000..d2a6ddc1a --- /dev/null +++ b/src/libsass/parser.hpp @@ -0,0 +1,400 @@ +#ifndef SASS_PARSER_H +#define SASS_PARSER_H + +#include +#include + +#include "ast.hpp" +#include "position.hpp" +#include "context.hpp" +#include "position.hpp" +#include "prelexer.hpp" + +#ifndef MAX_NESTING +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +#define MAX_NESTING 512 +#endif + +struct Lookahead { + const char* found; + const char* error; + const char* position; + bool parsable; + bool has_interpolants; + bool is_custom_property; +}; + +namespace Sass { + + class Parser : public ParserState { + public: + + enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; + + Context& ctx; + std::vector block_stack; + std::vector stack; + Media_Block_Ptr last_media_block; + const char* source; + const char* position; + const char* end; + Position before_token; + Position after_token; + ParserState pstate; + Backtraces traces; + size_t indentation; + size_t nestings; + + Token lexed; + + Parser(Context& ctx, const ParserState& pstate, Backtraces traces) + : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), + source(0), position(0), end(0), before_token(pstate), after_token(pstate), + pstate(pstate), traces(traces), indentation(0), nestings(0) + { + stack.push_back(Scope::Root); + } + + // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + // special static parsers to convert strings into certain selectors + static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + +#ifdef __clang__ + + // lex and peak uses the template parameter to branch on the action, which + // triggers clangs tautological comparison on the single-comparison + // branches. This is not a bug, just a merging of behaviour into + // one function + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-compare" + +#endif + + + // skip current token and next whitespace + // moves ParserState right before next token + void advanceToNextToken(); + + bool peek_newline(const char* start = 0); + + // skip over spaces, tabs and line comments + template + const char* sneak(const char* start = 0) + { + using namespace Prelexer; + + // maybe use optional start position from arguments? + const char* it_position = start ? start : position; + + // skip white-space? + if (mx == spaces || + mx == no_spaces || + mx == css_comments || + mx == css_whitespace || + mx == optional_spaces || + mx == optional_css_comments || + mx == optional_css_whitespace + ) { + return it_position; + } + + // skip over spaces, tabs and sass line comments + const char* pos = optional_css_whitespace(it_position); + // always return a valid position + return pos ? pos : it_position; + + } + + // match will not skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* match(const char* start = 0) + { + // match the given prelexer + return mx(position); + } + + // peek will only skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* peek(const char* start = 0) + { + + // sneak up to the actual token we want to lex + // this should skip over white-space if desired + const char* it_before_token = sneak < mx >(start); + + // match the given prelexer + const char* match = mx(it_before_token); + + // check if match is in valid range + return match <= end ? match : 0; + + } + + // white-space handling is built into the lexer + // this way you do not need to parse it yourself + // some matchers don't accept certain white-space + // we do not support start arg, since we manipulate + // sourcemap offset and we modify the position pointer! + // lex will only skip over space, tabs and line comment + template + const char* lex(bool lazy = true, bool force = false) + { + + if (*position == 0) return 0; + + // position considered before lexed token + // we can skip whitespace or comments for + // lazy developers (but we need control) + const char* it_before_token = position; + + // sneak up to the actual token we want to lex + // this should skip over white-space if desired + if (lazy) it_before_token = sneak < mx >(position); + + // now call matcher to get position after token + const char* it_after_token = mx(it_before_token); + + // check if match is in valid range + if (it_after_token > end) return 0; + + // maybe we want to update the parser state anyway? + if (force == false) { + // assertion that we got a valid match + if (it_after_token == 0) return 0; + // assertion that we actually lexed something + if (it_after_token == it_before_token) return 0; + } + + // create new lexed token object (holds the parse results) + lexed = Token(position, it_before_token, it_after_token); + + // advance position (add whitespace before current token) + before_token = after_token.add(position, it_before_token); + + // update after_token position for current token + after_token.add(it_before_token, it_after_token); + + // ToDo: could probably do this incremetal on original object (API wants offset?) + pstate = ParserState(path, source, lexed, before_token, after_token - before_token); + + // advance internal char iterator + return position = it_after_token; + + } + + // lex_css skips over space, tabs, line and block comment + // all block comments will be consumed and thrown away + // source-map position will point to token after the comment + template + const char* lex_css() + { + // copy old token + Token prev = lexed; + // store previous pointer + const char* oldpos = position; + Position bt = before_token; + Position at = after_token; + ParserState op = pstate; + // throw away comments + // update srcmap position + lex < Prelexer::css_comments >(); + // now lex a new token + const char* pos = lex< mx >(); + // maybe restore prev state + if (pos == 0) { + pstate = op; + lexed = prev; + position = oldpos; + after_token = at; + before_token = bt; + } + // return match + return pos; + } + + // all block comments will be skipped and thrown away + template + const char* peek_css(const char* start = 0) + { + // now peek a token (skip comments first) + return peek< mx >(peek < Prelexer::css_comments >(start)); + } + +#ifdef __clang__ + +#pragma clang diagnostic pop + +#endif + + void error(std::string msg); + void error(std::string msg, Position pos); + // generate message with given and expected sample + // text before and in the middle are configurable + void css_error(const std::string& msg, + const std::string& prefix = " after ", + const std::string& middle = ", was: ", + const bool trim = true); + void read_bom(); + + Block_Obj parse(); + Import_Obj parse_import(); + Definition_Obj parse_definition(Definition::Type which_type); + Parameters_Obj parse_parameters(); + Parameter_Obj parse_parameter(); + Mixin_Call_Obj parse_include_directive(); + Arguments_Obj parse_arguments(); + Argument_Obj parse_argument(); + Assignment_Obj parse_assignment(); + Ruleset_Obj parse_ruleset(Lookahead lookahead); + Selector_List_Obj parse_selector_list(bool chroot); + Complex_Selector_Obj parse_complex_selector(bool chroot); + Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); + Compound_Selector_Obj parse_compound_selector(); + Simple_Selector_Obj parse_simple_selector(); + Wrapped_Selector_Obj parse_negated_selector(); + Simple_Selector_Obj parse_pseudo_selector(); + Attribute_Selector_Obj parse_attribute_selector(); + Block_Obj parse_block(bool is_root = false); + Block_Obj parse_css_block(bool is_root = false); + bool parse_block_nodes(bool is_root = false); + bool parse_block_node(bool is_root = false); + + bool parse_number_prefix(); + Declaration_Obj parse_declaration(); + Expression_Obj parse_map(); + Expression_Obj parse_bracket_list(); + Expression_Obj parse_list(bool delayed = false); + Expression_Obj parse_comma_list(bool delayed = false); + Expression_Obj parse_space_list(); + Expression_Obj parse_disjunction(); + Expression_Obj parse_conjunction(); + Expression_Obj parse_relation(); + Expression_Obj parse_expression(); + Expression_Obj parse_operators(); + Expression_Obj parse_factor(); + Expression_Obj parse_value(); + Function_Call_Obj parse_calc_function(); + Function_Call_Obj parse_function_call(); + Function_Call_Schema_Obj parse_function_call_schema(); + String_Obj parse_url_function_string(); + String_Obj parse_url_function_argument(); + String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); + String_Obj parse_string(); + Value_Obj parse_static_value(); + String_Schema_Obj parse_css_variable_value(bool top_level = true); + String_Schema_Obj parse_css_variable_value_token(bool top_level = true); + String_Obj parse_ie_property(); + String_Obj parse_ie_keyword_arg(); + String_Schema_Obj parse_value_schema(const char* stop); + String_Obj parse_identifier_schema(); + If_Obj parse_if_directive(bool else_if = false); + For_Obj parse_for_directive(); + Each_Obj parse_each_directive(); + While_Obj parse_while_directive(); + Return_Obj parse_return_directive(); + Content_Obj parse_content_directive(); + void parse_charset_directive(); + Media_Block_Obj parse_media_block(); + List_Obj parse_media_queries(); + Media_Query_Obj parse_media_query(); + Media_Query_Expression_Obj parse_media_expression(); + Supports_Block_Obj parse_supports_directive(); + Supports_Condition_Obj parse_supports_condition(); + Supports_Condition_Obj parse_supports_negation(); + Supports_Condition_Obj parse_supports_operator(); + Supports_Condition_Obj parse_supports_interpolation(); + Supports_Condition_Obj parse_supports_declaration(); + Supports_Condition_Obj parse_supports_condition_in_parens(); + At_Root_Block_Obj parse_at_root_block(); + At_Root_Query_Obj parse_at_root_query(); + String_Schema_Obj parse_almost_any_value(); + Directive_Obj parse_special_directive(); + Directive_Obj parse_prefixed_directive(); + Directive_Obj parse_directive(); + Warning_Obj parse_warning(); + Error_Obj parse_error(); + Debug_Obj parse_debug(); + + Value_Ptr color_or_string(const std::string& lexed) const; + + // be more like ruby sass + Expression_Obj lex_almost_any_value_token(); + Expression_Obj lex_almost_any_value_chars(); + Expression_Obj lex_interp_string(); + Expression_Obj lex_interp_uri(); + Expression_Obj lex_interpolation(); + + // these will throw errors + Token lex_variable(); + Token lex_identifier(); + + void parse_block_comments(); + + Lookahead lookahead_for_value(const char* start = 0); + Lookahead lookahead_for_selector(const char* start = 0); + Lookahead lookahead_for_include(const char* start = 0); + + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); + + void throw_syntax_error(std::string message, size_t ln = 0); + void throw_read_error(std::string message, size_t ln = 0); + + + template + Expression_Obj lex_interp() + { + if (lex < open >(false)) { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (!itpl.isNull()) schema->append(itpl); + while (lex < close >(false)) { + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (!itpl.isNull()) schema->append(itpl); + } else { + return schema; + } + } + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + return 0; + } + + public: + static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); + static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); + private: + Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; + Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; + Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; + Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; + + static const char* re_attr_sensitive_close(const char* src); + static const char* re_attr_insensitive_close(const char* src); + + }; + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); +} + +#endif diff --git a/src/libsass/plugins.md b/src/libsass/plugins.md new file mode 100644 index 000000000..a9711e3e1 --- /dev/null +++ b/src/libsass/plugins.md @@ -0,0 +1,47 @@ +Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). + +## plugin.cpp + +```C++ +#include +#include +#include +#include "sass_values.h" + +union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, fn_foo); + // return the list + return fn_list; +} +``` + +To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). + +## Compile with gcc on linux + +```bash +g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +``` + +## Compile with mingw on windows + +```bash +g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass +``` diff --git a/src/libsass/prelexer.cpp b/src/libsass/prelexer.cpp new file mode 100644 index 000000000..a43b1ee3c --- /dev/null +++ b/src/libsass/prelexer.cpp @@ -0,0 +1,1774 @@ +#include "sass.hpp" +#include +#include +#include +#include "util.hpp" +#include "position.hpp" +#include "prelexer.hpp" +#include "constants.hpp" + + +namespace Sass { + // using namespace Lexer; + using namespace Constants; + + namespace Prelexer { + + + /* + + def string_re(open, close) + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + end + end + + # A hash of regular expressions that are used for tokenizing strings. + # + # The key is a `[Symbol, Boolean]` pair. + # The symbol represents which style of quotation to use, + # while the boolean represents whether or not the string + # is following an interpolated segment. + STRING_REGULAR_EXPRESSIONS = { + :double => { + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + false => string_re('"', '"'), + true => string_re('', '"') + }, + :single => { + false => string_re("'", "'"), + true => string_re('', "'") + }, + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a + # non-standard version of http://www.w3.org/TR/css3-conditional/ + :url_prefix => { + false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + :domain => { + false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + } + } + */ + + /* + /#{open} + ( + \\. + | + \# (?!\{) + | + [^#{close}\\#] + )* + (#{close}|#\{) + /m + false => string_re('"', '"'), + true => string_re('', '"') + */ + extern const char string_double_negates[] = "\"\\#"; + const char* re_string_double_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_double_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'"'>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + extern const char string_single_negates[] = "'\\#"; + const char* re_string_single_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_single_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'\''>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + /* + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + */ + const char* re_string_uri_close(const char* src) + { + return sequence < + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < optional < W >, exactly <')'> >, + lookahead < exactly< hash_lbrace > > + > + >, + optional < + sequence < optional < W >, exactly <')'> > + > + >(src); + } + + const char* re_string_uri_open(const char* src) + { + return sequence < + exactly <'u'>, + exactly <'r'>, + exactly <'l'>, + exactly <'('>, + W, + alternatives< + quoted_string, + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < W, exactly <')'> >, + exactly< hash_lbrace > + > + > + > + >(src); + } + + // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. + const char* line_comment(const char* src) + { + return sequence< + exactly < + slash_slash + >, + non_greedy< + any_char, + end_of_line + > + >(src); + } + + // Match a block comment. + const char* block_comment(const char* src) + { + return sequence< + delimited_by< + slash_star, + star_slash, + false + > + >(src); + } + /* not use anymore - remove? + const char* block_comment_prefix(const char* src) { + return exactly(src); + } + // Match either comment. + const char* comment(const char* src) { + return line_comment(src); + } + */ + + // Match zero plus white-space or line_comments + const char* optional_css_whitespace(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_whitespace(const char* src) { + return one_plus< alternatives >(src); + } + // Match optional_css_whitepace plus block_comments + const char* optional_css_comments(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_comments(const char* src) { + return one_plus< alternatives >(src); + } + + // Match one backslash escaped char /\\./ + const char* escape_seq(const char* src) + { + return sequence< + exactly<'\\'>, + alternatives < + minmax_range< + 1, 3, xdigit + >, + any_char + >, + optional < + exactly <' '> + > + >(src); + } + + // Match identifier start + const char* identifier_alpha(const char* src) + { + return alternatives< + unicode_seq, + alpha, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match identifier after start + const char* identifier_alnum(const char* src) + { + return alternatives< + unicode_seq, + alnum, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match CSS identifiers. + const char* strict_identifier(const char* src) + { + return sequence< + one_plus < strict_identifier_alpha >, + zero_plus < strict_identifier_alnum > + // word_boundary not needed + >(src); + } + + // Match CSS identifiers. + const char* identifier(const char* src) + { + return sequence< + zero_plus< exactly<'-'> >, + one_plus < identifier_alpha >, + zero_plus < identifier_alnum > + // word_boundary not needed + >(src); + } + + const char* strict_identifier_alpha(const char* src) + { + return alternatives < + alpha, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + const char* strict_identifier_alnum(const char* src) + { + return alternatives < + alnum, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + // Match a single CSS unit + const char* one_unit(const char* src) + { + return sequence < + optional < exactly <'-'> >, + strict_identifier_alpha, + zero_plus < alternatives< + strict_identifier_alnum, + sequence < + one_plus < exactly<'-'> >, + strict_identifier_alpha + > + > > + >(src); + } + + // Match numerator/denominator CSS units + const char* multiple_units(const char* src) + { + return + sequence < + one_unit, + zero_plus < + sequence < + exactly <'*'>, + one_unit + > + > + >(src); + } + + // Match complex CSS unit identifiers + const char* unit_identifier(const char* src) + { + return sequence < + multiple_units, + optional < + sequence < + exactly <'/'>, + negate < sequence < + exactly < calc_fn_kwd >, + exactly < '(' > + > >, + multiple_units + > > + >(src); + } + + const char* identifier_alnums(const char* src) + { + return one_plus< identifier_alnum >(src); + } + + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src) { + return alternatives < + exactly < '+' >, + sequence < + exactly < '-' >, + optional_css_whitespace, + exactly< '-' > + > + >(src); + } + + // Match interpolant schemas + const char* identifier_schema(const char* src) { + + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + sequence < + optional < + exactly <'$'> + >, + identifier + >, + exactly <'-'> + > + >, + interpolant, + zero_plus < + alternatives < + digits, + sequence < + optional < + exactly <'$'> + >, + identifier + >, + quoted_string, + exactly<'-'> + > + > + > + >, + negate < + exactly<'%'> + > + > (src); + } + + // interpolants can be recursive/nested + const char* interpolant(const char* src) { + return recursive_scopes< exactly, exactly >(src); + } + + // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ + const char* single_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'\''>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '\'' > + > + >, + exactly <'\''> + >(src); + } + + // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ + const char* double_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'"'>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '"' > + > + >, + exactly <'"'> + >(src); + } + + // $re_quoted = /(?:$re_squote|$re_dquote)/ + const char* quoted_string(const char* src) { + // match a quoted string, while skipping interpolants + return alternatives< + single_quoted_string, + double_quoted_string + >(src); + } + + const char* sass_value(const char* src) { + return alternatives < + quoted_string, + identifier, + percentage, + hex, + dimension, + number + >(src); + } + + // this is basically `one_plus < sass_value >` + // takes care to not parse invalid combinations + const char* value_combinations(const char* src) { + // `2px-2px` is invalid combo + bool was_number = false; + const char* pos; + while (src) { + if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { + was_number = false; + src = pos; + } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { + was_number = true; + src = pos; + } else { + break; + } + } + return src; + } + + // must be at least one interpolant + // can be surrounded by sass values + // make sure to never parse (dim)(dim) + // since this wrongly consumes `2px-1px` + // `2px1px` is valid number (unit `px1px`) + const char* value_schema(const char* src) + { + return sequence < + one_plus < + sequence < + optional < value_combinations >, + interpolant, + optional < value_combinations > + > + > + >(src); + } + + // Match CSS '@' keywords. + const char* at_keyword(const char* src) { + return sequence, identifier>(src); + } + + /* + tok(%r{ + ( + \\. + | + (?!url\() + [^"'/\#!;\{\}] # " + | + /(?![\*\/]) + | + \#(?!\{) + | + !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. + )+ + }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || + interpolation(:warn_for_color) + */ + const char* re_almost_any_value_token(const char* src) { + + return alternatives < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + uri_prefix + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + >, + block_comment, + line_comment, + interpolant, + space, + sequence < + exactly<'u'>, + exactly<'r'>, + exactly<'l'>, + exactly<'('>, + zero_plus < + alternatives < + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + > + >, + // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + exactly<')'> + > + >(src); + } + + /* + DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, + :each, :while, :if, :else, :extend, :import, :media, :charset, :content, + :_moz_document, :at_root, :error] + */ + const char* re_special_directive(const char* src) { + return alternatives < + word < mixin_kwd >, + word < include_kwd >, + word < function_kwd >, + word < return_kwd >, + word < debug_kwd >, + word < warn_kwd >, + word < for_kwd >, + word < each_kwd >, + word < while_kwd >, + word < if_kwd >, + word < else_kwd >, + word < extend_kwd >, + word < import_kwd >, + word < media_kwd >, + word < charset_kwd >, + word < content_kwd >, + // exactly < moz_document_kwd >, + word < at_root_kwd >, + word < error_kwd > + >(src); + } + + const char* re_prefixed_directive(const char* src) { + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < alnum >, + exactly <'-'> + > + >, + exactly < supports_kwd > + >(src); + } + + const char* re_reference_combinator(const char* src) { + return sequence < + optional < + sequence < + zero_plus < + exactly <'-'> + >, + identifier, + exactly <'|'> + > + >, + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* static_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + re_reference_combinator, + exactly <'/'> + >(src); + } + + const char* schema_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + optional < + sequence < + css_ip_identifier, + exactly <'|'> + > + >, + css_ip_identifier, + exactly <'/'> + > (src); + } + + const char* kwd_import(const char* src) { + return word(src); + } + + const char* kwd_at_root(const char* src) { + return word(src); + } + + const char* kwd_with_directive(const char* src) { + return word(src); + } + + const char* kwd_without_directive(const char* src) { + return word(src); + } + + const char* kwd_media(const char* src) { + return word(src); + } + + const char* kwd_supports_directive(const char* src) { + return word(src); + } + + const char* kwd_mixin(const char* src) { + return word(src); + } + + const char* kwd_function(const char* src) { + return word(src); + } + + const char* kwd_return_directive(const char* src) { + return word(src); + } + + const char* kwd_include_directive(const char* src) { + return word(src); + } + + const char* kwd_content_directive(const char* src) { + return word(src); + } + + const char* kwd_charset_directive(const char* src) { + return word(src); + } + + const char* kwd_extend(const char* src) { + return word(src); + } + + + const char* kwd_if_directive(const char* src) { + return word(src); + } + + const char* kwd_else_directive(const char* src) { + return word(src); + } + const char* elseif_directive(const char* src) { + return sequence< exactly< else_kwd >, + optional_css_comments, + word< if_after_else_kwd > >(src); + } + + const char* kwd_for_directive(const char* src) { + return word(src); + } + + const char* kwd_from(const char* src) { + return word(src); + } + + const char* kwd_to(const char* src) { + return word(src); + } + + const char* kwd_through(const char* src) { + return word(src); + } + + const char* kwd_each_directive(const char* src) { + return word(src); + } + + const char* kwd_in(const char* src) { + return word(src); + } + + const char* kwd_while_directive(const char* src) { + return word(src); + } + + const char* name(const char* src) { + return one_plus< alternatives< alnum, + exactly<'-'>, + exactly<'_'>, + escape_seq > >(src); + } + + const char* kwd_warn(const char* src) { + return word(src); + } + + const char* kwd_err(const char* src) { + return word(src); + } + + const char* kwd_dbg(const char* src) { + return word(src); + } + + /* not used anymore - remove? + const char* directive(const char* src) { + return sequence< exactly<'@'>, identifier >(src); + } */ + + const char* kwd_null(const char* src) { + return word(src); + } + + const char* css_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* css_ip_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + alternatives < + identifier, + interpolant + > + >(src); + } + + // Match CSS type selectors + const char* namespace_prefix(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_identifier + > + >, + exactly <'|'>, + negate < + exactly <'='> + > + >(src); + } + + // Match CSS type selectors + const char* namespace_schema(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_ip_identifier + > + >, + exactly<'|'>, + negate < + exactly <'='> + > + >(src); + } + + const char* hyphens_and_identifier(const char* src) { + return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); + } + const char* hyphens_and_name(const char* src) { + return sequence< zero_plus< exactly< '-' > >, name >(src); + } + const char* universal(const char* src) { + return sequence< optional, exactly<'*'> >(src); + } + // Match CSS id names. + const char* id_name(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS class names. + const char* class_name(const char* src) { + return sequence, identifier >(src); + } + // Attribute name in an attribute selector. + const char* attribute_name(const char* src) { + return alternatives< sequence< optional, identifier>, + identifier >(src); + } + // match placeholder selectors + const char* placeholder(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS numeric constants. + + const char* op(const char* src) { + return class_char(src); + } + const char* sign(const char* src) { + return class_char(src); + } + const char* unsigned_number(const char* src) { + return alternatives, + exactly<'.'>, + one_plus >, + digits>(src); + } + const char* number(const char* src) { + return sequence< + optional, + unsigned_number, + optional< + sequence< + exactly<'e'>, + optional, + unsigned_number + > + > + >(src); + } + const char* coefficient(const char* src) { + return alternatives< sequence< optional, digits >, + sign >(src); + } + const char* binomial(const char* src) { + return sequence < + optional < sign >, + optional < digits >, + exactly <'n'>, + zero_plus < sequence < + optional_css_whitespace, sign, + optional_css_whitespace, digits + > > + >(src); + } + const char* percentage(const char* src) { + return sequence< number, exactly<'%'> >(src); + } + const char* ampersand(const char* src) { + return exactly<'&'>(src); + } + + /* not used anymore - remove? + const char* em(const char* src) { + return sequence< number, exactly >(src); + } */ + const char* dimension(const char* src) { + return sequence(src); + } + const char* hex(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 4 && len != 7) ? 0 : p; + } + const char* hexa(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 9) ? 0 : p; + } + const char* hex0(const char* src) { + const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 8) ? 0 : p; + } + + /* no longer used - remove? + const char* rgb_prefix(const char* src) { + return word(src); + }*/ + // Match CSS uri specifiers. + + const char* uri_prefix(const char* src) { + return sequence < + exactly < + url_kwd + >, + zero_plus < + sequence < + exactly <'-'>, + one_plus < + alpha + > + > + >, + exactly <'('> + >(src); + } + + // TODO: rename the following two functions + /* no longer used - remove? + const char* uri(const char* src) { + return sequence< exactly, + optional, + quoted_string, + optional, + exactly<')'> >(src); + }*/ + /* no longer used - remove? + const char* url_value(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename + optional< exactly<'/'> > >(src); + }*/ + /* no longer used - remove? + const char* url_schema(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + filename_schema >(src); // optional trailing slash + }*/ + // Match CSS "!important" keyword. + const char* kwd_important(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS "!optional" keyword. + const char* kwd_optional(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!default" keyword. + const char* default_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!global" keyword. + const char* global_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS pseudo-class/element prefixes. + const char* pseudo_prefix(const char* src) { + return sequence< exactly<':'>, optional< exactly<':'> > >(src); + } + // Match CSS function call openers. + const char* functional_schema(const char* src) { + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + identifier, + exactly <'-'> + > + >, + one_plus < + sequence < + interpolant, + alternatives < + digits, + identifier, + exactly<'+'>, + exactly<'-'> + > + > + > + > + >, + negate < + exactly <'%'> + >, + lookahead < + exactly <'('> + > + > (src); + } + + const char* re_nothing(const char* src) { + return src; + } + + const char* re_functional(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + const char* re_pseudo_selector(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + // Match the CSS negation pseudo-class. + const char* pseudo_not(const char* src) { + return word< pseudo_not_fn_kwd >(src); + } + // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. + const char* even(const char* src) { + return word(src); + } + const char* odd(const char* src) { + return word(src); + } + // Match CSS attribute-matching operators. + const char* exact_match(const char* src) { return exactly<'='>(src); } + const char* class_match(const char* src) { return exactly(src); } + const char* dash_match(const char* src) { return exactly(src); } + const char* prefix_match(const char* src) { return exactly(src); } + const char* suffix_match(const char* src) { return exactly(src); } + const char* substring_match(const char* src) { return exactly(src); } + // Match CSS combinators. + /* not used anymore - remove? + const char* adjacent_to(const char* src) { + return sequence< optional_spaces, exactly<'+'> >(src); + } + const char* precedes(const char* src) { + return sequence< optional_spaces, exactly<'~'> >(src); + } + const char* parent_of(const char* src) { + return sequence< optional_spaces, exactly<'>'> >(src); + } + const char* ancestor_of(const char* src) { + return sequence< spaces, negate< exactly<'{'> > >(src); + }*/ + + // Match SCSS variable names. + const char* variable(const char* src) { + return sequence, identifier>(src); + } + + // parse `calc`, `-a-calc` and `--b-c-calc` + // but do not parse `foocalc` or `foo-calc` + const char* calc_fn_call(const char* src) { + return sequence < + optional < sequence < + hyphens, + one_plus < sequence < + strict_identifier, + hyphens + > > + > >, + exactly < calc_fn_kwd >, + word_boundary + >(src); + } + + // Match Sass boolean keywords. + const char* kwd_true(const char* src) { + return word(src); + } + const char* kwd_false(const char* src) { + return word(src); + } + const char* kwd_only(const char* src) { + return keyword < only_kwd >(src); + } + const char* kwd_and(const char* src) { + return keyword < and_kwd >(src); + } + const char* kwd_or(const char* src) { + return keyword < or_kwd >(src); + } + const char* kwd_not(const char* src) { + return keyword < not_kwd >(src); + } + const char* kwd_eq(const char* src) { + return exactly(src); + } + const char* kwd_neq(const char* src) { + return exactly(src); + } + const char* kwd_gt(const char* src) { + return exactly(src); + } + const char* kwd_gte(const char* src) { + return exactly(src); + } + const char* kwd_lt(const char* src) { + return exactly(src); + } + const char* kwd_lte(const char* src) { + return exactly(src); + } + + // match specific IE syntax + const char* ie_progid(const char* src) { + return sequence < + word, + exactly<':'>, + alternatives< identifier_schema, identifier >, + zero_plus< sequence< + exactly<'.'>, + alternatives< identifier_schema, identifier > + > >, + zero_plus < sequence< + exactly<'('>, + optional_css_whitespace, + optional < sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, + zero_plus< sequence< + optional_css_whitespace, + exactly<','>, + optional_css_whitespace, + sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > + > + > > + > >, + optional_css_whitespace, + exactly<')'> + > > + >(src); + } + const char* ie_expression(const char* src) { + return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); + } + const char* ie_property(const char* src) { + return alternatives < ie_expression, ie_progid >(src); + } + + // const char* ie_args(const char* src) { + // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, + // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); + // } + + const char* ie_keyword_arg_property(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier + >(src); + } + const char* ie_keyword_arg_value(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier, + quoted_string, + number, + hex, + hexa, + sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + > + >(src); + } + + const char* ie_keyword_arg(const char* src) { + return sequence < + ie_keyword_arg_property, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + ie_keyword_arg_value + >(src); + } + + // Path matching functions. + /* not used anymore - remove? + const char* folder(const char* src) { + return sequence< zero_plus< any_char_except<'/'> >, + exactly<'/'> >(src); + } + const char* folders(const char* src) { + return zero_plus< folder >(src); + }*/ + /* not used anymore - remove? + const char* chunk(const char* src) { + char inside_str = 0; + const char* p = src; + size_t depth = 0; + while (true) { + if (!*p) { + return 0; + } + else if (!inside_str && (*p == '"' || *p == '\'')) { + inside_str = *p; + } + else if (*p == inside_str && *(p-1) != '\\') { + inside_str = 0; + } + else if (*p == '(' && !inside_str) { + ++depth; + } + else if (*p == ')' && !inside_str) { + if (depth == 0) return p; + else --depth; + } + ++p; + } + // unreachable + return 0; + } + */ + + // follow the CSS spec more closely and see if this helps us scan URLs correctly + /* not used anymore - remove? + const char* NL(const char* src) { + return alternatives< exactly<'\n'>, + sequence< exactly<'\r'>, exactly<'\n'> >, + exactly<'\r'>, + exactly<'\f'> >(src); + }*/ + + const char* H(const char* src) { + return std::isxdigit(*src) ? src+1 : 0; + } + + const char* W(const char* src) { + return zero_plus< alternatives< + space, + exactly< '\t' >, + exactly< '\r' >, + exactly< '\n' >, + exactly< '\f' > + > >(src); + } + + const char* UUNICODE(const char* src) { + return sequence< exactly<'\\'>, + between, + optional< W > + >(src); + } + + const char* NONASCII(const char* src) { + return nonascii(src); + } + + const char* ESCAPE(const char* src) { + return alternatives< + UUNICODE, + sequence< + exactly<'\\'>, + alternatives< + NONASCII, + escapable_character + > + > + >(src); + } + + const char* list_terminator(const char* src) { + return alternatives < + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<']'>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + >(src); + }; + + const char* space_list_terminator(const char* src) { + return alternatives < + exactly<','>, + list_terminator + >(src); + }; + + + // const char* real_uri_prefix(const char* src) { + // return alternatives< + // exactly< url_kwd >, + // exactly< url_prefix_kwd > + // >(src); + // } + + const char* real_uri(const char* src) { + return sequence< + exactly< url_kwd >, + exactly< '(' >, + W, + real_uri_value, + exactly< ')' > + >(src); + } + + const char* real_uri_suffix(const char* src) { + return sequence< W, exactly< ')' > >(src); + } + + const char* real_uri_value(const char* src) { + return + sequence< + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + real_uri_suffix, + exactly< hash_lbrace > + > + > + > + (src); + } + + const char* static_string(const char* src) { + const char* pos = src; + const char * s = quoted_string(pos); + Token t(pos, s); + const unsigned int p = count_interval< interpolant >(t.begin, t.end); + return (p == 0) ? t.end : 0; + } + + const char* unicode_seq(const char* src) { + return sequence < + alternatives < + exactly< 'U' >, + exactly< 'u' > + >, + exactly< '+' >, + padded_token < + 6, xdigit, + exactly < '?' > + > + >(src); + } + + const char* static_component(const char* src) { + return alternatives< identifier, + static_string, + percentage, + hex, + hexa, + exactly<'|'>, + // exactly<'+'>, + sequence < number, unit_identifier >, + number, + sequence< exactly<'!'>, word > + >(src); + } + + const char* static_property(const char* src) { + return + sequence < + zero_plus< + sequence < + optional_css_comments, + alternatives < + exactly<','>, + exactly<'('>, + exactly<')'>, + kwd_optional, + quoted_string, + interpolant, + identifier, + percentage, + dimension, + variable, + alnum, + sequence < + exactly <'\\'>, + any_char + > + > + > + >, + lookahead < + sequence < + optional_css_comments, + alternatives < + exactly <';'>, + exactly <'}'>, + end_of_file + > + > + > + >(src); + } + + const char* static_value(const char* src) { + return sequence< sequence< + static_component, + zero_plus< identifier > + >, + zero_plus < sequence< + alternatives< + sequence< optional_spaces, alternatives< + exactly < '/' >, + exactly < ',' >, + exactly < ' ' > + >, optional_spaces >, + spaces + >, + static_component + > >, + zero_plus < spaces >, + alternatives< exactly<';'>, exactly<'}'> > + >(src); + } + + extern const char css_variable_url_negates[] = "()[]{}\"'#/"; + const char* css_variable_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; + const char* css_variable_top_level_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_top_level_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + const char* parenthese_scope(const char* src) { + return sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + >(src); + } + + const char* re_selector_list(const char* src) { + return alternatives < + // partial bem selector + sequence < + ampersand, + one_plus < + exactly < '-' > + >, + word_boundary, + optional_spaces + >, + // main selector matching + one_plus < + alternatives < + // consume whitespace and comments + spaces, block_comment, line_comment, + // match `/deep/` selector (pass-trough) + // there is no functionality for it yet + schema_reference_combinator, + // match selector ops /[*&%,\[\]]/ + class_char < selector_lookahead_ops >, + // match selector combinators /[>+~]/ + class_char < selector_combinator_ops >, + // match pseudo selectors + sequence < + exactly <'('>, + optional_spaces, + optional , + optional_spaces, + exactly <')'> + >, + // match attribute compare operators + alternatives < + exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match + >, + // main selector match + sequence < + // allow namespace prefix + optional < namespace_schema >, + // modifiers prefixes + alternatives < + sequence < + exactly <'#'>, + // not for interpolation + negate < exactly <'{'> > + >, + // class match + exactly <'.'>, + // single or double colon + sequence < + optional < pseudo_prefix >, + // fix libsass issue 2376 + negate < uri_prefix > + > + >, + // accept hypens in token + one_plus < sequence < + // can start with hyphens + zero_plus < + sequence < + exactly <'-'>, + optional_spaces + > + >, + // now the main token + alternatives < + kwd_optional, + exactly <'*'>, + quoted_string, + interpolant, + identifier, + variable, + percentage, + binomial, + dimension, + alnum + > + > >, + // can also end with hyphens + zero_plus < exactly<'-'> > + > + > + > + >(src); + } + + const char* type_selector(const char* src) { + return sequence< optional, identifier>(src); + } + const char* re_type_selector(const char* src) { + return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); + } + const char* re_static_expression(const char* src) { + return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); + } + + // lexer special_fn: these functions cannot be overloaded + // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) + const char* re_special_fun(const char* src) { + + // match this first as we test prefix hyphens + if (const char* calc = calc_fn_call(src)) { + return calc; + } + + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < + alternatives < + alpha, + exactly <'+'>, + exactly <'-'> + > + > + > + >, + alternatives < + word < expression_kwd >, + sequence < + sequence < + exactly < progid_kwd >, + exactly <':'> + >, + zero_plus < + alternatives < + char_range <'a', 'z'>, + exactly <'.'> + > + > + > + > + >(src); + } + + } +} diff --git a/src/libsass/sass.cpp b/src/libsass/sass.cpp new file mode 100644 index 000000000..72edd7ced --- /dev/null +++ b/src/libsass/sass.cpp @@ -0,0 +1,151 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "sass.h" +#include "file.hpp" +#include "util.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // helper to convert string list to vector + std::vector list2vec(struct string_list* cur) + { + std::vector list; + while (cur) { + list.push_back(cur->string); + cur = cur->next; + } + return list; + } + +} + +extern "C" { + using namespace Sass; + + // Allocate libsass heap memory + // Don't forget string termination! + void* ADDCALL sass_alloc_memory(size_t size) + { + void* ptr = malloc(size); + if (ptr == NULL) { + std::cerr << "Out of memory.\n"; + exit(EXIT_FAILURE); + } + return ptr; + } + + char* ADDCALL sass_copy_c_string(const char* str) + { + size_t len = strlen(str) + 1; + char* cpy = (char*) sass_alloc_memory(len); + std::memcpy(cpy, str, len); + return cpy; + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_memory(void* ptr) + { + if (ptr) free (ptr); + } + + // caller must free the returned memory + char* ADDCALL sass_string_quote (const char *str, const char quote_mark) + { + std::string quoted = quote(str, quote_mark); + return sass_copy_c_string(quoted.c_str()); + } + + // caller must free the returned memory + char* ADDCALL sass_string_unquote (const char *str) + { + std::string unquoted = unquote(str); + return sass_copy_c_string(unquoted.c_str()); + } + + char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_include(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_file(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + // this has the original resolve logic for sass include + char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_include(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_file(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Get compiled libsass version + const char* ADDCALL libsass_version(void) + { + return LIBSASS_VERSION; + } + + // Get compiled libsass version + const char* ADDCALL libsass_language_version(void) + { + return LIBSASS_LANGUAGE_VERSION; + } + +} + +namespace Sass { + + // helper to aid dreaded MSVC debug mode + char* sass_copy_string(std::string str) + { + // In MSVC the following can lead to segfault: + // sass_copy_c_string(stream.str().c_str()); + // Reason is that the string returned by str() is disposed before + // sass_copy_c_string is invoked. The string is actually a stack + // object, so indeed nobody is holding on to it. So it seems + // perfectly fair to release it right away. So the const char* + // by c_str will point to invalid memory. I'm not sure if this is + // the behavior for all compiler, but I'm pretty sure we would + // have gotten more issues reported if that would be the case. + // Wrapping it in a functions seems the cleanest approach as the + // function must hold on to the stack variable until it's done. + return sass_copy_c_string(str.c_str()); + } + +} \ No newline at end of file diff --git a/src/libsass/sass2scss.cpp b/src/libsass/sass2scss.cpp new file mode 100644 index 000000000..8645d0c37 --- /dev/null +++ b/src/libsass/sass2scss.cpp @@ -0,0 +1,895 @@ +/** + * sass2scss + * Licensed under the MIT License + * Copyright (c) Marcel Greter + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +// include library +#include +#include +#include +#include +#include +#include +#include + +///* +// +// src comments: comments in sass syntax (staring with //) +// css comments: multiline comments in css syntax (starting with /*) +// +// KEEP_COMMENT: keep src comments in the resulting css code +// STRIP_COMMENT: strip out all comments (either src or css) +// CONVERT_COMMENT: convert all src comments to css comments +// +//*/ + +// our own header +#include "sass2scss.h" + +// add namespace for c++ +namespace Sass +{ + + // return the actual prettify value from options + #define PRETTIFY(converter) (converter.options - (converter.options & 248)) + // query the options integer to check if the option is enables + #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) + #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) + #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) + + // some makros to access the indentation stack + #define INDENT(converter) (converter.indents.top()) + + // some makros to query comment parser status + #define IS_PARSING(converter) (converter.comment == "") + #define IS_COMMENT(converter) (converter.comment != "") + #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) + #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) + + // pretty printer helper function + static std::string closer (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " }" : + PRETTIFY(converter) <= 1 ? " }" : + "\n" + INDENT(converter) + "}"; + } + + // pretty printer helper function + static std::string opener (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " { " : + PRETTIFY(converter) <= 2 ? " {" : + "\n" + INDENT(converter) + "{"; + } + + // check if the given string is a pseudo selector + // needed to differentiate from sass property syntax + static bool isPseudoSelector (std::string& sel) + { + + size_t len = sel.length(); + if (len < 1) return false; + size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); + if (pos != std::string::npos) sel.erase(pos, std::string::npos); + size_t i = sel.length(); + while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } + + // CSS Level 1 - Recommendation + if (sel == ":link") return true; + if (sel == ":visited") return true; + if (sel == ":active") return true; + + // CSS Level 2 (Revision 1) - Recommendation + if (sel == ":lang") return true; + if (sel == ":first-child") return true; + if (sel == ":hover") return true; + if (sel == ":focus") return true; + // disabled - also valid properties + // if (sel == ":left") return true; + // if (sel == ":right") return true; + if (sel == ":first") return true; + + // Selectors Level 3 - Recommendation + if (sel == ":target") return true; + if (sel == ":root") return true; + if (sel == ":nth-child") return true; + if (sel == ":nth-last-of-child") return true; + if (sel == ":nth-of-type") return true; + if (sel == ":nth-last-of-type") return true; + if (sel == ":last-child") return true; + if (sel == ":first-of-type") return true; + if (sel == ":last-of-type") return true; + if (sel == ":only-child") return true; + if (sel == ":only-of-type") return true; + if (sel == ":empty") return true; + if (sel == ":not") return true; + + // CSS Basic User Interface Module Level 3 - Working Draft + if (sel == ":default") return true; + if (sel == ":valid") return true; + if (sel == ":invalid") return true; + if (sel == ":in-range") return true; + if (sel == ":out-of-range") return true; + if (sel == ":required") return true; + if (sel == ":optional") return true; + if (sel == ":read-only") return true; + if (sel == ":read-write") return true; + if (sel == ":dir") return true; + if (sel == ":enabled") return true; + if (sel == ":disabled") return true; + if (sel == ":checked") return true; + if (sel == ":indeterminate") return true; + if (sel == ":nth-last-child") return true; + + // Selectors Level 4 - Working Draft + if (sel == ":any-link") return true; + if (sel == ":local-link") return true; + if (sel == ":scope") return true; + if (sel == ":active-drop-target") return true; + if (sel == ":valid-drop-target") return true; + if (sel == ":invalid-drop-target") return true; + if (sel == ":current") return true; + if (sel == ":past") return true; + if (sel == ":future") return true; + if (sel == ":placeholder-shown") return true; + if (sel == ":user-error") return true; + if (sel == ":blank") return true; + if (sel == ":nth-match") return true; + if (sel == ":nth-last-match") return true; + if (sel == ":nth-column") return true; + if (sel == ":nth-last-column") return true; + if (sel == ":matches") return true; + + // Fullscreen API - Living Standard + if (sel == ":fullscreen") return true; + + // not a pseudo selector + return false; + + } + + static size_t findFirstCharacter (std::string& sass, size_t pos) + { + return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); + } + + static size_t findLastCharacter (std::string& sass, size_t pos) + { + return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); + } + + static bool isUrl (std::string& sass, size_t pos) + { + return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; + } + + // check if there is some char data + // will ignore everything in comments + static bool hasCharData (std::string& sass) + { + + size_t col_pos = 0; + + while (true) + { + + // try to find some meaningfull char + col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); + + // there was no meaningfull char found + if (col_pos == std::string::npos) return false; + + // found a multiline comment opener + if (sass.substr(col_pos, 2) == "/*") + { + // find the multiline comment closer + col_pos = sass.find("*/", col_pos); + // maybe we did not find the closer here + if (col_pos == std::string::npos) return false; + // skip closer + col_pos += 2; + } + else + { + return true; + } + + } + + } + // EO hasCharData + + // find src comment opener + // correctly skips quoted strings + static size_t findCommentOpener (std::string& sass) + { + + size_t col_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + size_t brackets = 0; + + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*()", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + if (character == '(') + { + if (!quoted && !apoed) brackets ++; + } + else if (character == ')') + { + if (!quoted && !apoed) brackets --; + } + else if (character == '\"') + { + // invert quote bool + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + // invert quote bool + if (!quoted && !comment) apoed = !apoed; + } + else if (col_pos > 0 && character == '/') + { + if (sass.at(col_pos - 1) == '*') + { + comment = false; + } + // next needs to be a slash too + else if (sass.at(col_pos - 1) == '/') + { + // only found if not in single or double quote, bracket or comment + if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; + } + } + else if (character == '\\') + { + // skip next char if in quote + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (col_pos > 0 && character == '*') + { + // opening a multiline comment + if (sass.at(col_pos - 1) == '/') + { + // we are now in a comment + if (!quoted && !apoed) comment = true; + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + return col_pos; + + } + // EO findCommentOpener + + // remove multiline comments from sass string + // correctly skips quoted strings + static std::string removeMultilineComment (std::string &sass) + { + + std::string clean = ""; + size_t col_pos = 0; + size_t open_pos = 0; + size_t close_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + + // process sass til string end + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + // found quoted string delimiter + if (character == '\"') + { + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + if (!quoted && !comment) apoed = !apoed; + } + // found possible comment closer + else if (character == '/') + { + // look back to see if it is actually a closer + if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') + { + close_pos = col_pos + 1; comment = false; + } + } + else if (character == '\\') + { + // skip escaped char + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (character == '*') + { + // look back to see if it is actually an opener + if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') + { + comment = true; open_pos = col_pos - 1; + clean += sass.substr(close_pos, open_pos - close_pos); + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + // add final parts (add half open comment text) + if (comment) clean += sass.substr(open_pos); + else clean += sass.substr(close_pos); + + // return string + return clean; + + } + // EO removeMultilineComment + + // right trim a given string + std::string rtrim(const std::string &sass) + { + std::string trimmed = sass; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) + { trimmed.erase(pos_ws + 1); } + else { trimmed.clear(); } + return trimmed; + } + // EO rtrim + + // flush whitespace and print additional text, but + // only print additional chars and buffer whitespace + std::string flush (std::string& sass, converter& converter) + { + + // return flushed + std::string scss = ""; + + // print whitespace buffer + scss += PRETTIFY(converter) > 0 ? + converter.whitespace : ""; + // reset whitespace buffer + converter.whitespace = ""; + + // remove possible newlines from string + size_t pos_right = sass.find_last_not_of("\n\r"); + if (pos_right == std::string::npos) return scss; + + // get the linefeeds from the string + std::string lfs = sass.substr(pos_right + 1); + sass = sass.substr(0, pos_right + 1); + + // find some source comment opener + size_t comment_pos = findCommentOpener(sass); + // check if there was a source comment + if (comment_pos != std::string::npos) + { + // convert comment (but only outside other coments) + if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) + { + // convert to multiline comment + sass.at(comment_pos + 1) = '*'; + // add comment node to the whitespace + sass += " */"; + } + // not at line start + if (comment_pos > 0) + { + // also include whitespace before the actual comment opener + size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); + comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; + } + if (!STRIP_COMMENT(converter)) + { + // add comment node to the whitespace + converter.whitespace += sass.substr(comment_pos); + } + else + { + // sass = removeMultilineComments(sass); + } + // update the actual sass code + sass = sass.substr(0, comment_pos); + } + + // add newline as getline discharged it + converter.whitespace += lfs + "\n"; + + // maybe remove any leading whitespace + if (PRETTIFY(converter) == 0) + { + // remove leading whitespace and update string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + if (pos_left != std::string::npos) sass = sass.substr(pos_left); + } + + // add flushed data + scss += sass; + + // return string + return scss; + + } + // EO flush + + // process a line of the sass text + std::string process (std::string& sass, converter& converter) + { + + // resulting string + std::string scss = ""; + + // strip multi line comments + if (STRIP_COMMENT(converter)) + { + sass = removeMultilineComment(sass); + } + + // right trim input + sass = rtrim(sass); + + // get postion of first meaningfull character in string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + + // special case for final run + if (converter.end_of_file) pos_left = 0; + + // maybe has only whitespace + if (pos_left == std::string::npos) + { + // just add complete whitespace + converter.whitespace += sass + "\n"; + } + // have meaningfull first char + else + { + + // extract and store indentation string + std::string indent = sass.substr(0, pos_left); + + // check if current line starts a comment + std::string open = sass.substr(pos_left, 2); + + // line has less or same indentation + // finalize previous open parser context + if (indent.length() <= INDENT(converter).length()) + { + + // close multilinie comment + if (IS_CSS_COMMENT(converter)) + { + // check if comments will be stripped anyway + if (!STRIP_COMMENT(converter)) scss += " */"; + } + // close src comment comment + else if (IS_SRC_COMMENT(converter)) + { + // add a newline to avoid closer on same line + // this would put the bracket in the comment node + // no longer needed since we parse them correctly + // if (KEEP_COMMENT(converter)) scss += "\n"; + } + // close css properties + else if (converter.property) + { + // add closer unless in concat mode + if (!converter.comma) + { + // if there was no colon we have a selector + // looks like there were no inner properties + if (converter.selector) scss += " {}"; + // add final semicolon + else if (!converter.semicolon) scss += ";"; + } + } + + // reset comment state + converter.comment = ""; + + } + + // make sure we close every "higher" block + while (indent.length() < INDENT(converter).length()) + { + // pop stacked context + converter.indents.pop(); + // print close bracket + if (IS_PARSING(converter)) + { scss += closer(converter); } + else { scss += " */"; } + // reset comment state + converter.comment = ""; + } + + // reset converter state + converter.selector = false; + + // looks like some undocumented behavior ... + // https://github.com/mgreter/sass2scss/issues/29 + if (sass.substr(pos_left, 1) == "\\") { + converter.selector = true; + sass[pos_left] = ' '; + } + + // check if we have sass property syntax + if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") + { + + // default to a selector + // change back if property found + converter.selector = true; + // get postion of first whitespace char + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + // assertion check for valid result + if (pos_wspace != std::string::npos) + { + // get the possible pseudo selector + std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); + // get position of the first real property value char + // pseudo selectors get this far, but have no actual value + size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); + // assertion check for valid result + if (pos_value != std::string::npos) + { + // only process if not (fallowed by a semicolon or is a pseudo selector) + if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) + { + // create new string by interchanging the colon sign for property and value + sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); + // try to find a colon in the current line, but only ... + size_t pos_colon = sass.find_first_not_of(":", pos_left); + // assertion for valid result + if (pos_colon != std::string::npos) + { + // ... after the first word (skip begining colons) + pos_colon = sass.find_first_of(":", pos_colon); + // it is a selector if there was no colon found + converter.selector = pos_colon == std::string::npos; + } + } + } + } + + // check if we have a BEM property (one colon and no selector) + if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; + } + + } + + // terminate some statements immediately + else if ( + sass.substr(pos_left, 5) == "@warn" || + sass.substr(pos_left, 6) == "@debug" || + sass.substr(pos_left, 6) == "@error" || + sass.substr(pos_left, 6) == "@value" || + sass.substr(pos_left, 8) == "@charset" || + sass.substr(pos_left, 10) == "@namespace" + ) { sass = indent + sass.substr(pos_left); } + // replace some specific sass shorthand directives (if not fallowed by a white space character) + else if (sass.substr(pos_left, 1) == "=") + { sass = indent + "@mixin " + sass.substr(pos_left + 1); } + else if (sass.substr(pos_left, 1) == "+") + { + // must be followed by a mixin call (no whitespace afterwards or at ending directly) + if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { + sass = indent + "@include " + sass.substr(pos_left + 1); + } + } + + // add quotes for import if needed + else if (sass.substr(pos_left, 7) == "@import") + { + // get positions for the actual import url + size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); + size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); + size_t start = pos; + bool in_dqstr = false; + bool in_sqstr = false; + bool is_escaped = false; + do { + if (is_escaped) { + is_escaped = false; + } + else if (sass[pos] == '\\') { + is_escaped = true; + } + else if (sass[pos] == '"') { + if (!in_sqstr) in_dqstr = ! in_dqstr; + } + else if (sass[pos] == '\'') { + if (!in_dqstr) in_sqstr = ! in_sqstr; + } + else if (in_dqstr || in_sqstr) { + // skip over quoted stuff + } + else if (sass[pos] == ',' || sass[pos] == 0) { + if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { + size_t end = findLastCharacter(sass, pos - 1) + 1; + sass = sass.replace(end, 0, "\""); + sass = sass.replace(start, 0, "\""); + pos += 2; + } + start = findFirstCharacter(sass, pos + 1); + } + } + while (sass[pos++] != 0); + + } + else if ( + sass.substr(pos_left, 7) != "@return" && + sass.substr(pos_left, 7) != "@extend" && + sass.substr(pos_left, 8) != "@include" && + sass.substr(pos_left, 8) != "@content" + ) { + + // probably a selector anyway + converter.selector = true; + // try to find first colon in the current line + size_t pos_colon = sass.find_first_of(":", pos_left); + // assertion that we have a colon + if (pos_colon != std::string::npos) + { + // it is not a selector if we have a space after a colon + if (sass[pos_colon+1] == ' ') converter.selector = false; + if (sass[pos_colon+1] == ' ') converter.selector = false; + } + + } + + // current line has more indentation + if (indent.length() >= INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // has meaningfull chars + if (hasCharData(sass)) + { + // is probably a property + // also true for selectors + converter.property = true; + } + } + } + // current line has more indentation + if (indent.length() > INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // had meaningfull chars + if (converter.property) + { + // print block opener + scss += opener(converter); + // push new stack context + converter.indents.push(""); + // store block indentation + INDENT(converter) = indent; + } + } + // is and will be a src comment + else if (!IS_CSS_COMMENT(converter)) + { + // scss does not allow multiline src comments + // therefore add forward slashes to all lines + sass.at(INDENT(converter).length()+0) = '/'; + // there is an edge case here if indentation + // is minimal (will overwrite the fist char) + sass.at(INDENT(converter).length()+1) = '/'; + // could code around that, but I dont' think + // this will ever be the cause for any trouble + } + } + + // line is opening a new comment + if (open == "/*" || open == "//") + { + // reset the property state + converter.property = false; + // close previous comment + if (IS_CSS_COMMENT(converter) && open != "") + { + if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; + } + // force single line comments + // into a correct css comment + if (CONVERT_COMMENT(converter)) + { + if (IS_PARSING(converter)) + { sass.at(pos_left + 1) = '*'; } + } + // set comment flag + converter.comment = open; + + } + + // flush data only under certain conditions + if (!( + // strip css and src comments if option is set + (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || + // strip src comment even if strip option is not set + // but only if the keep src comment option is not set + (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) + )) + { + // flush data and buffer whitespace + scss += flush(sass, converter); + } + + // get postion of last meaningfull char + size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + + // check for invalid result + if (pos_right != std::string::npos) + { + + // get the last meaningfull char + std::string close = sass.substr(pos_right, 1); + + // check if next line should be concatenated (list mode) + converter.comma = IS_PARSING(converter) && close == ","; + converter.semicolon = IS_PARSING(converter) && close == ";"; + + // check if we have more than + // one meaningfull char + if (pos_right > 0) + { + + // get the last two chars from string + std::string close = sass.substr(pos_right - 1, 2); + // update parser status for expicitly closed comment + if (close == "*/") converter.comment = ""; + + } + + } + // EO have meaningfull chars from end + + } + // EO have meaningfull chars from start + + // return scss + return scss; + + } + // EO process + + // read line with either CR, LF or CR LF format + // http://stackoverflow.com/a/6089413/1550314 + static std::istream& safeGetline(std::istream& is, std::string& t) + { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf* sb = is.rdbuf(); + + for(;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if(sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if(t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char)c; + } + } + } + + // the main converter function for c++ + char* sass2scss (const std::string& sass, const int options) + { + + // local variables + std::string line; + std::string scss = ""; + std::stringstream stream(sass); + + // create converter variable + converter converter; + // initialise all options + converter.comma = false; + converter.property = false; + converter.selector = false; + converter.semicolon = false; + converter.end_of_file = false; + converter.comment = ""; + converter.whitespace = ""; + converter.indents.push(""); + converter.options = options; + + // read line by line and process them + while(safeGetline(stream, line) && !stream.eof()) + { scss += process(line, converter); } + + // create mutable string + std::string closer = ""; + // set the end of file flag + converter.end_of_file = true; + // process to close all open blocks + scss += process(closer, converter); + + // allocate new memory on the heap + // caller has to free it after use + char * cstr = (char*) malloc (scss.length() + 1); + // create a copy of the string + strcpy (cstr, scss.c_str()); + // return pointer + return &cstr[0]; + + } + // EO sass2scss + +} +// EO namespace + +// implement for c +extern "C" +{ + + char* ADDCALL sass2scss (const char* sass, const int options) + { + return Sass::sass2scss(sass, options); + } + + // Get compiled sass2scss version + const char* ADDCALL sass2scss_version(void) { + return SASS2SCSS_VERSION; + } + +} diff --git a/src/libsass/sass_context.cpp b/src/libsass/sass_context.cpp new file mode 100644 index 000000000..7a0a49ce1 --- /dev/null +++ b/src/libsass/sass_context.cpp @@ -0,0 +1,799 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include + +#include "sass.h" +#include "ast.hpp" +#include "file.hpp" +#include "json.hpp" +#include "util.hpp" +#include "context.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" +#include "ast_fwd_decl.hpp" +#include "error_handling.hpp" + +#define LFEED "\n" + +// C++ helper +namespace Sass { + // see sass_copy_c_string(std::string str) + static inline JsonNode* json_mkstream(const std::stringstream& stream) + { + // hold on to string on stack! + std::string str(stream.str()); + return json_mkstring(str.c_str()); + } + + static int handle_error(Sass_Context* c_ctx) { + try { + throw; + } + catch (Exception::Base& e) { + std::stringstream msg_stream; + std::string cwd(Sass::File::get_cwd()); + std::string msg_prefix(e.errtype()); + bool got_newline = false; + msg_stream << msg_prefix << ": "; + const char* msg = e.what(); + while (msg && *msg) { + if (*msg == '\r') { + got_newline = true; + } + else if (*msg == '\n') { + got_newline = true; + } + else if (got_newline) { + msg_stream << std::string(msg_prefix.size() + 2, ' '); + got_newline = false; + } + msg_stream << *msg; + ++msg; + } + if (!got_newline) msg_stream << "\n"; + + if (e.traces.empty()) { + // we normally should have some traces, still here as a fallback + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << std::string(msg_prefix.size() + 2, ' '); + msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; + } + else { + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << traces_to_string(e.traces, " "); + } + + // now create the code trace (ToDo: maybe have util functions?) + if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { + size_t lines = e.pstate.line; + const char* line_beg = e.pstate.src; + // scan through src until target line + // move line_beg pointer to line start + while (line_beg && *line_beg && lines != 0) { + if (*line_beg == '\n') --lines; + utf8::unchecked::next(line_beg); + } + const char* line_end = line_beg; + // move line_end before next newline character + while (line_end && *line_end && *line_end != '\n') { + if (*line_end == '\n') break; + if (*line_end == '\r') break; + utf8::unchecked::next(line_end); + } + if (line_end && *line_end != 0) ++ line_end; + size_t line_len = line_end - line_beg; + size_t move_in = 0; size_t shorten = 0; + size_t left_chars = 42; size_t max_chars = 76; + // reported excerpt should not exceed `max_chars` chars + if (e.pstate.column > line_len) left_chars = e.pstate.column; + if (e.pstate.column > left_chars) move_in = e.pstate.column - left_chars; + if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; + utf8::advance(line_beg, move_in, line_end); + utf8::retreat(line_end, shorten, line_beg); + std::string sanitized; std::string marker(e.pstate.column - move_in, '-'); + utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); + msg_stream << ">> " << sanitized << "\n"; + msg_stream << " " << marker << "^\n"; + } + + JsonNode* json_err = json_mkobject(); + json_append_member(json_err, "status", json_mknumber(1)); + json_append_member(json_err, "file", json_mkstring(e.pstate.path)); + json_append_member(json_err, "line", json_mknumber((double)(e.pstate.line + 1))); + json_append_member(json_err, "column", json_mknumber((double)(e.pstate.column + 1))); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 1; + c_ctx->error_file = sass_copy_c_string(e.pstate.path); + c_ctx->error_line = e.pstate.line + 1; + c_ctx->error_column = e.pstate.column + 1; + c_ctx->error_src = e.pstate.src; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::bad_alloc& ba) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(2)); + json_append_member(json_err, "message", json_mkstring(ba.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(ba.what()); + c_ctx->error_status = 2; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::exception& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(3)); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 3; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::string& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e.c_str())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.c_str()); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (const char* e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e)); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (...) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unknown error occurred" << std::endl; + json_append_member(json_err, "status", json_mknumber(5)); + json_append_member(json_err, "message", json_mkstring("unknown")); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string("unknown"); + c_ctx->error_status = 5; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + return c_ctx->error_status; + } + + // allow one error handler to throw another error + // this can happen with invalid utf8 and json lib + static int handle_errors(Sass_Context* c_ctx) { + try { return handle_error(c_ctx); } + catch (...) { return handle_error(c_ctx); } + } + + static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() + { + + // assert valid pointer + if (compiler == 0) return 0; + // The cpp context must be set by now + Context* cpp_ctx = compiler->cpp_ctx; + Sass_Context* c_ctx = compiler->c_ctx; + // We will take care to wire up the rest + compiler->cpp_ctx->c_compiler = compiler; + compiler->state = SASS_COMPILER_PARSED; + + try { + + // get input/output path from options + std::string input_path = safe_str(c_ctx->input_path); + std::string output_path = safe_str(c_ctx->output_path); + + // maybe skip some entries of included files + // we do not include stdin for data contexts + bool skip = c_ctx->type == SASS_CONTEXT_DATA; + + // dispatch parse call + Block_Obj root(cpp_ctx->parse()); + // abort on errors + if (!root) return 0; + + // skip all prefixed files? (ToDo: check srcmap) + // IMO source-maps should point to headers already + // therefore don't skip it for now. re-enable or + // remove completely once this is tested + size_t headers = cpp_ctx->head_imports; + + // copy the included files on to the context (dont forget to free later) + if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) + throw(std::bad_alloc()); + + // return parsed block + return root; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + +} + +extern "C" { + using namespace Sass; + + static void sass_clear_options (struct Sass_Options* options); + static void sass_reset_options (struct Sass_Options* options); + static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { + // do not overwrite ourself + if (to == from) return; + // free assigned memory + sass_clear_options(to); + // move memory + *to = *from; + // Reset pointers on source + sass_reset_options(from); + } + + #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } + #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } + #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ + { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } + #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) + + #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ + type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } + #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ + type sass_context_take_##option (struct Sass_Context* ctx) \ + { type foo = ctx->option; ctx->option = 0; return foo; } + + + // generic compilation function (not exported, use file/data compile instead) + static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() + { + try { + // register our custom functions + if (c_ctx->c_functions) { + auto this_func_data = c_ctx->c_functions; + while (this_func_data && *this_func_data) { + cpp_ctx->add_c_function(*this_func_data); + ++this_func_data; + } + } + + // register our custom headers + if (c_ctx->c_headers) { + auto this_head_data = c_ctx->c_headers; + while (this_head_data && *this_head_data) { + cpp_ctx->add_c_header(*this_head_data); + ++this_head_data; + } + } + + // register our custom importers + if (c_ctx->c_importers) { + auto this_imp_data = c_ctx->c_importers; + while (this_imp_data && *this_imp_data) { + cpp_ctx->add_c_importer(*this_imp_data); + ++this_imp_data; + } + } + + // reset error status + c_ctx->error_json = 0; + c_ctx->error_text = 0; + c_ctx->error_message = 0; + c_ctx->error_status = 0; + // reset error position + c_ctx->error_src = 0; + c_ctx->error_file = 0; + c_ctx->error_line = std::string::npos; + c_ctx->error_column = std::string::npos; + + // allocate a new compiler instance + void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); + if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } + Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; + compiler->state = SASS_COMPILER_CREATED; + + // store in sass compiler + compiler->c_ctx = c_ctx; + compiler->cpp_ctx = cpp_ctx; + cpp_ctx->c_compiler = compiler; + + // use to parse block + return compiler; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + + // generic compilation function (not exported, use file/data compile instead) + static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) + { + + // prepare sass compiler with context and options + Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); + + try { + // call each compiler step + sass_compiler_parse(compiler); + sass_compiler_execute(compiler); + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + sass_delete_compiler(compiler); + + return c_ctx->error_status; + } + + inline void init_options (struct Sass_Options* options) + { + options->precision = 5; + options->indent = " "; + options->linefeed = LFEED; + } + + Sass_Options* ADDCALL sass_make_options (void) + { + struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); + if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } + init_options(options); + return options; + } + + Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) + { + SharedObj::setTaint(true); // needed for static colors + struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_FILE; + init_options(ctx); + try { + if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } + if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } + sass_option_set_input_path(ctx, input_path); + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) + { + struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_DATA; + init_options(ctx); + try { + if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } + if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } + ctx->source_string = source_string; + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 0; + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_prepare_context(data_ctx, cpp_ctx); + } + + struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 0; + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_prepare_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 1; + if (data_ctx->error_status) + return data_ctx->error_status; + try { + if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } + // empty source string is a valid case, even if not really usefull (different than with file context) + // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } + } + catch (...) { return handle_errors(data_ctx) | 1; } + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_compile_context(data_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 1; + if (file_ctx->error_status) + return file_ctx->error_status; + try { + if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } + if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } + } + catch (...) { return handle_errors(file_ctx) | 1; } + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_compile_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_PARSED) return 0; + if (compiler->state != SASS_COMPILER_CREATED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + // parse the context we have set up (file or data) + compiler->root = sass_parse_block(compiler); + // success + return 0; + } + + int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_EXECUTED) return 0; + if (compiler->state != SASS_COMPILER_PARSED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->root.isNull()) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + compiler->state = SASS_COMPILER_EXECUTED; + Context* cpp_ctx = compiler->cpp_ctx; + Block_Obj root = compiler->root; + // compile the parsed root block + try { compiler->c_ctx->output_string = cpp_ctx->render(root); } + // pass catched errors to generic error handler + catch (...) { return handle_errors(compiler->c_ctx) | 1; } + // generate source map json and store on context + compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); + // success + return 0; + } + + // helper function, not exported, only accessible locally + static void sass_reset_options (struct Sass_Options* options) + { + // free pointer before + // or copy/move them + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + options->extensions = 0; + } + + // helper function, not exported, only accessible locally + static void sass_clear_options (struct Sass_Options* options) + { + if (options == 0) return; + // Deallocate custom functions, headers and importes + sass_delete_function_list(options->c_functions); + sass_delete_importer_list(options->c_importers); + sass_delete_importer_list(options->c_headers); + // Deallocate inc paths + if (options->plugin_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->plugin_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Deallocate inc paths + if (options->include_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->include_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Deallocate extension + if (options->extensions) { + struct string_list* cur; + struct string_list* next; + cur = options->extensions; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Free options strings + free(options->input_path); + free(options->output_path); + free(options->plugin_path); + free(options->include_path); + free(options->source_map_file); + free(options->source_map_root); + // Reset our pointers + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + options->extensions = 0; + } + + // helper function, not exported, only accessible locally + // sass_free_context is also defined in old sass_interface + static void sass_clear_context (struct Sass_Context* ctx) + { + if (ctx == 0) return; + // release the allocated memory (mostly via sass_copy_c_string) + if (ctx->output_string) free(ctx->output_string); + if (ctx->source_map_string) free(ctx->source_map_string); + if (ctx->error_message) free(ctx->error_message); + if (ctx->error_text) free(ctx->error_text); + if (ctx->error_json) free(ctx->error_json); + if (ctx->error_file) free(ctx->error_file); + free_string_array(ctx->included_files); + // play safe and reset properties + ctx->output_string = 0; + ctx->source_map_string = 0; + ctx->error_message = 0; + ctx->error_text = 0; + ctx->error_json = 0; + ctx->error_file = 0; + ctx->included_files = 0; + // debug leaked memory + #ifdef DEBUG_SHARED_PTR + SharedObj::dumpMemLeaks(); + #endif + // now clear the options + sass_clear_options(ctx); + } + + void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) + { + if (compiler == 0) { + return; + } + Context* cpp_ctx = compiler->cpp_ctx; + if (cpp_ctx) delete(cpp_ctx); + compiler->cpp_ctx = NULL; + compiler->c_ctx = NULL; + compiler->root = NULL; + free(compiler); + } + + void ADDCALL sass_delete_options (struct Sass_Options* options) + { + sass_clear_options(options); free(options); + } + + // Deallocate all associated memory with file context + void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) + { + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + // Deallocate all associated memory with data context + void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) + { + // clean the source string if it was not passed + // we reset this member once we start parsing + if (ctx->source_string) free(ctx->source_string); + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + + // Getters for sass context from specific implementations + struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } + + // Getters for context options from Sass_Context + struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } + void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + + // Getters for Sass_Compiler options (get conected sass context) + enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } + struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + // Getters for Sass_Compiler options (query import stack) + size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } + Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } + Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } + // Getters for Sass_Compiler options (query function stack) + size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } + + // Calculate the size of the stored null terminated array + size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) + { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } + + // Create getter and setters for options + IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); + IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); + + // Create getter and setters for context + IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); + IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); + IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); + IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); + + // Take ownership of memory (value on context is set to 0) + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); + IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); + + // Push function for import extenions + void ADDCALL sass_option_push_import_extension(struct Sass_Options* options, const char* ext) + { + struct string_list* extension = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (extension == 0) return; + extension->string = ext ? sass_copy_c_string(ext) : 0; + struct string_list* last = options->extensions; + if (!options->extensions) { + options->extensions = extension; + } else { + while (last->next) + last = last->next; + last->next = extension; + } + } + + // Push function for include paths (no manipulation support for now) + void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) + { + + struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (include_path == 0) return; + include_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->include_paths; + if (!options->include_paths) { + options->include_paths = include_path; + } else { + while (last->next) + last = last->next; + last->next = include_path; + } + + } + + // Push function for include paths (no manipulation support for now) + size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) + { + size_t len = 0; + struct string_list* cur = options->include_paths; + while (cur) { len ++; cur = cur->next; } + return len; + } + + // Push function for include paths (no manipulation support for now) + const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) + { + struct string_list* cur = options->include_paths; + while (i) { i--; cur = cur->next; } + return cur->string; + } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) + { + + struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (plugin_path == 0) return; + plugin_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->plugin_paths; + if (!options->plugin_paths) { + options->plugin_paths = plugin_path; + } else { + while (last->next) + last = last->next; + last->next = plugin_path; + } + + } + +} diff --git a/src/libsass/sass_context.hpp b/src/libsass/sass_context.hpp new file mode 100644 index 000000000..9d192a301 --- /dev/null +++ b/src/libsass/sass_context.hpp @@ -0,0 +1,132 @@ +#ifndef SASS_SASS_CONTEXT_H +#define SASS_SASS_CONTEXT_H + +#include "sass/base.h" +#include "sass/context.h" +#include "ast_fwd_decl.hpp" + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. LibSass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Maybe use array interface instead? + char* extension; + char* include_path; + char* plugin_path; + + // Extensions (linked string list) + struct string_list* extensions; + // Include paths (linked string list) + struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; + + // Path to source map file + // Enables source map generation + // Used to create sourceMappingUrl + char* source_map_file; + + // Directly inserted in source maps + char* source_map_root; + + // Custom functions that can be called from sccs code + Sass_Function_List c_functions; + + // List of custom importers + Sass_Importer_List c_importers; + + // List of custom headers + Sass_Importer_List c_headers; + +}; + + +// base for all contexts +struct Sass_Context : Sass_Options +{ + + // store context type info + enum Sass_Input_Style type; + + // generated output data + char* output_string; + + // generated source map json + char* source_map_string; + + // error status + int error_status; + char* error_json; + char* error_text; + char* error_message; + // error position + char* error_file; + size_t error_line; + size_t error_column; + const char* error_src; + + // report imported files + char** included_files; + +}; + +// struct for file compilation +struct Sass_File_Context : Sass_Context { + + // no additional fields required + // input_path is already on options + +}; + +// struct for data compilation +struct Sass_Data_Context : Sass_Context { + + // provided source string + char* source_string; + char* srcmap_string; + +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + Sass::Context* cpp_ctx; + // Sass::Block + Sass::Block_Obj root; +}; + +#endif diff --git a/src/libsass/setup-environment.md b/src/libsass/setup-environment.md new file mode 100644 index 000000000..805613656 --- /dev/null +++ b/src/libsass/setup-environment.md @@ -0,0 +1,68 @@ +## Requirements +In order to install and setup your local development environment, there are some prerequisites: + +* git +* gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) +* ruby w/ bundler + +OS X: +First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: + +```` +xcode-select --install +```` + +## Cloning the Projects + +First, clone the project and then add a line to your `~/.bash_profile` that will let other programs know where the LibSass dev files are. + +```` +git clone git@github.com:sass/libsass.git +cd libsass +echo "export SASS_LIBSASS_PATH=$(pwd)" >> ~/.bash_profile + +```` + +Then, if you run the "bootstrap" script, it should clone all the other required projects. + +```` +./script/bootstrap +```` + +You should now have a `sass-spec` and `sassc` folder within the libsass folder. Both of these are clones of their respective git projects. If you want to do a pull request, remember to work in those folders. For instance, if you want to add a test (see other documentation for how to do that), make sure to commit it to your *fork* of the sass-spec github project. Also, whenever you are running tests, make sure to `pull` from the origin! We want to make sure we are testing against the newest libsass, sassc, and sass-spec! + +Now, try and see if you can build the project. We do that with the `make` command. + +```` +make +```` + +At this point, if you get an error, something is most likely wrong with your compiler installation. Yikes. It's hard to cover how to fix this in an article. Feel free to open an issue and we'll try and help! But, remember, before you do that, googling the error message is your friend! Many problems are solved quickly that way. + +## Running The Spec Against LibSass + +Then, to run the spec against LibSass, just run: + +```` +./script/spec +```` + +If you get an error about `SASS_LIBSASS_PATH`, you may still need to set a variable pointing to the libsass folder, like this: + +```` +export SASS_LIBSASS_PATH=/Users/you/path/libsass +```` + +...where the latter part is to the `libsass` directory you've cloned. You can get this path by typing `pwd` in the Terminal + +## Running the Spec Against Ruby Sass + +Go into the sass-spec folder that should have been cloned earlier with the "bootstrap" command. Run the following. + +```` +bundle install +./sass-spec.rb +```` + +Voila! Now you are testing against Sass too! + diff --git a/src/libsass/source-map-internals.md b/src/libsass/source-map-internals.md new file mode 100644 index 000000000..50f83b54f --- /dev/null +++ b/src/libsass/source-map-internals.md @@ -0,0 +1,51 @@ +This document is mainly intended for developers! + +# Documenting some of the source map internals + +Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. + +The main storage for SourceMap mappings is the `mappings` vector: + +``` +# in source_map.hpp +vector mappings +# in mappings.hpp +struct Mapping ... + Position original_position; + Position generated_position; +``` + +## Every parsed token has its source associated + +LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: + +``` +if (lex< custom_property_name >()) { + Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); + return new (ctx.mem) Declaration(path, prop->position(), prop, ...); +} +``` + +## How is the `source_position` calculated + +This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! + +``` +lex< exactly < empty_str > >(); +end = new (ctx.mem) String_Constant(path, source_position, lexed); +``` + +## How are mappings for the output created + +So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: + +``` +# in source_map.hpp +void add_mapping(AST_Node* node); +``` + +This method is called in two places: +- `Inspect::append_to_buffer` +- `Output_[Nested|Compressed]::append_to_buffer` + +Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/src/libsass/tap-runner b/src/libsass/tap-runner new file mode 100755 index 000000000..56c13bfb4 --- /dev/null +++ b/src/libsass/tap-runner @@ -0,0 +1 @@ +$@ $TEST_FLAGS --tap --silent | tapout tap diff --git a/src/libsass/trace.md b/src/libsass/trace.md new file mode 100644 index 000000000..4a57c901f --- /dev/null +++ b/src/libsass/trace.md @@ -0,0 +1,26 @@ +## This is proposed interface in https://github.com/sass/libsass/pull/1288 + +Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. + +Both macros simulate a string stream, so they can be used like this: + + TRACE() << "Reached."; + +produces: + + [LibSass] parse_value parser.cpp:1384 Reached. + +`TRACE()` + logs function name, source filename, source file name to the standard error and the attached + stream to the standard error. + +`TRACEINST(obj)` + logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: + + TRACEINST(this) << "String_Constant created " << this; + +produces: + + [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") + +The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/src/libsass/triage.md b/src/libsass/triage.md new file mode 100644 index 000000000..0fc11784c --- /dev/null +++ b/src/libsass/triage.md @@ -0,0 +1,17 @@ +This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: + +1. Issue is reported by a user. +2. If the issue seems like a bug, then the "bug" tag is added. +3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. +4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. +5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. +6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! +7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. +8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". +9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". +10. A C++ developer will then work on the issue and issue a pull request to fix the issue. +11. A core member verifies that the fix does actually fix the spec tests. +12. The fix is merged into the project. +13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues +14. The issue is closed +15. Have a soda pop or enjoyable beverage of your choice diff --git a/src/libsass/unicode.md b/src/libsass/unicode.md new file mode 100644 index 000000000..a1eb5b1cf --- /dev/null +++ b/src/libsass/unicode.md @@ -0,0 +1,45 @@ +LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your +input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected +behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! + +### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) + +This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. + +Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). + +### Current status on LibSass unicode support + +LibSass should/is fully UTF (and therefore plain ASCII) compatible. + +~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ + +LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. + +### Current encoding auto detection + +LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! + +### What is currently not supported + +- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) +- Using non ASCII characters in different encodings in different includes + +### What is missing to support the above cases + +- A way to convert between encodings (like libiconv/ICU) +- Sniffing the charset inside the file (source is available) +- Handling the conversion on import (and export) +- Optional: Make output encoding configurable +- Optional: Add optional/mandatory BOM (configurable) + +### Low priority feature + +I guess the current implementation should handle more than 99% of all real world use cases. +A) Unicode characters are still seldomly seen (as they can be written escaped) +~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. +Although I'm not sure how this applies to asian and other "exotic" codepages!~~ + +I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). + +I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/src/libsass/util.hpp b/src/libsass/util.hpp new file mode 100644 index 000000000..f23475fe0 --- /dev/null +++ b/src/libsass/util.hpp @@ -0,0 +1,56 @@ +#ifndef SASS_UTIL_H +#define SASS_UTIL_H + +#include +#include +#include +#include "sass.hpp" +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#define SASS_ASSERT(cond, msg) assert(cond && msg) + +namespace Sass { + + double round(double val, size_t precision = 0); + double sass_strtod(const char* str); + const char* safe_str(const char *, const char* = ""); + void free_string_array(char **); + char **copy_strings(const std::vector&, char ***, int = 0); + std::string read_css_string(const std::string& str, bool css = true); + std::string evacuate_escapes(const std::string& str); + std::string string_to_output(const std::string& str); + std::string comment_to_string(const std::string& text); + std::string read_hex_escapes(const std::string& str); + std::string escape_string(const std::string& str); + void newline_to_space(std::string& str); + + std::string quote(const std::string&, char q = 0); + std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); + char detect_best_quotemark(const char* s, char qm = '"'); + + bool is_hex_doublet(double n); + bool is_color_doublet(double r, double g, double b); + + bool peek_linefeed(const char* start); + + namespace Util { + + std::string rtrim(const std::string& str); + + std::string normalize_underscores(const std::string& str); + std::string normalize_decimals(const std::string& str); + + bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); + bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); + bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); + bool isAscii(const char chr); + + } +} +#endif From 4a858f1e42cdf3fbd8773f1d3127399971b0bc3f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 24 Apr 2018 20:25:42 +1000 Subject: [PATCH 126/286] Respect quiet option in Node API This is an extension of #2268. Catches a few places missed the first time around. --- bin/node-sass | 6 ++++++ lib/render.js | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/node-sass b/bin/node-sass index 62abdb229..5f1a8d650 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -161,6 +161,12 @@ function getEmitter() { } }); + emitter.on('info', function(data) { + if (!options.quiet) { + console.info(data); + } + }); + emitter.on('log', stdout.write.bind(stdout)); return emitter; diff --git a/lib/render.js b/lib/render.js index d7a45d78f..858e02e74 100644 --- a/lib/render.js +++ b/lib/render.js @@ -64,7 +64,7 @@ module.exports = function(options, emitter) { return done(); } - emitter.emit('warn', chalk.green('Rendering Complete, saving .css file...')); + emitter.emit('info', chalk.green('Rendering Complete, saving .css file...')); mkdirp(path.dirname(destination), function(err) { if (err) { @@ -76,7 +76,7 @@ module.exports = function(options, emitter) { return emitter.emit('error', chalk.red(err)); } - emitter.emit('warn', chalk.green('Wrote CSS to ' + destination)); + emitter.emit('info', chalk.green('Wrote CSS to ' + destination)); emitter.emit('write', err, destination, result.css.toString()); done(); }); @@ -94,7 +94,7 @@ module.exports = function(options, emitter) { return emitter.emit('error', chalk.red('Error' + err)); } - emitter.emit('warn', chalk.green('Wrote Source Map to ' + sourceMap)); + emitter.emit('info', chalk.green('Wrote Source Map to ' + sourceMap)); emitter.emit('write-source-map', err, sourceMap, result.map); done(); }); From 5e10a9b9df87db448374bd7e78ffb3ba2699aff6 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 24 Apr 2018 22:48:33 +1000 Subject: [PATCH 127/286] 4.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 313587830..451f8df52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.8.3", + "version": "4.9.0", "libsass": "3.5.3", "description": "Wrapper around libsass", "license": "MIT", From eb5ad0a7b8b98a35d752c3dea3573c5fb491be8e Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Apr 2018 01:52:56 +1000 Subject: [PATCH 128/286] Revert "Bump LibSass@3.5.3 (#2342)" This reverts commit 8004d10773085147ecf09ccb75b08efd9b46e554. --- package.json | 4 +- src/libsass/GNUmakefile.am | 56 +- src/libsass/Readme.md | 110 +- src/libsass/api-context-example.md | 45 - src/libsass/api-context-internal.md | 163 -- src/libsass/api-context.md | 295 -- src/libsass/api-doc.md | 215 -- src/libsass/api-function-example.md | 67 - src/libsass/api-function-internal.md | 8 - src/libsass/api-function.md | 74 - src/libsass/api-importer-example.md | 112 - src/libsass/api-importer-internal.md | 20 - src/libsass/api-importer.md | 86 - src/libsass/api-value-example.md | 55 - src/libsass/api-value-internal.md | 76 - src/libsass/api-value.md | 154 -- src/libsass/build-on-darwin.md | 27 - src/libsass/build-on-gentoo.md | 55 - src/libsass/build-on-windows.md | 139 - src/libsass/build-shared-library.md | 35 - src/libsass/build-with-autotools.md | 78 - src/libsass/build-with-makefiles.md | 68 - src/libsass/build-with-mingw.md | 107 - src/libsass/build-with-visual-studio.md | 90 - src/libsass/build.md | 97 - src/libsass/compatibility-plan.md | 48 - src/libsass/configure.ac | 20 +- src/libsass/context.cpp | 926 ------- src/libsass/context.h | 173 -- src/libsass/context.hpp | 155 -- src/libsass/contributing.md | 17 - src/libsass/cssize.cpp | 606 ----- src/libsass/custom-functions-internal.md | 122 - src/libsass/debugger.hpp | 801 ------ src/libsass/dev-ast-memory.md | 223 -- src/libsass/eval.cpp | 1663 ------------ src/libsass/expand.cpp | 817 ------ src/libsass/file.cpp | 485 ---- src/libsass/file.hpp | 139 - src/libsass/functions.cpp | 2234 --------------- src/libsass/implementations.md | 65 - src/libsass/inspect.cpp | 1138 -------- src/libsass/operators.cpp | 240 -- src/libsass/parser.cpp | 3137 ---------------------- src/libsass/parser.hpp | 400 --- src/libsass/plugins.md | 47 - src/libsass/prelexer.cpp | 1774 ------------ src/libsass/sass.cpp | 151 -- src/libsass/sass2scss.cpp | 895 ------ src/libsass/sass_context.cpp | 799 ------ src/libsass/sass_context.hpp | 132 - src/libsass/setup-environment.md | 68 - src/libsass/source-map-internals.md | 51 - src/libsass/tap-runner | 1 - src/libsass/trace.md | 26 - src/libsass/triage.md | 17 - src/libsass/unicode.md | 45 - src/libsass/util.hpp | 56 - 58 files changed, 146 insertions(+), 19561 deletions(-) delete mode 100644 src/libsass/api-context-example.md delete mode 100644 src/libsass/api-context-internal.md delete mode 100644 src/libsass/api-context.md delete mode 100644 src/libsass/api-doc.md delete mode 100644 src/libsass/api-function-example.md delete mode 100644 src/libsass/api-function-internal.md delete mode 100644 src/libsass/api-function.md delete mode 100644 src/libsass/api-importer-example.md delete mode 100644 src/libsass/api-importer-internal.md delete mode 100644 src/libsass/api-importer.md delete mode 100644 src/libsass/api-value-example.md delete mode 100644 src/libsass/api-value-internal.md delete mode 100644 src/libsass/api-value.md delete mode 100644 src/libsass/build-on-darwin.md delete mode 100644 src/libsass/build-on-gentoo.md delete mode 100644 src/libsass/build-on-windows.md delete mode 100644 src/libsass/build-shared-library.md delete mode 100644 src/libsass/build-with-autotools.md delete mode 100644 src/libsass/build-with-makefiles.md delete mode 100644 src/libsass/build-with-mingw.md delete mode 100644 src/libsass/build-with-visual-studio.md delete mode 100644 src/libsass/build.md delete mode 100644 src/libsass/compatibility-plan.md delete mode 100644 src/libsass/context.cpp delete mode 100644 src/libsass/context.h delete mode 100644 src/libsass/context.hpp delete mode 100644 src/libsass/contributing.md delete mode 100644 src/libsass/cssize.cpp delete mode 100644 src/libsass/custom-functions-internal.md delete mode 100644 src/libsass/debugger.hpp delete mode 100644 src/libsass/dev-ast-memory.md delete mode 100644 src/libsass/eval.cpp delete mode 100644 src/libsass/expand.cpp delete mode 100644 src/libsass/file.cpp delete mode 100644 src/libsass/file.hpp delete mode 100644 src/libsass/functions.cpp delete mode 100644 src/libsass/implementations.md delete mode 100644 src/libsass/inspect.cpp delete mode 100644 src/libsass/operators.cpp delete mode 100644 src/libsass/parser.cpp delete mode 100644 src/libsass/parser.hpp delete mode 100644 src/libsass/plugins.md delete mode 100644 src/libsass/prelexer.cpp delete mode 100644 src/libsass/sass.cpp delete mode 100644 src/libsass/sass2scss.cpp delete mode 100644 src/libsass/sass_context.cpp delete mode 100644 src/libsass/sass_context.hpp delete mode 100644 src/libsass/setup-environment.md delete mode 100644 src/libsass/source-map-internals.md delete mode 100755 src/libsass/tap-runner delete mode 100644 src/libsass/trace.md delete mode 100644 src/libsass/triage.md delete mode 100644 src/libsass/unicode.md delete mode 100644 src/libsass/util.hpp diff --git a/package.json b/package.json index 451f8df52..466e3ab63 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.9.0", - "libsass": "3.5.3", + "libsass": "3.5.2", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.1", + "sass-spec": "^3.5.1", "unique-temp-dir": "^1.0.0" } } diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am index d197261e7..9e658a415 100644 --- a/src/libsass/GNUmakefile.am +++ b/src/libsass/GNUmakefile.am @@ -25,49 +25,63 @@ else AM_CXXFLAGS += -std=c++0x endif -TEST_EXTENSIONS = .rb - if ENABLE_TESTS -SASS_SASSC_PATH ?= $(top_srcdir)/sassc -SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec - noinst_PROGRAMS = tester + tester_LDADD = src/libsass.la +tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c +tester_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` +tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" +tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" tester_LDFLAGS = $(AM_LDFLAGS) -nodist_tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c -SASS_SASSC_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` -tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" -tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" if ENABLE_COVERAGE nodist_EXTRA_tester_SOURCES = non-existent-file-to-force-CXX-linking.cxx endif -TESTS = $(SASS_SPEC_PATH)/sass-spec.rb -RB_LOG_COMPILER = ./script/tap-runner -AM_RB_LOG_FLAGS = $(RUBY) +SASS_SASSC_PATH ?= $(top_srcdir)/sassc +SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec + +TESTS = \ + $(SASS_SPEC_PATH)/spec/basic \ + $(SASS_SPEC_PATH)/spec/css \ + $(SASS_SPEC_PATH)/spec/extend-tests \ + $(SASS_SPEC_PATH)/spec/extends \ + $(SASS_SPEC_PATH)/spec/libsass \ + $(SASS_SPEC_PATH)/spec/libsass-closed-issues \ + $(SASS_SPEC_PATH)/spec/maps \ + $(SASS_SPEC_PATH)/spec/misc \ + $(SASS_SPEC_PATH)/spec/regressions \ + $(SASS_SPEC_PATH)/spec/scss \ + $(SASS_SPEC_PATH)/spec/scss-tests \ + $(SASS_SPEC_PATH)/spec/types SASS_TEST_FLAGS = -V 3.5 --impl libsass -SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH) -SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) ./script/tap-driver +AM_LOG_FLAGS = -c ./tester $(LOG_FLAGS) +if USE_TAP + AM_LOG_FLAGS += -t + SASS_TEST_FLAGS += -t | tapout + LOG_COMPILER = ./script/tap-runner $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb +else + LOG_COMPILER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb +endif SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb +SASS_TESTER += -c $(top_srcdir)/tester$(EXEEXT) test: - $(SASS_TESTER) $(SASS_TEST_FLAGS) + $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) test_build: - $(SASS_TESTER) $(SASS_TEST_FLAGS) + $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) test_full: - $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) + $(SASS_TESTER) --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) test_probe: - $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) - -.PHONY: test test_build test_full test_probe + $(SASS_TESTER) --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) endif diff --git a/src/libsass/Readme.md b/src/libsass/Readme.md index a233fae48..908de2dc4 100644 --- a/src/libsass/Readme.md +++ b/src/libsass/Readme.md @@ -1,20 +1,104 @@ -Welcome to the LibSass documentation! +LibSass - Sass compiler written in C++ +====================================== -## First Off -LibSass is just a library. To run the code locally (i.e. to compile your stylesheets), you need an implementer. SassC (get it?) is an implementer written in C. There are a number of other implementations of LibSass - for example Node. We encourage you to write your own port - the whole point of LibSass is that we want to bring Sass to many other languages, not just Ruby! +Currently maintained by Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) +Originally created by Aaron Leung ([@akhleung]) and Hampton Catlin ([@hcatlin]) -We're working hard on moving to full parity with Ruby Sass... learn more at the [The-LibSass-Compatibility-Plan](compatibility-plan.md)! +[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass "Travis CI") +[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master "Appveyor CI") +[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3 "Code coverage of spec tests") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Percentage of issues still open") +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Average time to resolve an issue") +[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE "Bountysource") +[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/ "Slack communication channels") -### Implementing LibSass -If you're interested in implementing LibSass in your own project see the [API Documentation](api-doc.md) which now includes implementing -your own [Sass functions](api-function.md). You may wish to [look at other implementations](implementations.md) for your language of choice. -Or make your own! +[LibSass](https://github.com/sass/libsass "LibSass GitHub Project") is just a library! +If you want to use LibSass to compile Sass, you need an implementer. Some +implementations are only bindings into other programming languages. But most also +ship with a command line interface (CLI) you can use directly. There is also +[SassC](https://github.com/sass/sassc), which is the official lightweight +CLI tool built by the same people as LibSass. -### Contributing to LibSass +### Excerpt of "sanctioned" implementations: -| Issue Tracker | Issue Triage | Community Guidelines | -|-------------------|----------------------------------|-----------------------------| -| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [[Sass Community Guidelines|http://sass-lang.com/community-guidelines]]. Be nice and everyone else will be nice too! | +- https://github.com/sass/node-sass (Node.js) +- https://github.com/sass/perl-libsass (Perl) +- https://github.com/sass/libsass-python (Python) +- https://github.com/wellington/go-libsass (Go) +- https://github.com/sass/sassc-ruby (Ruby) +- https://github.com/sass/libsass-net (C#) +- https://github.com/medialize/sass.js (JS) +- https://github.com/bit3/jsass (Java) -Please refer to the steps on [Building LibSass](build.md) +This list does not say anything about the quality of either the listed or not listed [implementations](docs/implementations.md)! +The authors of the listed projects above are just known to work regularly together with LibSass developers. + +About +----- + +LibSass is a C++ port of the original Ruby Sass CSS compiler with a [C API](docs/api-doc.md). +We coded LibSass with portability and efficiency in mind. You can expect LibSass to be a lot +faster than Ruby Sass and on par or faster than the best alternative CSS compilers around. + +Developing +---------- + +As noted above, the LibSass repository does not contain any binaries or other way to execute +LibSass. Therefore, you need an implementer to develop LibSass. Easiest is to start with +the official [SassC](http://github.com/sass/sassc) CLI wrapper. It is *guaranteed* to compile +with the latest code in LibSass master, since it is also used in the CI process. There is no +limitation here, as you may use any other LibSass implementer to test your LibSass branch! + +Testing +------- + +Since LibSass is a pure library, tests are run through the [Sass-Spec](https://github.com/sass/sass-spec) +project using the [SassC](http://github.com/sass/sassc) CLI wrapper. To run the tests against LibSass while +developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and +then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. +Note that the scripts in the `./script` folder are mainly intended for our CI needs. + +Building +-------- + +To build LibSass you need GCC 4.6+ or Clang/LLVM. If your OS is older, you may need to upgrade +them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.6+ or VS 2013 +Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows with various build chains +and/or command line interpreters. + +See the [build docs for further instructions](docs/build.md)! + +Compatibility +------------- + +Current LibSass 3.4 should be compatible with Sass 3.4. Please refer to the [sass compatibility +page](http://sass-compatibility.github.io/) for a more detailed comparison. But note that there +are still a few incomplete edges which we are aware of. Otherwise LibSass has reached a good level +of stability, thanks to our ever growing [Sass-Spec test suite](https://github.com/sass/sass-spec). + +About Sass +---------- + +Sass is a CSS pre-processor language to add on exciting, new, awesome features to CSS. Sass was +the first language of its kind and by far the most mature and up to date codebase. + +Sass was originally conceived of by the co-creator of this library, Hampton Catlin ([@hcatlin]). +Most of the language has been the result of years of work by Natalie Weizenbaum ([@nex3]) and +Chris Eppstein ([@chriseppstein]). + +For more information about Sass itself, please visit http://sass-lang.com + +Initial development of LibSass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). + +Licensing +--------- + +Our [MIT license](LICENSE) is designed to be as simple and liberal as possible. + +[@hcatlin]: https://github.com/hcatlin +[@akhleung]: https://github.com/akhleung +[@chriseppstein]: https://github.com/chriseppstein +[@nex3]: https://github.com/nex3 +[@mgreter]: https://github.com/mgreter +[@xzyfer]: https://github.com/xzyfer diff --git a/src/libsass/api-context-example.md b/src/libsass/api-context-example.md deleted file mode 100644 index 4f2a2a0ce..000000000 --- a/src/libsass/api-context-example.md +++ /dev/null @@ -1,45 +0,0 @@ -## Example main.c - -```C -#include -#include "sass/context.h" - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // configure some options ... - sass_option_set_precision(ctx_opt, 10); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -### Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: 21px * 2; }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` - diff --git a/src/libsass/api-context-internal.md b/src/libsass/api-context-internal.md deleted file mode 100644 index 1a2818b34..000000000 --- a/src/libsass/api-context-internal.md +++ /dev/null @@ -1,163 +0,0 @@ -```C -// Input behaviours -enum Sass_Input_Style { - SASS_CONTEXT_NULL, - SASS_CONTEXT_FILE, - SASS_CONTEXT_DATA, - SASS_CONTEXT_FOLDER -}; - -// sass config options structure -struct Sass_Inspect_Options { - - // Output style for the generated css code - // A value from above SASS_STYLE_* constants - enum Sass_Output_Style output_style; - - // Precision for fractional numbers - int precision; - -}; - -// sass config options structure -struct Sass_Output_Options : Sass_Inspect_Options { - - // String to be used for indentation - const char* indent; - // String to be used to for line feeds - const char* linefeed; - - // Emit comments in the generated CSS indicating - // the corresponding source line. - bool source_comments; - -}; - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* include_path; - char* plugin_path; - - // Include paths (linked string list) - struct string_list* include_paths; - // Plugin paths (linked string list) - struct string_list* plugin_paths; - - // Path to source map file - // Enables source map generation - // Used to create sourceMappingUrl - char* source_map_file; - - // Directly inserted in source maps - char* source_map_root; - - // Custom functions that can be called from sccs code - Sass_Function_List c_functions; - - // Callback to overload imports - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - const char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; -``` - diff --git a/src/libsass/api-context.md b/src/libsass/api-context.md deleted file mode 100644 index dfd10c181..000000000 --- a/src/libsass/api-context.md +++ /dev/null @@ -1,295 +0,0 @@ -Sass Contexts come in two flavors: - -- `Sass_File_Context` -- `Sass_Data_Context` - -### Basic Usage - -```C -#include "sass/context.h" -``` - -***Sass_Options*** - -```C -// Precision for fractional numbers -int precision; -``` -```C -// Output style for the generated css code -// A value from above SASS_STYLE_* constants -int output_style; -``` -```C -// Emit comments in the generated CSS indicating -// the corresponding source line. -bool source_comments; -``` -```C -// embed sourceMappingUrl as data uri -bool source_map_embed; -``` -```C -// embed include contents in maps -bool source_map_contents; -``` -```C -// create file urls for sources -bool source_map_file_urls; -``` -```C -// Disable sourceMappingUrl in css output -bool omit_source_map_url; -``` -```C -// Treat source_string as sass (as opposed to scss) -bool is_indented_syntax_src; -``` -```C -// The input path is used for source map -// generating. It can be used to define -// something with string compilation or to -// overload the input file path. It is -// set to "stdin" for data contexts and -// to the input file on file contexts. -char* input_path; -``` -```C -// The output path is used for source map -// generating. LibSass will not write to -// this file, it is just used to create -// information in source-maps etc. -char* output_path; -``` -```C -// String to be used for indentation -const char* indent; -``` -```C -// String to be used to for line feeds -const char* linefeed; -``` -```C -// Colon-separated list of paths -// Semicolon-separated on Windows -char* include_path; -char* plugin_path; -``` -```C -// Additional include paths -// Must be null delimited -char** include_paths; -char** plugin_paths; -``` -```C -// Path to source map file -// Enables the source map generating -// Used to create sourceMappingUrl -char* source_map_file; -``` -```C -// Directly inserted in source maps -char* source_map_root; -``` -```C -// Custom functions that can be called from Sass code -Sass_C_Function_List c_functions; -``` -```C -// Callback to overload imports -Sass_C_Import_Callback importer; -``` - -***Sass_Context*** - -```C -// store context type info -enum Sass_Input_Style type; -```` -```C -// generated output data -char* output_string; -``` -```C -// generated source map json -char* source_map_string; -``` -```C -// error status -int error_status; -char* error_json; -char* error_text; -char* error_message; -// error position -char* error_file; -size_t error_line; -size_t error_column; -``` -```C -// report imported files -char** included_files; -``` - -***Sass_File_Context*** - -```C -// no additional fields required -// input_path is already on options -``` - -***Sass_Data_Context*** - -```C -// provided source string -char* source_string; -``` - -### Sass Context API - -```C -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Create and initialize an option struct -struct Sass_Options* sass_make_options (void); -// Create and initialize a specific context -struct Sass_File_Context* sass_make_file_context (const char* input_path); -struct Sass_Data_Context* sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -int sass_compile_file_context (struct Sass_File_Context* ctx); -int sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ctx); -struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Usefull if you only want to query the included files -int sass_compiler_parse (struct Sass_Compiler* compiler); -int sass_compiler_execute (struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -void sass_delete_compiler (struct Sass_Compiler* compiler); -void sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -void sass_delete_file_context (struct Sass_File_Context* ctx); -void sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for Context from specific implementation -struct Sass_Context* sass_file_context_get_context (struct Sass_File_Context* file_ctx); -struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - -// Getters for Context_Options from Sass_Context -struct Sass_Options* sass_context_get_options (struct Sass_Context* ctx); -struct Sass_Options* sass_file_context_get_options (struct Sass_File_Context* file_ctx); -struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* data_ctx); -void sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -void sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - -// Getters for Sass_Context values -const char* sass_context_get_output_string (struct Sass_Context* ctx); -int sass_context_get_error_status (struct Sass_Context* ctx); -const char* sass_context_get_error_json (struct Sass_Context* ctx); -const char* sass_context_get_error_text (struct Sass_Context* ctx); -const char* sass_context_get_error_message (struct Sass_Context* ctx); -const char* sass_context_get_error_file (struct Sass_Context* ctx); -size_t sass_context_get_error_line (struct Sass_Context* ctx); -size_t sass_context_get_error_column (struct Sass_Context* ctx); -const char* sass_context_get_source_map_string (struct Sass_Context* ctx); -char** sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options (query import stack) -size_t sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -Sass_Import_Entry sass_compiler_get_last_import(struct Sass_Compiler* compiler); -Sass_Import_Entry sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -// Getters for Sass_Compiler options (query function stack) -size_t sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -Sass_Callee_Entry sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -Sass_Callee_Entry sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// Take ownership of memory (value on context is set to 0) -char* sass_context_take_error_json (struct Sass_Context* ctx); -char* sass_context_take_error_text (struct Sass_Context* ctx); -char* sass_context_take_error_message (struct Sass_Context* ctx); -char* sass_context_take_error_file (struct Sass_Context* ctx); -char* sass_context_take_output_string (struct Sass_Context* ctx); -char* sass_context_take_source_map_string (struct Sass_Context* ctx); -``` - -### Sass Options API - -```C -// Getters for Context_Option values -int sass_option_get_precision (struct Sass_Options* options); -enum Sass_Output_Style sass_option_get_output_style (struct Sass_Options* options); -bool sass_option_get_source_comments (struct Sass_Options* options); -bool sass_option_get_source_map_embed (struct Sass_Options* options); -bool sass_option_get_source_map_contents (struct Sass_Options* options); -bool sass_option_get_source_map_file_urls (struct Sass_Options* options); -bool sass_option_get_omit_source_map_url (struct Sass_Options* options); -bool sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -const char* sass_option_get_indent (struct Sass_Options* options); -const char* sass_option_get_linefeed (struct Sass_Options* options); -const char* sass_option_get_input_path (struct Sass_Options* options); -const char* sass_option_get_output_path (struct Sass_Options* options); -const char* sass_option_get_source_map_file (struct Sass_Options* options); -const char* sass_option_get_source_map_root (struct Sass_Options* options); -Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); -Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); - -// Getters for Context_Option include path array -size_t sass_option_get_include_path_size(struct Sass_Options* options); -const char* sass_option_get_include_path(struct Sass_Options* options, size_t i); -// Plugin paths to load dynamic libraries work the same -size_t sass_option_get_plugin_path_size(struct Sass_Options* options); -const char* sass_option_get_plugin_path(struct Sass_Options* options, size_t i); - -// Setters for Context_Option values -void sass_option_set_precision (struct Sass_Options* options, int precision); -void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); -void sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); -void sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -void sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -void sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -void sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -void sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -void sass_option_set_indent (struct Sass_Options* options, const char* indent); -void sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); -void sass_option_set_input_path (struct Sass_Options* options, const char* input_path); -void sass_option_set_output_path (struct Sass_Options* options, const char* output_path); -void sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); -void sass_option_set_include_path (struct Sass_Options* options, const char* include_path); -void sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); -void sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); -void sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); -void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); - -// Push function for paths (no manipulation support for now) -void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -void sass_option_push_include_path (struct Sass_Options* options, const char* path); - -// Resolve a file via the given include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -char* sass_find_file (const char* path, struct Sass_Options* opt); -char* sass_find_include (const char* path, struct Sass_Options* opt); - -// Resolve a file relative to last import or include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -char* sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -char* sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); -``` - -### More links - -- [Sass Context Example](api-context-example.md) -- [Sass Context Internal](api-context-internal.md) - diff --git a/src/libsass/api-doc.md b/src/libsass/api-doc.md deleted file mode 100644 index 376561612..000000000 --- a/src/libsass/api-doc.md +++ /dev/null @@ -1,215 +0,0 @@ -## Introduction - -LibSass wouldn't be much good without a way to interface with it. These -interface documentations describe the various functions and data structures -available to implementers. They are split up over three major components, which -have all their own source files (plus some common functionality). - -- [Sass Context](api-context.md) - Trigger and handle the main Sass compilation -- [Sass Value](api-value.md) - Exchange values and its format with LibSass -- [Sass Function](api-function.md) - Get invoked by LibSass for function statments -- [Sass Importer](api-importer.md) - Get invoked by LibSass for @import statments - -### Basic usage - -First you will need to include the header file! -This will automatically load all other headers too! - -```C -#include "sass/context.h" -``` - -## Basic C Example - -```C -#include -#include "sass/context.h" - -int main() { - puts(libsass_version()); - return 0; -} -``` - -```bash -gcc -Wall version.c -lsass -o version && ./version -``` - -## More C Examples - -- [Sample code for Sass Context](api-context-example.md) -- [Sample code for Sass Value](api-value-example.md) -- [Sample code for Sass Function](api-function-example.md) -- [Sample code for Sass Importer](api-importer-example.md) - -## Compiling your code - -The most important is your sass file (or string of sass code). With this, you -will want to start a LibSass compiler. Here is some pseudocode describing the -process. The compiler has two different modes: direct input as a string with -`Sass_Data_Context` or LibSass will do file reading for you by using -`Sass_File_Context`. See the code for a list of options available -[Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) - -**Building a file compiler** - - context = sass_make_file_context("file.scss") - options = sass_file_context_get_options(context) - sass_option_set_precision(options, 1) - sass_option_set_source_comments(options, true) - - sass_file_context_set_options(context, options) - - compiler = sass_make_file_compiler(sass_context) - sass_compiler_parse(compiler) - sass_compiler_execute(compiler) - - output = sass_context_get_output_string(context) - // Retrieve errors during compilation - error_status = sass_context_get_error_status(context) - json_error = sass_context_get_error_json(context) - // Release memory dedicated to the C compiler - sass_delete_compiler(compiler) - -**Building a data compiler** - - context = sass_make_data_context("div { a { color: blue; } }") - options = sass_data_context_get_options(context) - sass_option_set_precision(options, 1) - sass_option_set_source_comments(options, true) - - sass_data_context_set_options(context, options) - - compiler = sass_make_data_compiler(context) - sass_compiler_parse(compiler) - sass_compiler_execute(compiler) - - output = sass_context_get_output_string(context) - // div a { color: blue; } - // Retrieve errors during compilation - error_status = sass_context_get_error_status(context) - json_error = sass_context_get_error_json(context) - // Release memory dedicated to the C compiler - sass_delete_compiler(compiler) - -## Sass Context Internals - -Everything is stored in structs: - -```C -struct Sass_Options; -struct Sass_Context : Sass_Options; -struct Sass_File_context : Sass_Context; -struct Sass_Data_context : Sass_Context; -``` - -This mirrors very well how `libsass` uses these structures. - -- `Sass_Options` holds everything you feed in before the compilation. It also hosts -`input_path` and `output_path` options, because they are used to generate/calculate -relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. -- `Sass_Context` holds all the data returned by the compilation step. -- `Sass_File_Context` is a specific implementation that requires no additional fields -- `Sass_Data_Context` is a specific implementation that adds the `input_source` field - -Structs can be down-casted to access `context` or `options`! - -## Memory handling and life-cycles - -We keep memory around for as long as the main [context](api-context.md) object -is not destroyed (`sass_delete_context`). LibSass will create copies of most -inputs/options beside the main sass code. You need to allocate and fill that -buffer before passing it to LibSass. You may also overtake memory management -from libsass for certain return values (i.e. `sass_context_take_output_string`). - -```C -// to allocate buffer to be filled -void* sass_alloc_memory(size_t size); -// to allocate a buffer from existing string -char* sass_copy_c_string(const char* str); -// to free overtaken memory when done -void sass_free_memory(void* ptr); -``` - -## Miscellaneous API functions - -```C -// Some convenient string helper function -char* sass_string_unquote (const char* str); -char* sass_string_quote (const char* str, const char quote_mark); - -// Get compiled libsass version -const char* libsass_version(void); - -// Implemented sass language version -// Hardcoded version 3.4 for time being -const char* libsass_language_version(void); -``` - -## Common Pitfalls - -**input_path** - -The `input_path` is part of `Sass_Options`, but it also is the main option for -`Sass_File_Context`. It is also used to generate relative file links in source- -maps. Therefore it is pretty usefull to pass this information if you have a -`Sass_Data_Context` and know the original path. - -**output_path** - -Be aware that `libsass` does not write the output file itself. This option -merely exists to give `libsass` the proper information to generate links in -source-maps. The file has to be written to the disk by the -binding/implementation. If the `output_path` is omitted, `libsass` tries to -extrapolate one from the `input_path` by replacing (or adding) the file ending -with `.css`. - -## Error Codes - -The `error_code` is integer value which indicates the type of error that -occurred inside the LibSass process. Following is the list of error codes along -with the short description: - -* 1: normal errors like parsing or `eval` errors -* 2: bad allocation error (memory error) -* 3: "untranslated" C++ exception (`throw std::exception`) -* 4: legacy string exceptions ( `throw const char*` or `std::string` ) -* 5: Some other unknown exception - -Although for the API consumer, error codes do not offer much value except -indicating whether *any* error occurred during the compilation, it helps -debugging the LibSass internal code paths. - -## Real-World Implementations - -The proof is in the pudding, so we have highlighted a few implementations that -should be on par with the latest LibSass interface version. Some of them may not -have all features implemented! - -1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) -2. [Go Example](https://godoc.org/github.com/wellington/go-libsass#example-Compiler--Stdin) -3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) - -## ABI forward compatibility - -We use a functional API to make dynamic linking more robust and future -compatible. The API is not yet 100% stable, so we do not yet guarantee -[ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward -compatibility. - -## Plugins (experimental) - -LibSass can load plugins from directories. Just define `plugin_path` on context -options to load all plugins from the directories. To implement plugins, please -consult the following example implementations. - -- https://github.com/mgreter/libsass-glob -- https://github.com/mgreter/libsass-math -- https://github.com/mgreter/libsass-digest - -## Internal Structs - -- [Sass Context Internals](api-context-internal.md) -- [Sass Value Internals](api-value-internal.md) -- [Sass Function Internals](api-function-internal.md) -- [Sass Importer Internals](api-importer-internal.md) diff --git a/src/libsass/api-function-example.md b/src/libsass/api-function-example.md deleted file mode 100644 index 38608e1a2..000000000 --- a/src/libsass/api-function-example.md +++ /dev/null @@ -1,67 +0,0 @@ -## Example main.c - -```C -#include -#include -#include "sass/context.h" - -union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) -{ - // get context/option struct associated with this compiler - struct Sass_Context* ctx = sass_compiler_get_context(comp); - struct Sass_Options* opts = sass_compiler_get_options(comp); - // get information about previous importer entry from the stack - Sass_Import_Entry import = sass_compiler_get_last_import(comp); - const char* prev_abs_path = sass_import_get_abs_path(import); - const char* prev_imp_path = sass_import_get_imp_path(import); - // get the cookie from function descriptor - void* cookie = sass_function_get_cookie(cb); - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate a custom function caller - Sass_Function_Entry fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - - // create list of all custom functions - Sass_Function_List fn_list = sass_make_function_list(1); - sass_function_set_list_entry(fn_list, 0, fn_foo); - sass_option_set_c_functions(ctx_opt, fn_list); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -### Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: foo(); }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` - diff --git a/src/libsass/api-function-internal.md b/src/libsass/api-function-internal.md deleted file mode 100644 index 69d81d04d..000000000 --- a/src/libsass/api-function-internal.md +++ /dev/null @@ -1,8 +0,0 @@ -```C -// Struct to hold custom function callback -struct Sass_Function { - const char* signature; - Sass_Function_Fn function; - void* cookie; -}; -``` diff --git a/src/libsass/api-function.md b/src/libsass/api-function.md deleted file mode 100644 index 8d9d97ca4..000000000 --- a/src/libsass/api-function.md +++ /dev/null @@ -1,74 +0,0 @@ -Sass functions are used to define new custom functions callable by Sass code. They are also used to overload debug or error statements. You can also define a fallback function, which is called for every unknown function found in the Sass code. Functions get passed zero or more `Sass_Values` (a `Sass_List` value) and they must also return a `Sass_Value`. Return a `Sass_Error` if you want to signal an error. - -## Special signatures - -- `*` - Fallback implementation -- `@warn` - Overload warn statements -- `@error` - Overload error statements -- `@debug` - Overload debug statements - -Note: The fallback implementation will be given the name of the called function as the first argument, before all the original function arguments. These features are pretty new and should be considered experimental. - -### Basic Usage - -```C -#include "sass/functions.h" -``` - -## Sass Function API - -```C -// Forward declaration -struct Sass_Compiler; -struct Sass_Function; - -// Typedef helpers for custom functions lists -typedef struct Sass_Function (*Sass_Function_Entry); -typedef struct Sass_Function* (*Sass_Function_List); -// Typedef defining function signature and return type -typedef union Sass_Value* (*Sass_Function_Fn) - (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); - -// Creators for sass function list and function descriptors -Sass_Function_List sass_make_function_list (size_t length); -Sass_Function_Entry sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); -// In case you need to free them yourself -void sass_delete_function (Sass_Function_Entry entry); -void sass_delete_function_list (Sass_Function_List list); - -// Setters and getters for callbacks on function lists -Sass_Function_Entry sass_function_get_list_entry(Sass_Function_List list, size_t pos); -void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); -Sass_Import_Entry sass_import_get_list_entry (Sass_Import_List list, size_t idx); - -// Getters for custom function descriptors -const char* sass_function_get_signature (Sass_Function_Entry cb); -Sass_Function_Fn sass_function_get_function (Sass_Function_Entry cb); -void* sass_function_get_cookie (Sass_Function_Entry cb); - -// Getters for callee entry -const char* sass_callee_get_name (Sass_Callee_Entry); -const char* sass_callee_get_path (Sass_Callee_Entry); -size_t sass_callee_get_line (Sass_Callee_Entry); -size_t sass_callee_get_column (Sass_Callee_Entry); -enum Sass_Callee_Type sass_callee_get_type (Sass_Callee_Entry); -Sass_Env_Frame sass_callee_get_env (Sass_Callee_Entry); - -// Getters and Setters for environments (lexical, local and global) -union Sass_Value* sass_env_get_lexical (Sass_Env_Frame, const char*); -void sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); -union Sass_Value* sass_env_get_local (Sass_Env_Frame, const char*); -void sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); -union Sass_Value* sass_env_get_global (Sass_Env_Frame, const char*); -void sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); -``` - -### More links - -- [Sass Function Example](api-function-example.md) -- [Sass Function Internal](api-function-internal.md) - diff --git a/src/libsass/api-importer-example.md b/src/libsass/api-importer-example.md deleted file mode 100644 index d83bf2609..000000000 --- a/src/libsass/api-importer-example.md +++ /dev/null @@ -1,112 +0,0 @@ -## Example importer.c - -```C -#include -#include -#include "sass/context.h" - -Sass_Import_List sass_importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) -{ - // get the cookie from importer descriptor - void* cookie = sass_importer_get_cookie(cb); - Sass_Import_List list = sass_make_import_list(2); - char* local = sass_copy_c_string("local { color: green; }"); - char* remote = sass_copy_c_string("remote { color: red; }"); - list[0] = sass_make_import_entry("/tmp/styles.scss", local, 0); - list[1] = sass_make_import_entry("http://www.example.com", remote, 0); - return list; -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate custom importer - Sass_Importer_Entry c_imp = - sass_make_importer(sass_importer, 0, 0); - // create list for all custom importers - Sass_Importer_List imp_list = sass_make_importer_list(1); - // put only the importer on to the list - sass_importer_set_list_entry(imp_list, 0, c_imp); - // register list on to the context options - sass_option_set_c_importers(ctx_opt, imp_list); - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -Compile importer.c - -```bash -gcc -c importer.c -o importer.o -gcc -o importer importer.o -lsass -echo "@import 'foobar';" > importer.scss -./importer importer.scss -``` - -## Importer Behavior Examples - -```C -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass handle the import request - return NULL; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass handle the request - // swallows »@import "http://…"« pass-through - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry(path, 0, 0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // return an error to halt execution - Sass_Import_List list = sass_make_import_list(1); - const char* message = "some error message"; - list[0] = sass_make_import_entry(path, 0, 0); - sass_import_set_error(list[0], sass_copy_c_string(message), 0, 0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass load the file identifed by the importer - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry("/tmp/file.scss", 0, 0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // completely hide the import - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // completely hide the import - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry(0, 0, 0); - return list; -} -``` diff --git a/src/libsass/api-importer-internal.md b/src/libsass/api-importer-internal.md deleted file mode 100644 index 63d70fe75..000000000 --- a/src/libsass/api-importer-internal.md +++ /dev/null @@ -1,20 +0,0 @@ -```C -// External import entry -struct Sass_Import { - char* imp_path; // path as found in the import statement - char *abs_path; // path after importer has resolved it - char* source; - char* srcmap; - // error handling - char* error; - size_t line; - size_t column; -}; - -// Struct to hold importer callback -struct Sass_Importer { - Sass_Importer_Fn importer; - double priority; - void* cookie; -}; -``` diff --git a/src/libsass/api-importer.md b/src/libsass/api-importer.md deleted file mode 100644 index b6265002e..000000000 --- a/src/libsass/api-importer.md +++ /dev/null @@ -1,86 +0,0 @@ -By using custom importers, Sass stylesheets can be implemented in any possible way, such as by being loaded via a remote server. Please note: this feature is experimental and is implemented differently than importers in Ruby Sass. Imports must be relative to the parent import context and therefore we need to pass this information to the importer callback. This is currently done by passing the complete import string/path of the previous import context. - -## Return Imports - -You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). - -```C -Sass_Import_Entry* rv = sass_make_import_list(1); -rv[0] = sass_make_import(rel, abs, source, srcmap); -``` - -Every import will then be included in LibSass. You are allowed to only return a file path without any loaded source. This way you can ie. implement rewrite rules for import paths and leave the loading part for LibSass. - -Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not deprecate the C-API once support has been implemented. It will be used to re-map the actual sourcemap with the provided ones. - -### Basic Usage - -```C -#include "sass/functions.h" -``` - -## Sass Importer API - -```C -// Forward declaration -struct Sass_Import; - -// Forward declaration -struct Sass_C_Import_Descriptor; - -// Typedef defining the custom importer callback -typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); -// Typedef defining the importer c function prototype -typedef Sass_Import_Entry* (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); - -// Creators for custom importer callback (with some additional pointer) -// The pointer is mostly used to store the callback into the actual function -Sass_C_Import_Callback sass_make_importer (Sass_C_Import_Fn, void* cookie); - -// Getters for import function descriptors -Sass_C_Import_Fn sass_import_get_function (Sass_C_Import_Callback fn); -void* sass_import_get_cookie (Sass_C_Import_Callback fn); - -// Deallocator for associated memory -void sass_delete_importer (Sass_C_Import_Callback fn); - -// Creator for sass custom importer return argument list -Sass_Import_Entry* sass_make_import_list (size_t length); -// Creator for a single import entry returned by the custom importer inside the list -Sass_Import_Entry sass_make_import_entry (const char* path, char* source, char* srcmap); -Sass_Import_Entry sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); - -// set error message to abort import and to print out a message (path from existing object is used in output) -Sass_Import_Entry sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (Sass_Import_Entry* list, size_t idx, Sass_Import_Entry entry); -Sass_Import_Entry sass_import_get_list_entry (Sass_Import_Entry* list, size_t idx); - -// Getters for import entry -const char* sass_import_get_imp_path (Sass_Import_Entry); -const char* sass_import_get_abs_path (Sass_Import_Entry); -const char* sass_import_get_source (Sass_Import_Entry); -const char* sass_import_get_srcmap (Sass_Import_Entry); -// Explicit functions to take ownership of these items -// The property on our struct will be reset to NULL -char* sass_import_take_source (Sass_Import_Entry); -char* sass_import_take_srcmap (Sass_Import_Entry); - -// Getters for import error entries -size_t sass_import_get_error_line (Sass_Import_Entry); -size_t sass_import_get_error_column (Sass_Import_Entry); -const char* sass_import_get_error_message (Sass_Import_Entry); - -// Deallocator for associated memory (incl. entries) -void sass_delete_import_list (Sass_Import_Entry*); -// Just in case we have some stray import structs -void sass_delete_import (Sass_Import_Entry); -``` - -### More links - -- [Sass Importer Example](api-importer-example.md) -- [Sass Importer Internal](api-importer-internal.md) - diff --git a/src/libsass/api-value-example.md b/src/libsass/api-value-example.md deleted file mode 100644 index 690654eaf..000000000 --- a/src/libsass/api-value-example.md +++ /dev/null @@ -1,55 +0,0 @@ -## Example operation.c - -```C -#include -#include -#include "sass/values.h" - -int main( int argc, const char* argv[] ) -{ - - // create two new sass values to be added - union Sass_Value* string = sass_make_string("String"); - union Sass_Value* number = sass_make_number(42, "nits"); - - // invoke the add operation which returns a new sass value - union Sass_Value* total = sass_value_op(ADD, string, number); - - // no further use for the two operands - sass_delete_value(string); - sass_delete_value(number); - - // this works since libsass will always return a - // string for add operations with a string as the - // left hand side. But you should never rely on it! - puts(sass_string_get_value(total)); - - // invoke stringification (uncompressed with precision of 5) - union Sass_Value* result = sass_value_stringify(total, false, 5); - - // no further use for the sum - sass_delete_value(total); - - // print the result - you may want to make - // sure result is indeed a string, altough - // stringify guarantees to return a string - // if (sass_value_is_string(result)) {} - // really depends on your level of paranoia - puts(sass_string_get_value(result)); - - // finally free result - sass_delete_value(result); - - // exit status - return 0; - -} -``` - -## Compile operation.c - -```bash -gcc -c operation.c -o operation.o -gcc -o operation operation.o -lsass -./operation # => String42nits -``` diff --git a/src/libsass/api-value-internal.md b/src/libsass/api-value-internal.md deleted file mode 100644 index fed402256..000000000 --- a/src/libsass/api-value-internal.md +++ /dev/null @@ -1,76 +0,0 @@ -```C -struct Sass_Unknown { - enum Sass_Tag tag; -}; - -struct Sass_Boolean { - enum Sass_Tag tag; - bool value; -}; - -struct Sass_Number { - enum Sass_Tag tag; - double value; - char* unit; -}; - -struct Sass_Color { - enum Sass_Tag tag; - double r; - double g; - double b; - double a; -}; - -struct Sass_String { - enum Sass_Tag tag; - char* value; -}; - -struct Sass_List { - enum Sass_Tag tag; - enum Sass_Separator separator; - size_t length; - // null terminated "array" - union Sass_Value** values; -}; - -struct Sass_Map { - enum Sass_Tag tag; - size_t length; - struct Sass_MapPair* pairs; -}; - -struct Sass_Null { - enum Sass_Tag tag; -}; - -struct Sass_Error { - enum Sass_Tag tag; - char* message; -}; - -struct Sass_Warning { - enum Sass_Tag tag; - char* message; -}; - -union Sass_Value { - struct Sass_Unknown unknown; - struct Sass_Boolean boolean; - struct Sass_Number number; - struct Sass_Color color; - struct Sass_String string; - struct Sass_List list; - struct Sass_Map map; - struct Sass_Null null; - struct Sass_Error error; - struct Sass_Warning warning; -}; - -struct Sass_MapPair { - union Sass_Value* key; - union Sass_Value* value; -}; -``` - diff --git a/src/libsass/api-value.md b/src/libsass/api-value.md deleted file mode 100644 index d78625875..000000000 --- a/src/libsass/api-value.md +++ /dev/null @@ -1,154 +0,0 @@ -`Sass_Values` are used to pass values and their types between the implementer -and LibSass. Sass knows various different value types (including nested arrays -and hash-maps). If you implement a binding to another programming language, you -have to find a way to [marshal][1] (convert) `Sass_Values` between the target -language and C. `Sass_Values` are currently only used by custom functions, but -it should also be possible to use them without a compiler context. - -[1]: https://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 - -### Basic Usage - -```C -#include "sass/values.h" -``` - -```C -// Type for Sass values -enum Sass_Tag { - SASS_BOOLEAN, - SASS_NUMBER, - SASS_COLOR, - SASS_STRING, - SASS_LIST, - SASS_MAP, - SASS_NULL, - SASS_ERROR, - SASS_WARNING -}; - -// Tags for denoting Sass list separators -enum Sass_Separator { - SASS_COMMA, - SASS_SPACE, - // only used internally to represent a hash map before evaluation - // otherwise we would be too early to check for duplicate keys - SASS_HASH -}; - -// Value Operators -enum Sass_OP { - AND, OR, // logical connectives - EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations - ADD, SUB, MUL, DIV, MOD, // arithmetic functions - NUM_OPS // so we know how big to make the op table -}; -``` - -### Sass Value API - -```C -// Forward declaration -union Sass_Value; - -// Creator functions for all value types -union Sass_Value* sass_make_null (void); -union Sass_Value* sass_make_boolean (bool val); -union Sass_Value* sass_make_string (const char* val); -union Sass_Value* sass_make_qstring (const char* val); -union Sass_Value* sass_make_number (double val, const char* unit); -union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -union Sass_Value* sass_make_map (size_t len); -union Sass_Value* sass_make_error (const char* msg); -union Sass_Value* sass_make_warning (const char* msg); - -// Generic destructor function for all types -// Will release memory of all associated Sass_Values -// Means we will delete recursively for lists and maps -void sass_delete_value (union Sass_Value* val); - -// Make a deep cloned copy of the given sass value -union Sass_Value* sass_clone_value (const union Sass_Value* val); - -// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) -union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); - -// Execute an operation for two Sass_Values and return the result as a Sass_Value too -union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); - -// Return the sass tag for a generic sass value -// Check is needed before accessing specific values! -enum Sass_Tag sass_value_get_tag (const union Sass_Value* v); - -// Check value to be of a specific type -// Can also be used before accessing properties! -bool sass_value_is_null (const union Sass_Value* v); -bool sass_value_is_number (const union Sass_Value* v); -bool sass_value_is_string (const union Sass_Value* v); -bool sass_value_is_boolean (const union Sass_Value* v); -bool sass_value_is_color (const union Sass_Value* v); -bool sass_value_is_list (const union Sass_Value* v); -bool sass_value_is_map (const union Sass_Value* v); -bool sass_value_is_error (const union Sass_Value* v); -bool sass_value_is_warning (const union Sass_Value* v); - -// Getters and setters for Sass_Number -double sass_number_get_value (const union Sass_Value* v); -void sass_number_set_value (union Sass_Value* v, double value); -const char* sass_number_get_unit (const union Sass_Value* v); -void sass_number_set_unit (union Sass_Value* v, char* unit); - -// Getters and setters for Sass_String -const char* sass_string_get_value (const union Sass_Value* v); -void sass_string_set_value (union Sass_Value* v, char* value); -bool sass_string_is_quoted(const union Sass_Value* v); -void sass_string_set_quoted(union Sass_Value* v, bool quoted); - -// Getters and setters for Sass_Boolean -bool sass_boolean_get_value (const union Sass_Value* v); -void sass_boolean_set_value (union Sass_Value* v, bool value); - -// Getters and setters for Sass_Color -double sass_color_get_r (const union Sass_Value* v); -void sass_color_set_r (union Sass_Value* v, double r); -double sass_color_get_g (const union Sass_Value* v); -void sass_color_set_g (union Sass_Value* v, double g); -double sass_color_get_b (const union Sass_Value* v); -void sass_color_set_b (union Sass_Value* v, double b); -double sass_color_get_a (const union Sass_Value* v); -void sass_color_set_a (union Sass_Value* v, double a); - -// Getter for the number of items in list -size_t sass_list_get_length (const union Sass_Value* v); -// Getters and setters for Sass_List -enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); -void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); -bool sass_list_get_is_bracketed (const union Sass_Value* v); -void sass_list_set_is_bracketed (union Sass_Value* v, bool value); -// Getters and setters for Sass_List values -union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); -void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); - -// Getter for the number of items in map -size_t sass_map_get_length (const union Sass_Value* v); -// Getters and setters for Sass_Map keys and values -union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); -void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); -union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); -void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); - -// Getters and setters for Sass_Error -char* sass_error_get_message (const union Sass_Value* v); -void sass_error_set_message (union Sass_Value* v, char* msg); - -// Getters and setters for Sass_Warning -char* sass_warning_get_message (const union Sass_Value* v); -void sass_warning_set_message (union Sass_Value* v, char* msg); -``` - -### More links - -- [Sass Value Example](api-value-example.md) -- [Sass Value Internal](api-value-internal.md) - diff --git a/src/libsass/build-on-darwin.md b/src/libsass/build-on-darwin.md deleted file mode 100644 index 119a5350e..000000000 --- a/src/libsass/build-on-darwin.md +++ /dev/null @@ -1,27 +0,0 @@ -To install LibSass, make sure the OS X build tools are installed: - - xcode-select --install - -## Homebrew - -To install homebrew, see [http://brew.sh](http://brew.sh) - - ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - -You can install the latest version of LibSass quite easily with brew. - - brew install --HEAD libsass - -To update this, do: - - brew reinstall --HEAD libsass - -Brew will build static and shared libraries, and a `libsass.pc` file in `/usr/local/lib/pkgconfig`. - -To use `libsass.pc`, make sure this path is in your `PKG_CONFIG_PATH` - - export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig - -## Manually - -See the linux instructions [Building-with-autotools](build-with-autotools.md) or [Building-with-makefiles](build-with-makefiles.md) diff --git a/src/libsass/build-on-gentoo.md b/src/libsass/build-on-gentoo.md deleted file mode 100644 index 601b1fe5e..000000000 --- a/src/libsass/build-on-gentoo.md +++ /dev/null @@ -1,55 +0,0 @@ -Here are two ebuilds to compile LibSass and sassc on gentoo linux. If you do not know how to use these ebuilds, you should probably read the gentoo wiki page about [portage overlays](http://wiki.gentoo.org/wiki/Overlay). - -## www-misc/libsass/libsass-9999.ebuild -```ebuild -EAPI=4 - -inherit eutils git-2 autotools - -DESCRIPTION="A C/C++ implementation of a Sass compiler." -HOMEPAGE="http://libsass.org/" -EGIT_PROJECT='libsass' -EGIT_REPO_URI="https://github.com/sass/libsass.git" -LICENSE="MIT" -SLOT="0" -KEYWORDS="" -IUSE="" -DEPEND="" -RDEPEND="${DEPEND}" -DEPEND="${DEPEND}" - -pkg_pretend() { - # older gcc is not supported - local major=$(gcc-major-version) - local minor=$(gcc-minor-version) - [[ "${MERGE_TYPE}" != "binary" && ( $major > 4 || ( $major == 4 && $minor < 5 ) ) ]] && \ - die "Sorry, but gcc earlier than 4.5 will not work for LibSass." -} - -src_prepare() { - eautoreconf -} -``` - -## www-misc/sassc/sassc-9999.ebuild -```ebuild -EAPI=4 - -inherit eutils git-2 autotools - -DESCRIPTION="Command Line Tool for LibSass." -HOMEPAGE="http://libsass.org/" -EGIT_PROJECT='sassc' -EGIT_REPO_URI="https://github.com/sass/sassc.git" -LICENSE="MIT" -SLOT="0" -KEYWORDS="" -IUSE="" -DEPEND="www-misc/libsass" -RDEPEND="${DEPEND}" -DEPEND="${DEPEND}" - -src_prepare() { - eautoreconf -} -``` diff --git a/src/libsass/build-on-windows.md b/src/libsass/build-on-windows.md deleted file mode 100644 index 0afaa2e4c..000000000 --- a/src/libsass/build-on-windows.md +++ /dev/null @@ -1,139 +0,0 @@ -We support builds via MingGW and via Visual Studio Community 2013. -Both should be considered experimental (MinGW was better tested)! - -## Building via MingGW (makefiles) - -First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. - -You need to have the following components installed: -![Visualization of components installed in the interface](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) - -Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. - -If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: - -```bash -gem install minitest -``` - -### Mount the mingw root directory - -As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: - -``` -C:\MinGW /mingw -``` - -### Starting a "MingGW" console - -Create a batch file with this content: -```bat -@echo off -set PATH=C:\MinGW\bin;%PATH% -REM only needed if not already available -set PATH=%PROGRAMFILES%\git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! - -### Get the sources - -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Decide for static or shared library - -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: - -```bat -set BUILD="shared" -``` - -### Compile the library -```bash -mingw32-make -C libsass -``` - -### Results can be found in -```bash -$ ls libsass/lib -libsass.a libsass.dll libsass.so -``` - -### Run the spec test-suite -```bash -mingw32-make -C libsass test_build -``` - -## Building via MingGW 64bit (makefiles) -Building libass to dll on window 64bit. - -+ downloads [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) , and unzip to "C:\mingw64". - -+ Create a batch file with this content: - -```bat -@echo off -set PATH=C:\mingw64\bin;%PATH% -set CC=gcc -REM only needed if not already available -set PATH=%PROGRAMFILES%\Git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -+ By default , mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​" , we can modify Makefile to fix this:(add "-static") - -``` bash -lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) - $(MKDIR) lib - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a -``` - -+ Compile the library - -```bash -mingw32-make -C libsass -``` - -By the way , if you are using java jna , [JNAerator](http://jnaerator.googlecode.com/) is a good tool. - -## Building via Visual Studio Community 2013 - -Open a Visual Studio 2013 command prompt: -- `VS2013 x86 Native Tools Command Prompt` - -Note: When I installed the community edition, I only got the 2012 command prompts. I copied them from the Startmenu to the Desktop and adjusted the paths from `Visual Studio 11.0` to `Visual Studio 12.0`. Since `libsass` uses some `C++11` features, you need at least a MSVC 2013 compiler (v120). - -### Get the source -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -git clone https://github.com/sass/sassc.git libsass/sassc -# only needed if you want to run the testsuite -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Compile sassc - -Sometimes `msbuild` seems not available from the command prompt. Just search for it and add it to the global path. It seems to be included in the .net folders too. - -```bat -cd libsass -REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin -msbuild /m:4 /p:Configuration=Release win\libsass.sln -REM running the spec test-suite manually (needs ruby and minitest gem) -ruby sass-spec\sass-spec.rb -V 3.5 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec -cd .. -``` - -[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files -[2]: https://msysgit.github.io/ -[3]: http://rubyinstaller.org/ diff --git a/src/libsass/build-shared-library.md b/src/libsass/build-shared-library.md deleted file mode 100644 index 3c143b46a..000000000 --- a/src/libsass/build-shared-library.md +++ /dev/null @@ -1,35 +0,0 @@ -This page is mostly intended for people that want to build a system library that gets distributed via RPMs or other means. This is currently in a experimental phase, as we currently do not really guarantee any ABI forward compatibility. The C API was rewritten to make this possible in the future, but we want to wait some more time till we can call this final and stable. - -Building via autotools --- - -You want to build a system library only via autotools, since it will create the proper `libtool` files to make it loadable on multiple systems. We hope this works correctly, but nobody of the `libsass` core team has much knowledge in this area. Therefore we are open for comments or improvements by people that have more experience in that matter (like package maintainers from various linux distributions). - -```bash -apt-get install autoconf libtool -git clone https://github.com/sass/libsass.git -cd libsass -autoreconf --force --install -./configure \ - --disable-tests \ - --disable-static \ - --enable-shared \ - --prefix=/usr -make -j5 install -cd .. -``` - -This should install these files -```bash -# $ ls -la /usr/lib/libsass.* -/usr/lib/libsass.la -/usr/lib/libsass.so -> libsass.so.0.0.9 -/usr/lib/libsass.so.0 -> libsass.so.0.0.9 -/usr/lib/libsass.so.0.0.9 -# $ ls -la /usr/include/sass* -/usr/include/sass.h -/usr/include/sass2scss.h -/usr/include/sass/context.h -/usr/include/sass/functions.h -/usr/include/sass/values.h -``` diff --git a/src/libsass/build-with-autotools.md b/src/libsass/build-with-autotools.md deleted file mode 100644 index a48ed18aa..000000000 --- a/src/libsass/build-with-autotools.md +++ /dev/null @@ -1,78 +0,0 @@ -### Get the sources -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Prerequisites - -In order to run autotools you need a few tools installed on your system. -```bash -yum install automake libtool # RedHat Linux -emerge -a automake libtool # Gentoo Linux -pkgin install automake libtool # SmartOS -``` - - -### Create configure script -```bash -cd libsass -autoreconf --force --install -cd .. -``` - -### Create custom makefiles -```bash -cd libsass -./configure \ - --disable-tests \ - --disable-shared \ - --prefix=/usr -cd .. -``` - -### Build the library -```bash -make -C libsass -j5 -``` - -### Install the library -The library will be installed to the location given as `prefix` to `configure`. This is standard behavior for autotools and not `libsass` specific. -```bash -make -C libsass -j5 install -``` - -### Configure options -The `configure` script is created by autotools. To get an overview of available options you can call `./configure --help`. When you execute this script, it will create specific makefiles, which you then use via the regular make command. - -There are some `libsass` specific options: - -``` -Optional Features: - --enable-tests enable testing the build - --enable-coverage enable coverage report for test suite - --enable-shared build shared libraries [default=yes] - --enable-static build static libraries [default=yes] - -Optional Packages: - --with-sassc-dir= specify directory of sassc sources for - testing (default: sassc) - --with-sass-spec-dir= specify directory of sass-spec for testing - (default: sass-spec) -``` - -### Build sassc and run spec test-suite - -```bash -cd libsass -autoreconf --force --install -./configure \ - --enable-tests \ - --enable-shared \ - --prefix=/usr -make -j5 test_build -cd .. -``` diff --git a/src/libsass/build-with-makefiles.md b/src/libsass/build-with-makefiles.md deleted file mode 100644 index 7ae2e33d6..000000000 --- a/src/libsass/build-with-makefiles.md +++ /dev/null @@ -1,68 +0,0 @@ -### Get the sources -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Decide for static or shared library - -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: - -```bash -export BUILD="shared" -``` - -Alternatively you can also define it directly when calling make: - -```bash -BUILD="shared" make ... -``` - -### Compile the library -```bash -make -C libsass -j5 -``` - -### Results can be found in -```bash -$ ls libsass/lib -libsass.a libsass.so -``` - -### Install onto the system - -We recommend to use [autotools to install](build-with-autotools.md) libsass onto the -system, since that brings all the benefits of using libtools as the main install method. -If you still want to install libsass via the makefile, you need to make sure that gnu -`install` utility (or compatible) is installed on your system. -```bash -yum install coreutils # RedHat Linux -emerge -a coreutils # Gentoo Linux -pkgin install coreutils # SmartOS -``` - -You can set the install location by setting `PREFIX` -```bash -PREFIX="/opt/local" make install -``` - - -### Compling sassc - -```bash -# Let build know library location -export SASS_LIBSASS_PATH="`pwd`/libsass" -# Invokes the sassc makefile -make -C libsass -j5 sassc -``` - -### Run the spec test-suite - -```bash -# needs ruby available -# also gem install minitest -make -C libsass -j5 test_build -``` diff --git a/src/libsass/build-with-mingw.md b/src/libsass/build-with-mingw.md deleted file mode 100644 index 416507f3c..000000000 --- a/src/libsass/build-with-mingw.md +++ /dev/null @@ -1,107 +0,0 @@ -## Building LibSass with MingGW (makefiles) - -First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. - -You need to have the following components installed: -![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) - -Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. - -If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: - -```bash -gem install minitest -``` - -### Mount the mingw root directory - -As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: - -``` -C:\MinGW /mingw -``` - -### Starting a "MingGW" console - -Create a batch file with this content: -```bat -@echo off -set PATH=C:\MinGW\bin;%PATH% -REM only needed if not already available -set PATH=%PROGRAMFILES%\git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! - -### Get the sources - -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Decide for static or shared library - -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: - -```bat -set BUILD="shared" -``` - -### Compile the library -```bash -mingw32-make -C libsass -``` - -### Results can be found in -```bash -$ ls libsass/lib -libsass.a libsass.dll libsass.so -``` - -### Run the spec test-suite -```bash -mingw32-make -C libsass test_build -``` - -## Building via MingGW 64bit (makefiles) -Building libass to dll on window 64bit. - -Download [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) and unzip to "C:\mingw64". - -Create a batch file with this content: - -```bat -@echo off -set PATH=C:\mingw64\bin;%PATH% -set CC=gcc -REM only needed if not already available -set PATH=%PROGRAMFILES%\Git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -By default, mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​", we can modify Makefile to fix this:(add "-static") - -``` bash -lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) - $(MKDIR) lib - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a -``` - -Compile the library - -```bash -mingw32-make -C libsass -``` - -By the way, if you are using java jna, [JNAerator](http://jnaerator.googlecode.com/) is a good tool. - -[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files -[2]: https://msysgit.github.io/ -[3]: http://rubyinstaller.org/ diff --git a/src/libsass/build-with-visual-studio.md b/src/libsass/build-with-visual-studio.md deleted file mode 100644 index 275b917b8..000000000 --- a/src/libsass/build-with-visual-studio.md +++ /dev/null @@ -1,90 +0,0 @@ -## Building LibSass with Visual Studio - -### Requirements: - -The minimum requirement to build LibSass with Visual Studio is "Visual Studio 2013 Express for Desktop". - -Additionally, it is recommended to have `git` installed and available in `PATH`, so to deduce the `libsass` version information. For instance, if GitHub for Windows (https://windows.github.com/) is installed, the `PATH` will have an entry resembling: `X:\Users\\AppData\Local\GitHub\PortableGit_\cmd\` (where `X` is the drive letter of system drive). If `git` is not available, inquiring the LibSass version will result in `[NA]`. - -### Build Steps: - -#### From Visual Studio: - -On opening the `win\libsass.sln` solution and build (Ctrl+Shift+B) to build `libsass.dll`. - -To Build LibSass as a static Library, it is recommended to set an environment variable `LIBSASS_STATIC_LIB` before launching the project: - -```cmd -cd path\to\libsass -SET LIBSASS_STATIC_LIB=1 -:: -:: or in PowerShell: -:: $env:LIBSASS_STATIC_LIB=1 -:: -win\libsass.sln -``` - -Visual Studio will form the filtered source tree as shown below: - -![image](https://cloud.githubusercontent.com/assets/3840695/9298985/aae9e072-44bf-11e5-89eb-e7995c098085.png) - -`Header Files` contains the .h and .hpp files, while `Source Files` covers `.c` and `.cpp`. The other used headers/sources will appear under `External Dependencies`. - -If there is a LibSass code file appearing under External Dependencies, it can be changed by altering the `win\libsass.vcxproj.filters` file or dragging in Solution Explorer. - -#### From Command Prompt: - -Notice that in the following commands: - -* If the platform is 32-bit Windows, replace `ProgramFiles(x86)` with `ProgramFiles`. -* To build with Visual Studio 2015, replace `12.0` with `14.0` in the aforementioned command. - -Open a command prompt: - -To build dynamic/shared library (`libsass.dll`): - -```cmd -:: debug build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln - -:: release build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:Configuration=Release -``` - -To build static library (`libsass.lib`): - -```cmd -:: debug build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:LIBSASS_STATIC_LIB=1 - -:: release build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release -``` - -#### From PowerShell: - -To build dynamic/shared library (`libsass.dll`): - -```powershell -# debug build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln - -# release build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:Configuration=Release -``` - -To build static library (`libsass.lib`): - -```powershell -# build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:LIBSASS_STATIC_LIB=1 - -# release build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release -``` diff --git a/src/libsass/build.md b/src/libsass/build.md deleted file mode 100644 index c656d8839..000000000 --- a/src/libsass/build.md +++ /dev/null @@ -1,97 +0,0 @@ -`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line][6]. Or some [bindings|Implementations][9] to use it within your favorite programming language. You should be able to get [`sassc`][6] running by following the instructions in this guide. - -Before starting, see [setup dev environment](setup-environment.md). - -Building on different Operating Systems --- - -We try to keep the code as OS independent and standard compliant as possible. Reading files from the file-system has some OS depending code, but will ultimately fall back to a posix compatible implementation. We do use some `C++11` features, but are so far only committed to use `unordered_map`. This means you will need a pretty recent compiler on most systems (gcc 4.5 seems to be the minimum). - -### Building on Linux (and other *nix flavors) - -Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). - -- [Building with makefiles][1] -- [Building with autotools][2] - -### Building on Windows (experimental) - -Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! - -- [Building with MinGW][3] -- [Building with Visual Studio][11] - -### Building on Max OS X (untested) - -Works the same as on linux, but you can also install LibSass via `homebrew`. - -- [Building on Mac OS X][10] - -### Building a system library (experimental) - -Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. - -- [Building shared system library][4] - -Compiling with clang instead of gcc --- - -To use clang you just need to set the appropriate environment variables: - -```bash -export CC=/usr/bin/clang -export CXX=/usr/bin/clang++ -``` - -Running the spec test-suite --- - -We constantly and automatically test `libsass` against the official [spec test-suite][5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: - -```bash -ruby -v -gem install minitest -# should be optional -gem install minitap -``` - -Including the LibSass version --- - -There is a function in `libsass` to query the current version. This has to be defined at compile time. We use a C macro for this, which can be defined by calling `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not need to do anything if you use the makefiles or autotools. They will try to fetch the version via git directly. If you only have the sources without the git repo, you can pass the version as an environment variable to `make` or `configure`: - -``` -export LIBSASS_VERSION="x.y.z." -``` - -Continuous Integration --- - -We use two CI services to automatically test all commits against the latest [spec test-suite][5]. - -- [LibSass on Travis-CI (linux)][7] -[![Build Status](https://travis-ci.org/sass/libsass.png?branch=master)](https://travis-ci.org/sass/libsass) -- [LibSass on AppVeyor (windows)][8] -[![Build status](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/mgreter/libsass-513/branch/master) - -Why not using CMake? --- - -There were some efforts to get `libsass` to compile with CMake, which should make it easier to create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! - -Miscellaneous --- - -- [Ebuilds for Gentoo Linux](build-on-gentoo.md) - -[1]: build-with-makefiles.md -[2]: build-with-autotools.md -[3]: build-with-mingw.md -[4]: build-shared-library.md -[5]: https://github.com/sass/sass-spec -[6]: https://github.com/sass/sassc -[7]: https://github.com/sass/libsass/blob/master/.travis.yml -[8]: https://github.com/sass/libsass/blob/master/appveyor.yml -[9]: implementations.md -[10]: build-on-darwin.md -[11]: build-with-visual-studio.md diff --git a/src/libsass/compatibility-plan.md b/src/libsass/compatibility-plan.md deleted file mode 100644 index d8e538fa4..000000000 --- a/src/libsass/compatibility-plan.md +++ /dev/null @@ -1,48 +0,0 @@ -This document is to serve as a living, changing plan for getting LibSass caught up with Ruby Sass. - -_Note: an "s" preceeding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ - -# Goal -**Our goal is to reach full s3.4 compatibility as soon as possible. LibSass version 3.4 will behave just like Ruby Sass 3.4** - -I highlight the goal, because there are some things that are *not* currently priorities. To be clear, they WILL be priorities, but they are not at the moment: - -* Performance Improvements -* Extensibility - -The overriding goal is correctness. - -## Verifying Correctness -LibSass uses the spec for its testing. The spec was originally based off s3.2 tests. Many things have changed in Ruby Sass since then and some of the tests need to be updated and changed in order to get them to match both LibSass and Ruby Sass. - -Until this project is complete, the spec will be primarily a place to test LibSass. By the time LibSass reaches 3.4, it is our goal that sass-spec will be fully usable as an official testing source for ALL implementations of Sass. - -## Version Naming -Until LibSass reaches parity with Ruby Sass, we will be aggressively bumping versions, and LibSass 3.4 will be the peer to Ruby Sass 3.4 in every way. - -# Release Plan - -## 3.0 -The goal of 3.0 is to introduce some of the most demanded features for LibSass. That is, we are focusing on issues and features that have kept adoption down. This is a mongrel release wrt which version of Sass it's targeting. It's often a mixture of 3.2 / 3.3 / 3.4 behaviours. This is not ideal, but it's favourable to not existing. Targeting 3.4 strictly during this release would mean we never actually release. - -# 3.1 -The goal of 3.1 is to update all the passing specs to agree with 3.4. This will not be a complete representation of s3.4 (aka, there will me missing features), but the goal is to change existing features and implemented features to match 3.4 behaviour. - -By the end of this, the sass-spec must pass against 3.4. - -Major issues: -* Variable Scoping -* Color Handling -* Precision - -# 3.2 -This version will focus on edge case fixes. There are a LOT of edge cases in the _todo_ tests and this is the release where we hunt those down like dogs (not that we want to hurt dogs, it's just a figure of speech in English). - -# 3.3 -Dress rehearsal. When we are 99% sure that we've fixed the main issues keeping us from saying we are compliant in s3.4 behaviour. - -# 3.4 -Compass Compatibility. We need to be able to work with Compass and all the other libraries out there. At this point, we are calling LibSass "mature" - -# Beyond 3.4 -Obviously, there is matching Sass 3.5 behaviour. But, beyond that, we'll want to focus on performance, stability, and error handling. These can always be improved upon and are the life's work of an open source project. We'll have to work closely with Sass in the future. diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac index b5a943217..bf05dbfaa 100644 --- a/src/libsass/configure.ac +++ b/src/libsass/configure.ac @@ -9,7 +9,6 @@ AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_FILES([include/sass/version.h]) AC_CONFIG_AUX_DIR([script]) - # These are flags passed to automake # Though they look like gcc flags! AM_INIT_AUTOMAKE([foreign parallel-tests -Wall]) @@ -94,16 +93,21 @@ the --with-sass-spec-dir= argument. ;; esac AC_SUBST(SASS_SPEC_PATH) -else - # we do not really need these paths for non test build - # but automake may error if we do not define them here - SASS_SPEC_PATH=sass-spec - SASS_SASSC_PATH=sassc - AC_SUBST(SASS_SPEC_PATH) - AC_SUBST(SASS_SASSC_PATH) + + # TODO: Remove this when automake requirements are 1.12+ + AC_MSG_CHECKING([whether we can use TAP mode]) + tmp=`$AWK '/TEST_LOG_DRIVER/' $srcdir/GNUmakefile.in` + if test "x$tmp" != "x"; then + use_tap=yes + else + use_tap=no + fi + AC_MSG_RESULT([$use_tap]) + fi AM_CONDITIONAL(ENABLE_TESTS, test "x$enable_tests" = "xyes") +AM_CONDITIONAL(USE_TAP, test "x$use_tap" = "xyes") AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage], diff --git a/src/libsass/context.cpp b/src/libsass/context.cpp deleted file mode 100644 index b199412cd..000000000 --- a/src/libsass/context.cpp +++ /dev/null @@ -1,926 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include -#include - -#include "ast.hpp" -#include "util.hpp" -#include "sass.h" -#include "context.hpp" -#include "plugins.hpp" -#include "constants.hpp" -#include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "output.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include "check_nesting.hpp" -#include "cssize.hpp" -#include "listize.hpp" -#include "extend.hpp" -#include "remove_placeholders.hpp" -#include "functions.hpp" -#include "sass_functions.hpp" -#include "backtrace.hpp" -#include "sass2scss.h" -#include "prelexer.hpp" -#include "emitter.hpp" - -namespace Sass { - using namespace Constants; - using namespace File; - using namespace Sass; - - inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) - { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } - - static std::string safe_input(const char* in_path) - { - // enforce some safe defaults - // used to create relative file links - std::string safe_path(in_path ? in_path : ""); - return safe_path == "" ? "stdin" : safe_path; - } - - static std::string safe_output(const char* out_path, const std::string& input_path = "") - { - std::string safe_path(out_path ? out_path : ""); - // maybe we can extract an output path from input path - if (safe_path == "" && input_path != "") { - int lastindex = static_cast(input_path.find_last_of(".")); - return (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css"; - } - // enforce some safe defaults - // used to create relative file links - return safe_path == "" ? "stdout" : safe_path; - } - - Context::Context(struct Sass_Context& c_ctx) - : CWD(File::get_cwd()), - c_options(c_ctx), - entry_path(""), - head_imports(0), - plugins(), - emitter(c_options), - - ast_gc(), - strings(), - resources(), - sheets(), - subset_map(), - import_stack(), - callee_stack(), - traces(), - c_compiler(NULL), - - c_headers (std::vector()), - c_importers (std::vector()), - c_functions (std::vector()), - - indent (safe_str(c_options.indent, " ")), - linefeed (safe_str(c_options.linefeed, "\n")), - - input_path (make_canonical_path(safe_input(c_options.input_path))), - output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), - source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), - source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) - - { - - // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. - // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. - // include_paths.push_back(CWD); - - // collect more paths from different options - collect_extensions(c_options.extension); - collect_extensions(c_options.extensions); - collect_include_paths(c_options.include_path); - collect_include_paths(c_options.include_paths); - collect_plugin_paths(c_options.plugin_path); - collect_plugin_paths(c_options.plugin_paths); - - // load plugins and register custom behaviors - for(auto plug : plugin_paths) plugins.load_plugins(plug); - for(auto fn : plugins.get_headers()) c_headers.push_back(fn); - for(auto fn : plugins.get_importers()) c_importers.push_back(fn); - for(auto fn : plugins.get_functions()) c_functions.push_back(fn); - - // sort the items by priority (lowest first) - sort (c_headers.begin(), c_headers.end(), sort_importers); - sort (c_importers.begin(), c_importers.end(), sort_importers); - - emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); - - } - - void Context::add_c_function(Sass_Function_Entry function) - { - c_functions.push_back(function); - } - void Context::add_c_header(Sass_Importer_Entry header) - { - c_headers.push_back(header); - // need to sort the array afterwards (no big deal) - sort (c_headers.begin(), c_headers.end(), sort_importers); - } - void Context::add_c_importer(Sass_Importer_Entry importer) - { - c_importers.push_back(importer); - // need to sort the array afterwards (no big deal) - sort (c_importers.begin(), c_importers.end(), sort_importers); - } - - Context::~Context() - { - // resources were allocated by malloc - for (size_t i = 0; i < resources.size(); ++i) { - free(resources[i].contents); - free(resources[i].srcmap); - } - // free all strings we kept alive during compiler execution - for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); - // everything that gets put into sources will be freed by us - // this shouldn't have anything in it anyway!? - for (size_t m = 0; m < import_stack.size(); ++m) { - sass_import_take_source(import_stack[m]); - sass_import_take_srcmap(import_stack[m]); - sass_delete_import(import_stack[m]); - } - // clear inner structures (vectors) and input source - resources.clear(); import_stack.clear(); - subset_map.clear(), sheets.clear(); - } - - Data_Context::~Data_Context() - { - // --> this will be freed by resources - // make sure we free the source even if not processed! - // if (resources.size() == 0 && source_c_str) free(source_c_str); - // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); - // source_c_str = 0; srcmap_c_str = 0; - } - - File_Context::~File_Context() - { - } - - void Context::collect_extensions(const char* exts_str) - { - if (exts_str) { - const char* beg = exts_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - std::string ext(beg, end - beg); - if (!ext.empty()) { - extensions.push_back(ext); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - std::string ext(beg); - if (!ext.empty()) { - extensions.push_back(ext); - } - } - } - - void Context::collect_extensions(string_list* paths_array) - { - while (paths_array) - { - collect_extensions(paths_array->string); - paths_array = paths_array->next; - } - } - - void Context::collect_include_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - std::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - std::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - } - } - - void Context::collect_include_paths(string_list* paths_array) - { - while (paths_array) - { - collect_include_paths(paths_array->string); - paths_array = paths_array->next; - } - } - - void Context::collect_plugin_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - std::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - std::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - } - } - - void Context::collect_plugin_paths(string_list* paths_array) - { - while (paths_array) - { - collect_plugin_paths(paths_array->string); - paths_array = paths_array->next; - } - } - - // resolve the imp_path in base_path or include_paths - // looks for alternatives and returns a list from one directory - std::vector Context::find_includes(const Importer& import) - { - // include configured extensions - std::vector exts(File::defaultExtensions); - if (extensions.size() > 0) { - exts.insert(exts.end(), extensions.begin(), extensions.end()); - } - // make sure we resolve against an absolute path - std::string base_path(rel2abs(import.base_path)); - // first try to resolve the load path relative to the base path - std::vector vec(resolve_includes(base_path, import.imp_path, exts)); - // then search in every include path (but only if nothing found yet) - for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) - { - // call resolve_includes and individual base path and append all results - std::vector resolved(resolve_includes(include_paths[i], import.imp_path, exts)); - if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); - } - // return vector - return vec; - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res) - { - - // do not parse same resource twice - // maybe raise an error in this case - // if (sheets.count(inc.abs_path)) { - // free(res.contents); free(res.srcmap); - // throw std::runtime_error("duplicate resource registered"); - // return; - // } - - // get index for this resource - size_t idx = resources.size(); - - // tell emitter about new resource - emitter.add_source_index(idx); - - // put resources under our control - // the memory will be freed later - resources.push_back(res); - - // add a relative link to the working directory - included_files.push_back(inc.abs_path); - // add a relative link to the source map output file - srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); - - // get pointer to the loaded content - Sass_Import_Entry import = sass_make_import( - inc.imp_path.c_str(), - inc.abs_path.c_str(), - res.contents, - res.srcmap - ); - // add the entry to the stack - import_stack.push_back(import); - - // get pointer to the loaded content - const char* contents = resources[idx].contents; - // keep a copy of the path around (for parserstates) - // ToDo: we clean it, but still not very elegant!? - strings.push_back(sass_copy_c_string(inc.abs_path.c_str())); - // create the initial parser state from resource - ParserState pstate(strings.back(), contents, idx); - - // check existing import stack for possible recursion - for (size_t i = 0; i < import_stack.size() - 2; ++i) { - auto parent = import_stack[i]; - if (std::strcmp(parent->abs_path, import->abs_path) == 0) { - std::string cwd(File::get_cwd()); - // make path relative to the current directory - std::string stack("An @import loop has been found:"); - for (size_t n = 1; n < i + 2; ++n) { - stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + - " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); - } - // implement error throw directly until we - // decided how to handle full stack traces - throw Exception::InvalidSyntax(pstate, traces, stack); - // error(stack, prstate ? *prstate : pstate, import_stack); - } - } - - // create a parser instance from the given c_str buffer - Parser p(Parser::from_c_str(contents, *this, traces, pstate)); - // do not yet dispose these buffers - sass_import_take_source(import); - sass_import_take_srcmap(import); - // then parse the root block - Block_Obj root = p.parse(); - // delete memory of current stack frame - sass_delete_import(import_stack.back()); - // remove current stack frame - import_stack.pop_back(); - // create key/value pair for ast node - std::pair - ast_pair(inc.abs_path, { res, root }); - // register resulting resource - sheets.insert(ast_pair); - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) - { - traces.push_back(Backtrace(prstate)); - register_resource(inc, res); - traces.pop_back(); - } - - // Add a new import to the context (called from `import_url`) - Include Context::load_import(const Importer& imp, ParserState pstate) - { - - // search for valid imports (ie. partials) on the filesystem - // this may return more than one valid result (ambiguous imp_path) - const std::vector resolved(find_includes(imp)); - - // error nicely on ambiguous imp_path - if (resolved.size() > 1) { - std::stringstream msg_stream; - msg_stream << "It's not clear which file to import for "; - msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; - msg_stream << "Candidates:" << "\n"; - for (size_t i = 0, L = resolved.size(); i < L; ++i) - { msg_stream << " " << resolved[i].imp_path << "\n"; } - msg_stream << "Please delete or rename all but one of these files." << "\n"; - error(msg_stream.str(), pstate, traces); - } - - // process the resolved entry - else if (resolved.size() == 1) { - bool use_cache = c_importers.size() == 0; - if (resolved[0].deprecated) { - // emit deprecation warning when import resolves to a .css file - deprecated( - "Including .css files with @import is non-standard behaviour which will be removed in future versions of LibSass.", - "Use a custom importer to maintain this behaviour. Check your implementations documentation on how to create a custom importer.", - true, pstate - ); - } - // use cache for the resource loading - if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; - // try to read the content of the resolved file entry - // the memory buffer returned must be freed by us! - if (char* contents = read_file(resolved[0].abs_path)) { - // register the newly resolved file resource - register_resource(resolved[0], { contents, 0 }, pstate); - // return resolved entry - return resolved[0]; - } - } - - // nothing found - return { imp, "" }; - - } - - void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { - - ParserState pstate(imp->pstate()); - std::string imp_path(unquote(load_path)); - std::string protocol("file"); - - using namespace Prelexer; - if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { - - protocol = std::string(imp_path.c_str(), proto - 3); - // if (protocol.compare("file") && true) { } - } - - // add urls (protocol other than file) and urls without procotol to `urls` member - // ToDo: if ctx_path is already a file resource, we should not add it here? - if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { - imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); - } - else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { - String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); - Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); - Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); - loc_args->append(loc_arg); - Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); - imp->urls().push_back(new_url); - } - else { - const Importer importer(imp_path, ctx_path); - Include include(load_import(importer, pstate)); - if (include.abs_path.empty()) { - error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); - } - imp->incs().push_back(include); - } - - } - - - // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet - bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) - { - // unique counter - size_t count = 0; - // need one correct import - bool has_import = false; - // process all custom importers (or custom headers) - for (Sass_Importer_Entry& importer_ent : importers) { - // int priority = sass_importer_get_priority(importer); - Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); - // skip importer if it returns NULL - if (Sass_Import_List includes = - fn(load_path.c_str(), importer_ent, c_compiler) - ) { - // get c pointer copy to iterate over - Sass_Import_List it_includes = includes; - while (*it_includes) { ++count; - // create unique path to use as key - std::string uniq_path = load_path; - if (!only_one && count) { - std::stringstream path_strm; - path_strm << uniq_path << ":" << count; - uniq_path = path_strm.str(); - } - // create the importer struct - Importer importer(uniq_path, ctx_path); - // query data from the current include - Sass_Import_Entry include_ent = *it_includes; - char* source = sass_import_take_source(include_ent); - char* srcmap = sass_import_take_srcmap(include_ent); - size_t line = sass_import_get_error_line(include_ent); - size_t column = sass_import_get_error_column(include_ent); - const char *abs_path = sass_import_get_abs_path(include_ent); - // handle error message passed back from custom importer - // it may (or may not) override the line and column info - if (const char* err_message = sass_import_get_error_message(include_ent)) { - if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); - if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); - else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); - } - // content for import was set - else if (source) { - // resolved abs_path should be set by custom importer - // use the created uniq_path as fallback (maybe enforce) - std::string path_key(abs_path ? abs_path : uniq_path); - // create the importer struct - Include include(importer, path_key); - // attach information to AST node - imp->incs().push_back(include); - // register the resource buffers - register_resource(include, { source, srcmap }, pstate); - } - // only a path was retuned - // try to load it like normal - else if(abs_path) { - // checks some urls to preserve - // `http://`, `https://` and `//` - // or dispatchs to `import_file` - // which will check for a `.css` extension - // or resolves the file on the filesystem - // added and resolved via `add_file` - // finally stores everything on `imp` - import_url(imp, abs_path, ctx_path); - } - // move to next - ++it_includes; - } - // deallocate the returned memory - sass_delete_import_list(includes); - // set success flag - has_import = true; - // break out of loop - if (only_one) break; - } - } - // return result - return has_import; - } - - void register_function(Context&, Signature sig, Native_Function f, Env* env); - void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); - void register_overload_stub(Context&, std::string name, Env* env); - void register_built_in_functions(Context&, Env* env); - void register_c_functions(Context&, Env* env, Sass_Function_List); - void register_c_function(Context&, Env* env, Sass_Function_Entry); - - char* Context::render(Block_Obj root) - { - // check for valid block - if (!root) return 0; - // start the render process - root->perform(&emitter); - // finish emitter stream - emitter.finalize(); - // get the resulting buffer from stream - OutputBuffer emitted = emitter.get_buffer(); - // should we append a source map url? - if (!c_options.omit_source_map_url) { - // generate an embeded source map - if (c_options.source_map_embed) { - emitted.buffer += linefeed; - emitted.buffer += format_embedded_source_map(); - } - // or just link the generated one - else if (source_map_file != "") { - emitted.buffer += linefeed; - emitted.buffer += format_source_mapping_url(source_map_file); - } - } - // create a copy of the resulting buffer string - // this must be freed or taken over by implementor - return sass_copy_c_string(emitted.buffer.c_str()); - } - - void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, ParserState pstate) - { - // create a custom import to resolve headers - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - // dispatch headers which will add custom functions - // custom headers are added to the import instance - call_headers(entry_path, ctx_path, pstate, imp); - // increase head count to skip later - head_imports += resources.size() - 1; - // add the statement if we have urls - if (!imp->urls().empty()) root->append(imp); - // process all other resources (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); - } - } - - Block_Obj File_Context::parse() - { - - // check if entry file is given - if (input_path.empty()) return 0; - - // create absolute path from input filename - // ToDo: this should be resolved via custom importers - std::string abs_path(rel2abs(input_path, CWD)); - - // try to load the entry file - char* contents = read_file(abs_path); - - // alternatively also look inside each include path folder - // I think this differs from ruby sass (IMO too late to remove) - for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { - // build absolute path for this include path entry - abs_path = rel2abs(input_path, include_paths[i]); - // try to load the resulting path - contents = read_file(abs_path); - } - - // abort early if no content could be loaded (various reasons) - if (!contents) throw std::runtime_error("File to read not found or unreadable: " + input_path); - - // store entry path - entry_path = abs_path; - - // create entry only for import stack - Sass_Import_Entry import = sass_make_import( - input_path.c_str(), - entry_path.c_str(), - contents, - 0 - ); - // add the entry to the stack - import_stack.push_back(import); - - // create the source entry for file entry - register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); - - // create root ast tree node - return compile(); - - } - - Block_Obj Data_Context::parse() - { - - // check if source string is given - if (!source_c_str) return 0; - - // convert indented sass syntax - if(c_options.is_indented_syntax_src) { - // call sass2scss to convert the string - char * converted = sass2scss(source_c_str, - // preserve the structure as much as possible - SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); - // replace old source_c_str with converted - free(source_c_str); source_c_str = converted; - } - - // remember entry path (defaults to stdin for string) - entry_path = input_path.empty() ? "stdin" : input_path; - - // ToDo: this may be resolved via custom importers - std::string abs_path(rel2abs(entry_path)); - char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); - strings.push_back(abs_path_c_str); - - // create entry only for the import stack - Sass_Import_Entry import = sass_make_import( - entry_path.c_str(), - abs_path_c_str, - source_c_str, - srcmap_c_str - ); - // add the entry to the stack - import_stack.push_back(import); - - // register a synthetic resource (path does not really exist, skip in includes) - register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); - - // create root ast tree node - return compile(); - } - - - - // parse root block from includes - Block_Obj Context::compile() - { - // abort if there is no data - if (resources.size() == 0) return 0; - // get root block from the first style sheet - Block_Obj root = sheets.at(entry_path).root; - // abort on invalid root - if (root.isNull()) return 0; - Env global; // create root environment - // register built-in functions on env - register_built_in_functions(*this, &global); - // register custom functions (defined via C-API) - for (size_t i = 0, S = c_functions.size(); i < S; ++i) - { register_c_function(*this, &global, c_functions[i]); } - // create initial backtrace entry - // create crtp visitor objects - Expand expand(*this, &global); - Cssize cssize(*this); - CheckNesting check_nesting; - // check nesting in all files - for (auto sheet : sheets) { - auto styles = sheet.second; - check_nesting(styles.root); - } - // expand and eval the tree - root = expand(root); - // check nesting - check_nesting(root); - // merge and bubble certain rules - root = cssize(root); - // should we extend something? - if (!subset_map.empty()) { - // create crtp visitor object - Extend extend(subset_map); - extend.setEval(expand.eval); - // extend tree nodes - extend(root); - } - - // clean up by removing empty placeholders - // ToDo: maybe we can do this somewhere else? - Remove_Placeholders remove_placeholders; - root->perform(&remove_placeholders); - // return processed tree - return root; - } - // EO compile - - std::string Context::format_embedded_source_map() - { - std::string map = emitter.render_srcmap(*this); - std::istringstream is( map ); - std::ostringstream buffer; - base64::encoder E; - E.encode(is, buffer); - std::string url = "data:application/json;base64," + buffer.str(); - url.erase(url.size() - 1); - return "/*# sourceMappingURL=" + url + " */"; - } - - std::string Context::format_source_mapping_url(const std::string& file) - { - std::string url = abs2rel(file, output_path, CWD); - return "/*# sourceMappingURL=" + url + " */"; - } - - char* Context::render_srcmap() - { - if (source_map_file == "") return 0; - std::string map = emitter.render_srcmap(*this); - return sass_copy_c_string(map.c_str()); - } - - - // for data context we want to start after "stdin" - // we probably always want to skip the header includes? - std::vector Context::get_included_files(bool skip, size_t headers) - { - // create a copy of the vector for manipulations - std::vector includes = included_files; - if (includes.size() == 0) return includes; - if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } - else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } - includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); - std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); - return includes; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) - { - Definition_Ptr def = make_native_function(sig, f, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) - { - Definition_Ptr def = make_native_function(sig, f, ctx); - std::stringstream ss; - ss << def->name() << "[f]" << arity; - def->environment(env); - (*env)[ss.str()] = def; - } - - void register_overload_stub(Context& ctx, std::string name, Env* env) - { - Definition_Ptr stub = SASS_MEMORY_NEW(Definition, - ParserState("[built-in function]"), - 0, - name, - 0, - 0, - true); - (*env)[name + "[f]"] = stub; - } - - - void register_built_in_functions(Context& ctx, Env* env) - { - using namespace Functions; - // RGB Functions - register_function(ctx, rgb_sig, rgb, env); - register_overload_stub(ctx, "rgba", env); - register_function(ctx, rgba_4_sig, rgba_4, 4, env); - register_function(ctx, rgba_2_sig, rgba_2, 2, env); - register_function(ctx, red_sig, red, env); - register_function(ctx, green_sig, green, env); - register_function(ctx, blue_sig, blue, env); - register_function(ctx, mix_sig, mix, env); - // HSL Functions - register_function(ctx, hsl_sig, hsl, env); - register_function(ctx, hsla_sig, hsla, env); - register_function(ctx, hue_sig, hue, env); - register_function(ctx, saturation_sig, saturation, env); - register_function(ctx, lightness_sig, lightness, env); - register_function(ctx, adjust_hue_sig, adjust_hue, env); - register_function(ctx, lighten_sig, lighten, env); - register_function(ctx, darken_sig, darken, env); - register_function(ctx, saturate_sig, saturate, env); - register_function(ctx, desaturate_sig, desaturate, env); - register_function(ctx, grayscale_sig, grayscale, env); - register_function(ctx, complement_sig, complement, env); - register_function(ctx, invert_sig, invert, env); - // Opacity Functions - register_function(ctx, alpha_sig, alpha, env); - register_function(ctx, opacity_sig, alpha, env); - register_function(ctx, opacify_sig, opacify, env); - register_function(ctx, fade_in_sig, opacify, env); - register_function(ctx, transparentize_sig, transparentize, env); - register_function(ctx, fade_out_sig, transparentize, env); - // Other Color Functions - register_function(ctx, adjust_color_sig, adjust_color, env); - register_function(ctx, scale_color_sig, scale_color, env); - register_function(ctx, change_color_sig, change_color, env); - register_function(ctx, ie_hex_str_sig, ie_hex_str, env); - // String Functions - register_function(ctx, unquote_sig, sass_unquote, env); - register_function(ctx, quote_sig, sass_quote, env); - register_function(ctx, str_length_sig, str_length, env); - register_function(ctx, str_insert_sig, str_insert, env); - register_function(ctx, str_index_sig, str_index, env); - register_function(ctx, str_slice_sig, str_slice, env); - register_function(ctx, to_upper_case_sig, to_upper_case, env); - register_function(ctx, to_lower_case_sig, to_lower_case, env); - // Number Functions - register_function(ctx, percentage_sig, percentage, env); - register_function(ctx, round_sig, round, env); - register_function(ctx, ceil_sig, ceil, env); - register_function(ctx, floor_sig, floor, env); - register_function(ctx, abs_sig, abs, env); - register_function(ctx, min_sig, min, env); - register_function(ctx, max_sig, max, env); - register_function(ctx, random_sig, random, env); - // List Functions - register_function(ctx, length_sig, length, env); - register_function(ctx, nth_sig, nth, env); - register_function(ctx, set_nth_sig, set_nth, env); - register_function(ctx, index_sig, index, env); - register_function(ctx, join_sig, join, env); - register_function(ctx, append_sig, append, env); - register_function(ctx, zip_sig, zip, env); - register_function(ctx, list_separator_sig, list_separator, env); - register_function(ctx, is_bracketed_sig, is_bracketed, env); - // Map Functions - register_function(ctx, map_get_sig, map_get, env); - register_function(ctx, map_merge_sig, map_merge, env); - register_function(ctx, map_remove_sig, map_remove, env); - register_function(ctx, map_keys_sig, map_keys, env); - register_function(ctx, map_values_sig, map_values, env); - register_function(ctx, map_has_key_sig, map_has_key, env); - register_function(ctx, keywords_sig, keywords, env); - // Introspection Functions - register_function(ctx, type_of_sig, type_of, env); - register_function(ctx, unit_sig, unit, env); - register_function(ctx, unitless_sig, unitless, env); - register_function(ctx, comparable_sig, comparable, env); - register_function(ctx, variable_exists_sig, variable_exists, env); - register_function(ctx, global_variable_exists_sig, global_variable_exists, env); - register_function(ctx, function_exists_sig, function_exists, env); - register_function(ctx, mixin_exists_sig, mixin_exists, env); - register_function(ctx, feature_exists_sig, feature_exists, env); - register_function(ctx, call_sig, call, env); - register_function(ctx, content_exists_sig, content_exists, env); - register_function(ctx, get_function_sig, get_function, env); - // Boolean Functions - register_function(ctx, not_sig, sass_not, env); - register_function(ctx, if_sig, sass_if, env); - // Misc Functions - register_function(ctx, inspect_sig, inspect, env); - register_function(ctx, unique_id_sig, unique_id, env); - // Selector functions - register_function(ctx, selector_nest_sig, selector_nest, env); - register_function(ctx, selector_append_sig, selector_append, env); - register_function(ctx, selector_extend_sig, selector_extend, env); - register_function(ctx, selector_replace_sig, selector_replace, env); - register_function(ctx, selector_unify_sig, selector_unify, env); - register_function(ctx, is_superselector_sig, is_superselector, env); - register_function(ctx, simple_selectors_sig, simple_selectors, env); - register_function(ctx, selector_parse_sig, selector_parse, env); - } - - void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) - { - while (descrs && *descrs) { - register_c_function(ctx, env, *descrs); - ++descrs; - } - } - void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) - { - Definition_Ptr def = make_c_function(descr, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - -} diff --git a/src/libsass/context.h b/src/libsass/context.h deleted file mode 100644 index 29754b75f..000000000 --- a/src/libsass/context.h +++ /dev/null @@ -1,173 +0,0 @@ -#ifndef SASS_C_CONTEXT_H -#define SASS_C_CONTEXT_H - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; // base struct -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// Create and initialize an option struct -ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); -// Create and initialize a specific context -ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); -ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); -ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Usefull if you only want to query the included files -ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); -ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); -ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); -ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for context from specific implementation -ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - -// Getters for Context_Options from Sass_Context -ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); -ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); -ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - - -// Getters for Context_Option values -ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); -ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); -ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); - -// Setters for Context_Option values -ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); -ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); -ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); -ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); -ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); -ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); -ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); -ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); -ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); -ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); -ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); -ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); -ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); -ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); - - -// Getters for Sass_Context values -ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); -ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for options include path array -ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); - -// Calculate the size of the stored null terminated array -ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); - -// Take ownership of memory (value on context is set to 0) -ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options -ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); -ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// Push function for import extenions -ADDAPI void ADDCALL sass_option_push_import_extension (struct Sass_Options* options, const char* ext); - -// Push function for paths (no manipulation support for now) -ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); - -// Resolve a file via the given include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); -ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); - -// Resolve a file relative to last import or include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/src/libsass/context.hpp b/src/libsass/context.hpp deleted file mode 100644 index f14e69f6d..000000000 --- a/src/libsass/context.hpp +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef SASS_CONTEXT_H -#define SASS_CONTEXT_H - -#include -#include -#include - -#define BUFFERSIZE 255 -#include "b64/encode.h" - -#include "ast_fwd_decl.hpp" -#include "kwd_arg_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "sass_context.hpp" -#include "environment.hpp" -#include "source_map.hpp" -#include "subset_map.hpp" -#include "backtrace.hpp" -#include "output.hpp" -#include "plugins.hpp" -#include "file.hpp" - - -struct Sass_Function; - -namespace Sass { - - class Context { - public: - void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); - bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; - bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; - - private: - bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); - - public: - const std::string CWD; - struct Sass_Options& c_options; - std::string entry_path; - size_t head_imports; - Plugins plugins; - Output emitter; - - // generic ast node garbage container - // used to avoid possible circular refs - std::vector ast_gc; - // resources add under our control - // these are guaranteed to be freed - std::vector strings; - std::vector resources; - std::map sheets; - Subset_Map subset_map; - std::vector import_stack; - std::vector callee_stack; - std::vector traces; - - struct Sass_Compiler* c_compiler; - - // absolute paths to includes - std::vector included_files; - // relative includes for sourcemap - std::vector srcmap_links; - // vectors above have same size - - std::vector plugin_paths; // relative paths to load plugins - std::vector include_paths; // lookup paths for includes - std::vector extensions; // lookup extensions for imports` - - - - - - void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); - - std::vector c_headers; - std::vector c_importers; - std::vector c_functions; - - void add_c_header(Sass_Importer_Entry header); - void add_c_importer(Sass_Importer_Entry importer); - void add_c_function(Sass_Function_Entry function); - - const std::string indent; // String to be used for indentation - const std::string linefeed; // String to be used for line feeds - const std::string input_path; // for relative paths in src-map - const std::string output_path; // for relative paths to the output - const std::string source_map_file; // path to source map file (enables feature) - const std::string source_map_root; // path for sourceRoot property (pass-through) - - virtual ~Context(); - Context(struct Sass_Context&); - virtual Block_Obj parse() = 0; - virtual Block_Obj compile(); - virtual char* render(Block_Obj root); - virtual char* render_srcmap(); - - void register_resource(const Include&, const Resource&); - void register_resource(const Include&, const Resource&, ParserState&); - std::vector find_includes(const Importer& import); - Include load_import(const Importer&, ParserState pstate); - - Sass_Output_Style output_style() { return c_options.output_style; }; - std::vector get_included_files(bool skip = false, size_t headers = 0); - - private: - void collect_plugin_paths(const char* paths_str); - void collect_plugin_paths(string_list* paths_array); - void collect_include_paths(const char* paths_str); - void collect_include_paths(string_list* paths_array); - void collect_extensions(const char* extensions_str); - void collect_extensions(string_list* extensions_array); - std::string format_embedded_source_map(); - std::string format_source_mapping_url(const std::string& out_path); - - - // void register_built_in_functions(Env* env); - // void register_function(Signature sig, Native_Function f, Env* env); - // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); - // void register_overload_stub(std::string name, Env* env); - - public: - const std::string& cwd() { return CWD; }; - }; - - class File_Context : public Context { - public: - File_Context(struct Sass_File_Context& ctx) - : Context(ctx) - { } - virtual ~File_Context(); - virtual Block_Obj parse(); - }; - - class Data_Context : public Context { - public: - char* source_c_str; - char* srcmap_c_str; - Data_Context(struct Sass_Data_Context& ctx) - : Context(ctx) - { - source_c_str = ctx.source_string; - srcmap_c_str = ctx.srcmap_string; - ctx.source_string = 0; // passed away - ctx.srcmap_string = 0; // passed away - } - virtual ~Data_Context(); - virtual Block_Obj parse(); - }; - -} - -#endif diff --git a/src/libsass/contributing.md b/src/libsass/contributing.md deleted file mode 100644 index 4a2d470ef..000000000 --- a/src/libsass/contributing.md +++ /dev/null @@ -1,17 +0,0 @@ -First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! - -## I'm a programmer - -Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! - -To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). - -## I'm not a backend programmer - -COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. - -Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. - -## I don't know what a computer is? - -Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/src/libsass/cssize.cpp b/src/libsass/cssize.cpp deleted file mode 100644 index 6a12fdf7b..000000000 --- a/src/libsass/cssize.cpp +++ /dev/null @@ -1,606 +0,0 @@ -#include "sass.hpp" -#include -#include -#include - -#include "cssize.hpp" -#include "context.hpp" - -namespace Sass { - - Cssize::Cssize(Context& ctx) - : ctx(ctx), - traces(ctx.traces), - block_stack(std::vector()), - p_stack(std::vector()) - { } - - Statement_Ptr Cssize::parent() - { - return p_stack.size() ? p_stack.back() : block_stack.front(); - } - - Block_Ptr Cssize::operator()(Block_Ptr b) - { - Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); - // bb->tabs(b->tabs()); - block_stack.push_back(bb); - append_block(b, bb); - block_stack.pop_back(); - return bb.detach(); - } - - Statement_Ptr Cssize::operator()(Trace_Ptr t) - { - traces.push_back(Backtrace(t->pstate())); - auto result = t->block()->perform(this); - traces.pop_back(); - return result; - } - - Statement_Ptr Cssize::operator()(Declaration_Ptr d) - { - String_Obj property = Cast(d->property()); - - if (Declaration_Ptr dd = Cast(parent())) { - String_Obj parent_property = Cast(dd->property()); - property = SASS_MEMORY_NEW(String_Constant, - d->property()->pstate(), - parent_property->to_string() + "-" + property->to_string()); - if (!dd->value()) { - d->tabs(dd->tabs() + 1); - } - } - - Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, - d->pstate(), - property, - d->value(), - d->is_important(), - d->is_custom_property()); - dd->is_indented(d->is_indented()); - dd->tabs(d->tabs()); - - p_stack.push_back(dd); - Block_Obj bb = d->block() ? operator()(d->block()) : NULL; - p_stack.pop_back(); - - if (bb && bb->length()) { - if (dd->value() && !dd->value()->is_invisible()) { - bb->unshift(dd); - } - return bb.detach(); - } - else if (dd->value() && !dd->value()->is_invisible()) { - return dd.detach(); - } - - return 0; - } - - Statement_Ptr Cssize::operator()(Directive_Ptr r) - { - if (!r->block() || !r->block()->length()) return r; - - if (parent()->statement_type() == Statement::RULESET) - { - return (r->is_keyframes()) ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); - } - - p_stack.push_back(r); - Directive_Obj rr = SASS_MEMORY_NEW(Directive, - r->pstate(), - r->keyword(), - r->selector(), - r->block() ? operator()(r->block()) : 0); - if (r->value()) rr->value(r->value()); - p_stack.pop_back(); - - bool directive_exists = false; - size_t L = rr->block() ? rr->block()->length() : 0; - for (size_t i = 0; i < L && !directive_exists; ++i) { - Statement_Obj s = r->block()->at(i); - if (s->statement_type() != Statement::BUBBLE) directive_exists = true; - else { - Bubble_Obj s_obj = Cast(s); - s = s_obj->node(); - if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; - else directive_exists = (Cast(s)->keyword() == rr->keyword()); - } - - } - - Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); - if (!(directive_exists || rr->is_keyframes())) - { - Directive_Ptr empty_node = Cast(rr); - empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); - result->append(empty_node); - } - - Block_Obj db = rr->block(); - if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); - Block_Obj ss = debubble(db, rr); - for (size_t i = 0, L = ss->length(); i < L; ++i) { - result->append(ss->at(i)); - } - - return result; - } - - Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) - { - if (!r->block() || !r->block()->length()) return r; - - Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, - r->pstate(), - operator()(r->block())); - if (!r->name().isNull()) rr->name(r->name()); - - return debubble(rr->block(), rr); - } - - Statement_Ptr Cssize::operator()(Ruleset_Ptr r) - { - p_stack.push_back(r); - // this can return a string schema - // string schema is not a statement! - // r->block() is already a string schema - // and that is comming from propset expand - Block_Ptr bb = operator()(r->block()); - // this should protect us (at least a bit) from our mess - // fixing this properly is harder that it should be ... - if (Cast(bb) == NULL) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, - r->pstate(), - r->selector(), - bb); - - rr->is_root(r->is_root()); - // rr->tabs(r->block()->tabs()); - p_stack.pop_back(); - - if (!rr->block()) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - - Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - for (size_t i = 0, L = rr->block()->length(); i < L; i++) - { - Statement_Ptr s = rr->block()->at(i); - if (bubblable(s)) rules->append(s); - if (!bubblable(s)) props->append(s); - } - - if (props->length()) - { - Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - pb->concat(props); - rr->block(pb); - - for (size_t i = 0, L = rules->length(); i < L; i++) - { - Statement_Ptr stm = rules->at(i); - stm->tabs(stm->tabs() + 1); - } - - rules->unshift(rr); - } - - Block_Ptr ptr = rules; - rules = debubble(rules); - void* lp = ptr; - void* rp = rules; - if (lp != rp) { - Block_Obj obj = ptr; - } - - if (!(!rules->length() || - !bubblable(rules->last()) || - parent()->statement_type() == Statement::RULESET)) - { - rules->last()->group_end(true); - } - return rules; - } - - Statement_Ptr Cssize::operator()(Null_Ptr m) - { - return 0; - } - - Statement_Ptr Cssize::operator()(Media_Block_Ptr m) - { - if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } - - if (parent()->statement_type() == Statement::MEDIA) - { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } - - p_stack.push_back(m); - - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); - } - - Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) - { - if (!m->block()->length()) - { return m; } - - if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } - - p_stack.push_back(m); - - Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, - m->pstate(), - m->condition(), - operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); - } - - Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) - { - bool tmp = false; - for (size_t i = 0, L = p_stack.size(); i < L; ++i) { - Statement_Ptr s = p_stack[i]; - tmp |= m->exclude_node(s); - } - - if (!tmp && m->block()) - { - Block_Ptr bb = operator()(m->block()); - for (size_t i = 0, L = bb->length(); i < L; ++i) { - // (bb->elements())[i]->tabs(m->tabs()); - Statement_Obj stm = bb->at(i); - if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); - } - if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); - return bb; - } - - if (m->exclude_node(parent())) - { - return SASS_MEMORY_NEW(Bubble, m->pstate(), m); - } - - return bubble(m); - } - - Statement_Ptr Cssize::bubble(Directive_Ptr m) - { - Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); - wrapper_block->append(new_rule); - Directive_Obj mm = SASS_MEMORY_NEW(Directive, - m->pstate(), - m->keyword(), - m->selector(), - wrapper_block); - if (m->value()) mm->value(m->value()); - - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) - { - if (!m || !m->block()) return NULL; - Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - if (new_rule) { - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - wrapper_block->append(new_rule); - } - - At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, - m->pstate(), - wrapper_block, - m->expression()); - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) - { - Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, - m->pstate(), - m->condition(), - wrapper_block); - - mm->tabs(m->tabs()); - - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement_Ptr Cssize::bubble(Media_Block_Ptr m) - { - Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - wrapper_block); - - mm->tabs(m->tabs()); - - return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - } - - bool Cssize::bubblable(Statement_Ptr s) - { - return Cast(s) || s->bubbles(); - } - - Block_Ptr Cssize::flatten(Block_Ptr b) - { - Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr ss = b->at(i); - if (Block_Ptr bb = Cast(ss)) { - Block_Obj bs = flatten(bb); - for (size_t j = 0, K = bs->length(); j < K; ++j) { - result->append(bs->at(j)); - } - } - else { - result->append(ss); - } - } - return result; - } - - std::vector> Cssize::slice_by_bubble(Block_Ptr b) - { - std::vector> results; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj value = b->at(i); - bool key = Cast(value) != NULL; - - if (!results.empty() && results.back().first == key) - { - Block_Obj wrapper_block = results.back().second; - wrapper_block->append(value); - } - else - { - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); - wrapper_block->append(value); - results.push_back(std::make_pair(key, wrapper_block)); - } - } - return results; - } - - Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) - { - Has_Block_Obj previous_parent = 0; - std::vector> baz = slice_by_bubble(children); - Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); - - for (size_t i = 0, L = baz.size(); i < L; ++i) { - bool is_bubble = baz[i].first; - Block_Obj slice = baz[i].second; - - if (!is_bubble) { - if (!parent) { - result->append(slice); - } - else if (previous_parent) { - previous_parent->block()->concat(slice); - } - else { - previous_parent = Cast(SASS_MEMORY_COPY(parent)); - previous_parent->block(slice); - previous_parent->tabs(parent->tabs()); - - result->append(previous_parent); - } - continue; - } - - for (size_t j = 0, K = slice->length(); j < K; ++j) - { - Statement_Ptr ss; - Statement_Obj stm = slice->at(j); - // this has to go now here (too bad) - Bubble_Obj node = Cast(stm); - Media_Block_Ptr m1 = NULL; - Media_Block_Ptr m2 = NULL; - if (parent) m1 = Cast(parent); - if (node) m2 = Cast(node->node()); - if (!parent || - parent->statement_type() != Statement::MEDIA || - node->node()->statement_type() != Statement::MEDIA || - (m1 && m2 && *m1->media_queries() == *m2->media_queries()) - ) - { - ss = node->node(); - } - else - { - List_Obj mq = merge_media_queries( - Cast(node->node()), - Cast(parent) - ); - if (!mq->length()) continue; - if (Media_Block* b = Cast(node->node())) { - b->media_queries(mq); - } - ss = node->node(); - } - - if (!ss) continue; - - ss->tabs(ss->tabs() + node->tabs()); - ss->group_end(node->group_end()); - - Block_Obj bb = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - bb->append(ss->perform(this)); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - - Block_Ptr wrapper = flatten(bb); - wrapper_block->append(wrapper); - - if (wrapper->length()) { - previous_parent = NULL; - } - - if (wrapper_block) { - result->append(wrapper_block); - } - } - } - - return flatten(result); - } - - Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) - { - return static_cast(n); - } - - void Cssize::append_block(Block_Ptr b, Block_Ptr cur) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj ith = b->at(i)->perform(this); - if (Block_Ptr bb = Cast(ith)) { - for (size_t j = 0, K = bb->length(); j < K; ++j) { - cur->append(bb->at(j)); - } - } - else if (ith) { - cur->append(ith); - } - } - } - - List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) - { - List_Ptr qq = SASS_MEMORY_NEW(List, - m1->media_queries()->pstate(), - m1->media_queries()->length(), - SASS_COMMA); - - for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { - for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { - Expression_Obj l1 = m1->media_queries()->at(i); - Expression_Obj l2 = m2->media_queries()->at(j); - Media_Query_Ptr mq1 = Cast(l1); - Media_Query_Ptr mq2 = Cast(l2); - Media_Query_Ptr mq = merge_media_query(mq1, mq2); - if (mq) qq->append(mq); - } - } - - return qq; - } - - - Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) - { - - std::string type; - std::string mod; - - std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); - std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; - std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); - std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; - - - if (t1.empty()) t1 = t2; - if (t2.empty()) t2 = t1; - - if ((m1 == "not") ^ (m2 == "not")) { - if (t1 == t2) { - return 0; - } - type = m1 == "not" ? t2 : t1; - mod = m1 == "not" ? m2 : m1; - } - else if (m1 == "not" && m2 == "not") { - if (t1 != t2) { - return 0; - } - type = t1; - mod = "not"; - } - else if (t1 != t2) { - return 0; - } else { - type = t1; - mod = m1.empty() ? m2 : m1; - } - - Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, - mq1->pstate(), - 0, - mq1->length() + mq2->length(), - mod == "not", - mod == "only"); - - if (!type.empty()) { - mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); - } - - mm->concat(mq2); - mm->concat(mq1); - - return mm; - } -} diff --git a/src/libsass/custom-functions-internal.md b/src/libsass/custom-functions-internal.md deleted file mode 100644 index 57fec82b8..000000000 --- a/src/libsass/custom-functions-internal.md +++ /dev/null @@ -1,122 +0,0 @@ -# Developer Documentation - -Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. - -## Sass_C_Function_Descriptor - -```C -struct Sass_C_Function_Descriptor { - const char* signature; - Sass_C_Function function; - void* cookie; -}; -``` - -- `signature`: The function declaration, like `foo($bar, $baz:1)` -- `function`: Reference to the C function callback -- `cookie`: any pointer you want to attach - -### signature - -The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. - - foo($bar, $baz: 2) - -In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. - -### function - -The callback function needs to be of the following form: - -```C -union Sass_Value* call_sass_function( - const union Sass_Value* s_args, - void* cookie -) { - return sass_clone_value(s_args); -} -``` - -### cookie - -The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! - -## Sass_Values - -```C -// allocate memory (copies passed strings) -union Sass_Value* sass_make_null (void); -union Sass_Value* sass_make_boolean (bool val); -union Sass_Value* sass_make_string (const char* val); -union Sass_Value* sass_make_qstring (const char* val); -union Sass_Value* sass_make_number (double val, const char* unit); -union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -union Sass_Value* sass_make_map (size_t len); -union Sass_Value* sass_make_error (const char* msg); -union Sass_Value* sass_make_warning (const char* msg); - -// Make a deep cloned copy of the given sass value -union Sass_Value* sass_clone_value (const union Sass_Value* val); - -// deallocate memory (incl. all copied memory) -void sass_delete_value (const union Sass_Value* val); -``` - -## Example main.c - -```C -#include -#include -#include "sass/context.h" - -union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) -{ - // we actually abuse the void* to store an "int" - return sass_make_number((size_t)cookie, "px"); -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate a custom function caller - Sass_C_Function_Callback fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - - // create list of all custom functions - Sass_C_Function_List fn_list = sass_make_function_list(1); - sass_function_set_list_entry(fn_list, 0, fn_foo); - sass_option_set_c_functions(ctx_opt, fn_list); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -## Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: foo(); }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` diff --git a/src/libsass/debugger.hpp b/src/libsass/debugger.hpp deleted file mode 100644 index f1ceabd9a..000000000 --- a/src/libsass/debugger.hpp +++ /dev/null @@ -1,801 +0,0 @@ -#ifndef SASS_DEBUGGER_H -#define SASS_DEBUGGER_H - -#include -#include -#include "node.hpp" -#include "ast_fwd_decl.hpp" - -using namespace Sass; - -inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); - -inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); -} - -inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : set) { - debug_ast(pair, ind + ""); - // debug_ast(set[pair], ind + "first: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) -{ - size_t pos = 0; - while((pos = str.find(oldStr, pos)) != std::string::npos) - { - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); - } - return str; -} - -inline std::string prettyprint(const std::string& str) { - std::string clean = str_replace(str, "\n", "\\n"); - clean = str_replace(clean, " ", "\\t"); - clean = str_replace(clean, "\r", "\\r"); - return clean; -} - -inline std::string longToHex(long long t) { - std::stringstream is; - is << std::hex << t; - return is.str(); -} - -inline std::string pstate_source_position(AST_Node_Ptr node) -{ - std::stringstream str; - Position start(node->pstate()); - Position end(start + node->pstate().offset); - str << (start.file == std::string::npos ? -1 : start.file) - << "@[" << start.line << ":" << start.column << "]" - << "-[" << end.line << ":" << end.column << "]"; -#ifdef DEBUG_SHARED_PTR - str << "x" << node->getRefCount() << "" - << " " << node->getDbgFile() - << "@" << node->getDbgLine(); -#endif - return str.str(); -} - -inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) -{ - if (node == 0) return; - if (ind == "") std::cerr << "####################################################################\n"; - if (Cast(node)) { - Bubble_Ptr bubble = Cast(node); - std::cerr << ind << "Bubble " << bubble; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << bubble->tabs(); - std::cerr << std::endl; - debug_ast(bubble->node(), ind + " ", env); - } else if (Cast(node)) { - Trace_Ptr trace = Cast(node); - std::cerr << ind << "Trace " << trace; - std::cerr << " (" << pstate_source_position(node) << ")" - << " [name:" << trace->name() << ", type: " << trace->type() << "]" - << std::endl; - debug_ast(trace->block(), ind + " ", env); - } else if (Cast(node)) { - At_Root_Block_Ptr root_block = Cast(node); - std::cerr << ind << "At_Root_Block " << root_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << root_block->tabs(); - std::cerr << std::endl; - debug_ast(root_block->expression(), ind + ":", env); - debug_ast(root_block->block(), ind + " ", env); - } else if (Cast(node)) { - Selector_List_Ptr selector = Cast(node); - std::cerr << ind << "Selector_List " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); - std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->schema(), ind + "#{} "); - - for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - -// } else if (Cast(node)) { -// Expression_Ptr expression = Cast(node); -// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; - - } else if (Cast(node)) { - Parent_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Parent_Selector " << selector; -// if (selector->not_selector()) cerr << " [in_declaration]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; -// debug_ast(selector->selector(), ind + "->", env); - - } else if (Cast(node)) { - Complex_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Complex_Selector " << selector - << " (" << pstate_source_position(node) << ")" - << " <" << selector->hash() << ">" - << " [length:" << longToHex(selector->length()) << "]" - << " [weight:" << longToHex(selector->specificity()) << "]" - << " [@media:" << selector->media_block() << "]" - << (selector->is_invisible() ? " [INVISIBLE]": " -") - << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_parent_ref() ? " [has parent]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << (selector->has_line_break() ? " [line-break]": " -") - << " -- "; - std::string del; - switch (selector->combinator()) { - case Complex_Selector::PARENT_OF: del = ">"; break; - case Complex_Selector::PRECEDES: del = "~"; break; - case Complex_Selector::ADJACENT_TO: del = "+"; break; - case Complex_Selector::ANCESTOR_OF: del = " "; break; - case Complex_Selector::REFERENCE: del = "//"; break; - } - // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); - if (selector->tail()) { - debug_ast(selector->tail(), ind + "{" + del + "}", env); - } else if(del != " ") { - std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; - } - ComplexSelectorSet set = selector->sources(); - // debug_sources_set(set, ind + " @--> "); - } else if (Cast(node)) { - Compound_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Compound_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->extended() ? " [extended]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Wrapped_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Wrapped_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->selector(), ind + " () ", env); - } else if (Cast(node)) { - Pseudo_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Pseudo_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->expression(), ind + " <= ", env); - } else if (Cast(node)) { - Attribute_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Attribute_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); - } else if (Cast(node)) { - Class_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Class_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - } else if (Cast(node)) { - Id_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Id_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - } else if (Cast(node)) { - Element_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Element_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; - std::cerr << std::endl; - } else if (Cast(node)) { - - Placeholder_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; - std::cerr << " (" << pstate_source_position(selector) << ")" - << " <" << selector->hash() << ">" - << " [@media:" << selector->media_block() << "]" - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << std::endl; - - } else if (Cast(node)) { - Simple_Selector* selector = Cast(node); - std::cerr << ind << "Simple_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; - - } else if (Cast(node)) { - Selector_Schema_Ptr selector = Cast(node); - std::cerr << ind << "Selector_Schema " << selector; - std::cerr << " (" << pstate_source_position(node) << ")" - << " [@media:" << selector->media_block() << "]" - << (selector->connect_parent() ? " [connect-parent]": " -") - << std::endl; - - debug_ast(selector->contents(), ind + " "); - // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { - Selector_Ptr selector = Cast(node); - std::cerr << ind << "Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << std::endl; - - } else if (Cast(node)) { - Media_Query_Expression_Ptr block = Cast(node); - std::cerr << ind << "Media_Query_Expression " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - - } else if (Cast(node)) { - Media_Query_Ptr block = Cast(node); - std::cerr << ind << "Media_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_negated() ? " [is_negated]": " -") - << (block->is_restricted() ? " [is_restricted]": " -") - << std::endl; - debug_ast(block->media_type(), ind + " "); - for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { - Media_Block_Ptr block = Cast(node); - std::cerr << ind << "Media_Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->media_queries(), ind + " =@ "); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Supports_Block_Ptr block = Cast(node); - std::cerr << ind << "Supports_Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->condition(), ind + " =@ "); - debug_ast(block->block(), ind + " <>"); - } else if (Cast(node)) { - Supports_Operator_Ptr block = Cast(node); - std::cerr << ind << "Supports_Operator " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->left(), ind + " left) "); - debug_ast(block->right(), ind + " right) "); - } else if (Cast(node)) { - Supports_Negation_Ptr block = Cast(node); - std::cerr << ind << "Supports_Negation " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->condition(), ind + " condition) "); - } else if (Cast(node)) { - At_Root_Query_Ptr block = Cast(node); - std::cerr << ind << "At_Root_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { - Supports_Declaration_Ptr block = Cast(node); - std::cerr << ind << "Supports_Declaration " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { - Block_Ptr root_block = Cast(node); - std::cerr << ind << "Block " << root_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (root_block->is_root()) std::cerr << " [root]"; - std::cerr << " " << root_block->tabs() << std::endl; - for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Warning_Ptr block = Cast(node); - std::cerr << ind << "Warning " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->message(), ind + " : "); - } else if (Cast(node)) { - Error_Ptr block = Cast(node); - std::cerr << ind << "Error " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Debug_Ptr block = Cast(node); - std::cerr << ind << "Debug " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->value(), ind + " "); - } else if (Cast(node)) { - Comment_Ptr block = Cast(node); - std::cerr << ind << "Comment " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << - " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(block->text(), ind + "// ", env); - } else if (Cast(node)) { - If_Ptr block = Cast(node); - std::cerr << ind << "If " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->predicate(), ind + " = "); - debug_ast(block->block(), ind + " <>"); - debug_ast(block->alternative(), ind + " ><"); - } else if (Cast(node)) { - Return_Ptr block = Cast(node); - std::cerr << ind << "Return " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Extension_Ptr block = Cast(node); - std::cerr << ind << "Extension " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "-> ", env); - } else if (Cast(node)) { - Content_Ptr block = Cast(node); - std::cerr << ind << "Content " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [@media:" << block->media_block() << "]"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Import_Stub_Ptr block = Cast(node); - std::cerr << ind << "Import_Stub " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->imp_path() << "] "; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Import_Ptr block = Cast(node); - std::cerr << ind << "Import " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - // std::vector files_; - for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); - debug_ast(block->import_queries(), ind + "@@ "); - } else if (Cast(node)) { - Assignment_Ptr block = Cast(node); - std::cerr << ind << "Assignment " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; - debug_ast(block->value(), ind + "=", env); - } else if (Cast(node)) { - Declaration_Ptr block = Cast(node); - std::cerr << ind << "Declaration " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->property(), ind + " prop: ", env); - debug_ast(block->value(), ind + " value: ", env); - debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Keyframe_Rule_Ptr has_block = Cast(node); - std::cerr << ind << "Keyframe_Rule " << has_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << has_block->tabs() << std::endl; - if (has_block->name()) debug_ast(has_block->name(), ind + "@"); - if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Directive_Ptr block = Cast(node); - std::cerr << ind << "Directive " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "~", env); - debug_ast(block->value(), ind + "+", env); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Each_Ptr block = Cast(node); - std::cerr << ind << "Each " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - For_Ptr block = Cast(node); - std::cerr << ind << "For " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - While_Ptr block = Cast(node); - std::cerr << ind << "While " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Definition_Ptr block = Cast(node); - std::cerr << ind << "Definition " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << block->name() << "] "; - std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; - // this seems to lead to segfaults some times? - // std::cerr << " [signature: " << block->signature() << "] "; - std::cerr << " [native: " << block->native_function() << "] "; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->parameters(), ind + " params: ", env); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Mixin_Call_Ptr block = Cast(node); - std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); - std::cerr << " (" << pstate_source_position(block) << ")"; - std::cerr << " [" << block->name() << "]"; - std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; - debug_ast(block->arguments(), ind + " args: "); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (Ruleset_Ptr ruleset = Cast(node)) { - std::cerr << ind << "Ruleset " << ruleset; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [indent: " << ruleset->tabs() << "]"; - std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << (ruleset->is_root() ? " [root]" : ""); - std::cerr << std::endl; - debug_ast(ruleset->selector(), ind + ">"); - debug_ast(ruleset->block(), ind + " "); - } else if (Cast(node)) { - Block_Ptr block = Cast(node); - std::cerr << ind << "Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << " [indent: " << block->tabs() << "]" << std::endl; - for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Variable_Ptr expression = Cast(node); - std::cerr << ind << "Variable " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]" << std::endl; - std::string name(expression->name()); - if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); - } else if (Cast(node)) { - Function_Call_Schema_Ptr expression = Cast(node); - std::cerr << ind << "Function_Call_Schema " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << "" << std::endl; - debug_ast(expression->name(), ind + "name: ", env); - debug_ast(expression->arguments(), ind + " args: ", env); - } else if (Cast(node)) { - Function_Call_Ptr expression = Cast(node); - std::cerr << ind << "Function_Call " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->arguments(), ind + " args: ", env); - debug_ast(expression->func(), ind + " func: ", env); - } else if (Cast(node)) { - Function_Ptr expression = Cast(node); - std::cerr << ind << "Function " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->definition(), ind + " definition: ", env); - } else if (Cast(node)) { - Arguments_Ptr expression = Cast(node); - std::cerr << ind << "Arguments " << expression; - if (expression->is_delayed()) std::cerr << " [delayed]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; - if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; - if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; - std::cerr << std::endl; - for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Argument_Ptr expression = Cast(node); - std::cerr << ind << "Argument " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->value().ptr() << "]"; - std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [rest: " << expression->is_rest_argument() << "] "; - std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; - debug_ast(expression->value(), ind + " value: ", env); - } else if (Cast(node)) { - Parameters_Ptr expression = Cast(node); - std::cerr << ind << "Parameters " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; - std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; - std::cerr << std::endl; - for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Parameter_Ptr expression = Cast(node); - std::cerr << ind << "Parameter " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [default: " << expression->default_value().ptr() << "] "; - std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; - } else if (Cast(node)) { - Unary_Expression_Ptr expression = Cast(node); - std::cerr << ind << "Unary_Expression " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type() << "]" << std::endl; - debug_ast(expression->operand(), ind + " operand: ", env); - } else if (Cast(node)) { - Binary_Expression_Ptr expression = Cast(node); - std::cerr << ind << "Binary_Expression " << expression; - if (expression->is_interpolant()) std::cerr << " [is interpolant] "; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [ws_before: " << expression->op().ws_before << "] "; - std::cerr << " [ws_after: " << expression->op().ws_after << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type_name() << "]" << std::endl; - debug_ast(expression->left(), ind + " left: ", env); - debug_ast(expression->right(), ind + " right: ", env); - } else if (Cast(node)) { - Map_Ptr expression = Cast(node); - std::cerr << ind << "Map " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [Hashed]" << std::endl; - for (const auto& i : expression->elements()) { - debug_ast(i.first, ind + " key: "); - debug_ast(i.second, ind + " val: "); - } - } else if (Cast(node)) { - List_Ptr expression = Cast(node); - std::cerr << ind << "List " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " (" << expression->length() << ") " << - (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << - " [delayed: " << expression->is_delayed() << "] " << - " [interpolant: " << expression->is_interpolant() << "] " << - " [listized: " << expression->from_selector() << "] " << - " [arglist: " << expression->is_arglist() << "] " << - " [bracketed: " << expression->is_bracketed() << "] " << - " [expanded: " << expression->is_expanded() << "] " << - " [hash: " << expression->hash() << "] " << - std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Content_Ptr expression = Cast(node); - std::cerr << ind << "Content " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [@media:" << expression->media_block() << "]"; - std::cerr << " [Statement]" << std::endl; - } else if (Cast(node)) { - Boolean_Ptr expression = Cast(node); - std::cerr << ind << "Boolean " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [" << expression->value() << "]" << std::endl; - } else if (Cast(node)) { - Color_Ptr expression = Cast(node); - std::cerr << ind << "Color " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; - } else if (Cast(node)) { - Number_Ptr expression = Cast(node); - std::cerr << ind << "Number " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [" << expression->value() << expression->unit() << "]" << - " [hash: " << expression->hash() << "] " << - std::endl; - } else if (Cast(node)) { - Null_Ptr expression = Cast(node); - std::cerr << ind << "Null " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] " - // " [hash: " << expression->hash() << "] " - << std::endl; - } else if (Cast(node)) { - String_Quoted_Ptr expression = Cast(node); - std::cerr << ind << "String_Quoted " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (Cast(node)) { - String_Constant_Ptr expression = Cast(node); - std::cerr << ind << "String_Constant " << expression; - if (expression->concrete_type()) { - std::cerr << " " << expression->concrete_type(); - } - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (Cast(node)) { - String_Schema_Ptr expression = Cast(node); - std::cerr << ind << "String_Schema " << expression; - std::cerr << " (" << pstate_source_position(expression) << ")"; - std::cerr << " " << expression->concrete_type(); - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->css()) std::cerr << " [css]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [is interpolant]"; - if (expression->has_interpolant()) std::cerr << " [has interpolant]"; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - String_Ptr expression = Cast(node); - std::cerr << ind << "String " << expression; - std::cerr << " " << expression->concrete_type(); - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (Cast(node)) { - Expression_Ptr expression = Cast(node); - std::cerr << ind << "Expression " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - switch (expression->concrete_type()) { - case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; - case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; - case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; - case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; - case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; - case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; - case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; - case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; - case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; - case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; - case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; - case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; - case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; - case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; - case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; - } - std::cerr << std::endl; - } else if (Cast(node)) { - Has_Block_Ptr has_block = Cast(node); - std::cerr << ind << "Has_Block " << has_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << has_block->tabs() << std::endl; - if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Statement_Ptr statement = Cast(node); - std::cerr << ind << "Statement " << statement; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << statement->tabs() << std::endl; - } - - if (ind == "") std::cerr << "####################################################################\n"; -} - -inline void debug_node(Node* node, std::string ind = "") -{ - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; - if (node->isCombinator()) { - std::cerr << ind; - std::cerr << "Combinator "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - switch (node->combinator()) { - case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; - case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; - case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; - case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; - case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; - } - std::cerr << std::endl; - // debug_ast(node->combinator(), ind + " "); - } else if (node->isSelector()) { - std::cerr << ind; - std::cerr << "Selector "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - debug_ast(node->selector(), ind + " "); - } else if (node->isCollection()) { - std::cerr << ind; - std::cerr << "Collection "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - for(auto n : (*node->collection())) { - debug_node(&n, ind + " "); - } - } else if (node->isNil()) { - std::cerr << ind; - std::cerr << "Nil "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } else { - std::cerr << ind; - std::cerr << "OTHER "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; -} - -/* -inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) -{ - debug_ast(const_cast(node), ind, env); -} -*/ -inline void debug_node(const Node* node, std::string ind = "") -{ - debug_node(const_cast(node), ind); -} - -inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &it : map.values()) { - debug_ast(it.first, ind + "first: "); - debug_ast(it.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : *entries) { - debug_ast(pair.first, ind + "first: "); - debug_ast(pair.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -#endif // SASS_DEBUGGER diff --git a/src/libsass/dev-ast-memory.md b/src/libsass/dev-ast-memory.md deleted file mode 100644 index 31004bcf2..000000000 --- a/src/libsass/dev-ast-memory.md +++ /dev/null @@ -1,223 +0,0 @@ -# LibSass smart pointer implementation - -LibSass uses smart pointers very similar to `shared_ptr` known -by Boost or C++11. Implementation is a bit less modular since -it was not needed. Various compile time debug options are -available if you need to debug memory life-cycles. - - -## Memory Classes - -### SharedObj - -Base class for the actual node implementations. This ensures -that every object has a reference counter and other values. - -```c++ -class AST_Node : public SharedObj { ... }; -``` - -### SharedPtr (base class for SharedImpl) - -Base class that holds on to the pointer. The reference counter -is stored inside the pointer object directly (`SharedObj`). - -### SharedImpl (inherits from SharedPtr) - -This is the main base class for objects you use in your code. It -will make sure that the memory it points at will be deleted once -all copies to the same object/memory go out of scope. - -```c++ -Class* pointer = new Class(...); -SharedImpl obj(pointer); -``` - -To spare the developer of typing the templated class every time, -we created typedefs for each available AST Node specialization. - -```c++ -typedef SharedImpl Number_Obj; -Number_Obj number = SASS_MEMORY_NEW(...); -``` - - -## Memory life-cycles - -### Pointer pickups - -I often use the terminology of "pickup". This means the moment when -a raw pointer not under any control is assigned to a reference counted -object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be -automatically released once the object goes out of scope (but only -if the reference counter reaches zero). Main point beeing, you don't -have to worry about memory management yourself. - -### Object detach - -Sometimes we can't return reference counted objects directly (see -invalid covariant return types problems below). But we often still -need to use reference objects inside a function to avoid leaks when -something throws. For this you can use `detach`, which basically -detaches the pointer memory from the reference counted object. So -when the reference counted object goes out of scope, it will not -free the attached memory. You are now again in charge of freeing -the memory (just assign it to a reference counted object again). - - -## Circular references - -Reference counted memory implementations are prone to circular references. -This can be addressed by using a multi generation garbage collector. But -for our use-case that seems overkill. There is no way so far for users -(sass code) to create circular references. Therefore we can code around -this possible issue. But developers should be aware of this limitation. - -There are AFAIR two places where circular references could happen. One is -the `sources` member on every `Selector`. The other one can happen in the -extend code (Node handling). The easy way to avoid this is to only assign -complete object clones to these members. If you know the objects lifetime -is longer than the reference you create, you can also just store the raw -pointer. Once needed this could be solved with weak pointers. - - -## Addressing the invalid covariant return types problems - -If you are not familiar with the mentioned problem, you may want -to read up on covariant return types and virtual functions, i.e. - -- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers -- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers -- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr - -We hit this issue at least with the CRTP visitor pattern (eval, expand, -listize and so forth). This means we cannot return reference counted -objects directly. We are forced to return raw pointers or we would need -to have a lot of explicit and expensive upcasts by callers/consumers. - -### Simple functions that allocate new AST Nodes - -In the parser step we often create new objects and can just return a -unique pointer (meaning ownership clearly shifts back to the caller). -The caller/consumer is responsible that the memory is freed. - -```c++ -typedef Number* Number_Ptr; -int parse_integer() { - ... // do the parsing - return 42; -} -Number_Ptr parse_number() { - Number_Ptr p_nr = SASS_MEMORY_NEW(...); - p_nr->value(parse_integer()); - return p_nr; -} -Number_Obj nr = parse_number(); -``` - -The above would be the encouraged pattern for such simple cases. - -### Allocate new AST Nodes in functions that can throw - -There is a major caveat with the previous example, considering this -more real-life implementation that throws an error. The throw may -happen deep down in another function. Holding raw pointers that -we need to free would leak in this case. - -```c++ -int parse_integer() { - ... // do the parsing - if (error) throw(error); - return 42; -} -``` - -With this `parse_integer` function the previous example would leak memory. -I guess it is pretty obvious, as the allocated memory will not be freed, -as it was never assigned to a SharedObj value. Therefore the above code -would better be written as: - -```c++ -typedef Number* Number_Ptr; -int parse_integer() { - ... // do the parsing - if (error) throw(error); - return 42; -} -// this leaks due to pointer return -// should return Number_Obj instead -// though not possible for virtuals! -Number_Ptr parse_number() { - Number_Obj nr = SASS_MEMORY_NEW(...); - nr->value(parse_integer()); // throws - return &nr; // Ptr from Obj -} -Number_Obj nr = parse_number(); -// will now be freed automatically -``` - -The example above unfortunately will not work as is, since we return a -`Number_Ptr` from that function. Therefore the object allocated inside -the function is already gone when it is picked up again by the caller. -The easy fix for the given simplified use case would be to change the -return type of `parse_number` to `Number_Obj`. Indeed we do it exactly -this way in the parser. But as stated above, this will not work for -virtual functions due to invalid covariant return types! - -### Return managed objects from virtual functions - -The easy fix would be to just create a new copy on the heap and return -that. But this seems like a very inelegant solution to this problem. I -mean why can't we just tell the object to treat it like a newly allocated -object? And indeed we can. I've added a `detach` method that will tell -the object to survive deallocation until the next pickup. This means -that it will leak if it is not picked up by consumer. - -```c++ -typedef Number* Number_Ptr; -int parse_integer() { - ... // do the parsing - if (error) throw(error); - return 42; -} -Number_Ptr parse_number() { - Number_Obj nr = SASS_MEMORY_NEW(...); - nr->value(parse_integer()); // throws - return nr.detach(); -} -Number_Obj nr = parse_number(); -// will now be freed automatically -``` - - -## Compile time debug options - -To enable memory debugging you need to define `DEBUG_SHARED_PTR`. -This can i.e. be done in `include/sass/base.h` - -```c++ -define DEBUG_SHARED_PTR -``` - -This will print lost memory on exit to stderr. You can also use -`setDbg(true)` on sepecific variables to emit reference counter -increase, decrease and other events. - - -## Why reinvent the wheel when there is `shared_ptr` from C++11 - -First, implementing a smart pointer class is not really that hard. It -was indeed also a learning experience for myself. But there are more -profound advantages: - -- Better GCC 4.4 compatibility (which most code still has OOTB) -- Not thread safe (give us some free performance on some compiler) -- Beeing able to track memory allocations for debugging purposes -- Adding additional features if needed (as seen in `detach`) -- Optional: optimized weak pointer implementation possible - -### Thread Safety - -As said above, this is not thread safe currently. But we don't need -this ATM anyway. And I guess we probably never will share AST Nodes -across different threads. \ No newline at end of file diff --git a/src/libsass/eval.cpp b/src/libsass/eval.cpp deleted file mode 100644 index 841f7277b..000000000 --- a/src/libsass/eval.cpp +++ /dev/null @@ -1,1663 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include -#include - -#include "file.hpp" -#include "eval.hpp" -#include "ast.hpp" -#include "bind.hpp" -#include "util.hpp" -#include "inspect.hpp" -#include "operators.hpp" -#include "environment.hpp" -#include "position.hpp" -#include "sass/values.h" -#include "to_value.hpp" -#include "to_c.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "color_maps.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - Eval::Eval(Expand& exp) - : exp(exp), - ctx(exp.ctx), - traces(exp.traces), - force(false), - is_in_comment(false), - is_in_selector_schema(false) - { - bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); - bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); - } - Eval::~Eval() { } - - Env* Eval::environment() - { - return exp.environment(); - } - - Selector_List_Obj Eval::selector() - { - return exp.selector(); - } - - Expression_Ptr Eval::operator()(Block_Ptr b) - { - Expression_Ptr val = 0; - for (size_t i = 0, L = b->length(); i < L; ++i) { - val = b->at(i)->perform(this); - if (val) return val; - } - return val; - } - - Expression_Ptr Eval::operator()(Assignment_Ptr a) - { - Env* env = exp.environment(); - std::string var(a->variable()); - if (a->is_global()) { - if (a->is_default()) { - if (env->has_global(var)) { - Expression_Ptr e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } - else { - env->set_global(var, a->value()->perform(this)); - } - } - else { - env->set_global(var, a->value()->perform(this)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Ptr e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(this)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); - } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - Expression_Ptr e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } - } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(this)); - } - else { - env->set_local(var, a->value()->perform(this)); - } - } - else { - env->set_lexical(var, a->value()->perform(this)); - } - return 0; - } - - Expression_Ptr Eval::operator()(If_Ptr i) - { - Expression_Obj rv = 0; - Env env(exp.environment()); - exp.env_stack.push_back(&env); - Expression_Obj cond = i->predicate()->perform(this); - if (!cond->is_false()) { - rv = i->block()->perform(this); - } - else { - Block_Obj alt = i->alternative(); - if (alt) rv = alt->perform(this); - } - exp.env_stack.pop_back(); - return rv.detach(); - } - - // For does not create a new env scope - // But iteration vars are reset afterwards - Expression_Ptr Eval::operator()(For_Ptr f) - { - std::string variable(f->variable()); - Expression_Obj low = f->lower_bound()->perform(this); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); - } - Expression_Obj high = f->upper_bound()->perform(this); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); - } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - std::stringstream msg; msg << "Incompatible units: '" - << sass_end->unit() << "' and '" - << sass_start->unit() << "'."; - error(msg.str(), low->pstate(), traces); - } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - exp.env_stack.push_back(&env); - Block_Obj body = f->block(); - Expression_Ptr val = 0; - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; - } - } - exp.env_stack.pop_back(); - return val; - } - - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Expression_Ptr Eval::operator()(Each_Ptr e) - { - std::vector variables(e->variables()); - Expression_Obj expr = e->list()->perform(this); - Env env(environment(), true); - exp.env_stack.push_back(&env); - List_Obj list = 0; - Map_Ptr map = 0; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); - } - else if (Selector_List_Ptr ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); - list = Cast(rv); - } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); - } - else { - list = Cast(expr); - } - - Block_Obj body = e->block(); - Expression_Obj val = 0; - - if (map) { - for (Expression_Obj key : map->keys()) { - Expression_Obj value = map->at(key); - - if (variables.size() == 1) { - List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(key); - variable->append(value); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], key); - env.set_local(variables[1], value); - } - - val = body->perform(this); - if (val) break; - } - } - else { - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Ptr item = list->at(i); - // unwrap value if the expression is an argument - if (Argument_Ptr arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List_Ptr scalars = Cast(item)) { - if (variables.size() == 1) { - Expression_Ptr var = scalars; - env.set_local(variables[0], var); - } else { - // XXX: this is never hit via spec tests - for (size_t j = 0, K = variables.size(); j < K; ++j) { - Expression_Ptr res = j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) - : scalars->at(j); - env.set_local(variables[j], res); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - // XXX: this is never hit via spec tests - Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } - } - val = body->perform(this); - if (val) break; - } - } - exp.env_stack.pop_back(); - return val.detach(); - } - - Expression_Ptr Eval::operator()(While_Ptr w) - { - Expression_Obj pred = w->predicate(); - Block_Obj body = w->block(); - Env env(environment(), true); - exp.env_stack.push_back(&env); - Expression_Obj cond = pred->perform(this); - while (!cond->is_false()) { - Expression_Obj val = body->perform(this); - if (val) { - exp.env_stack.pop_back(); - return val.detach(); - } - cond = pred->perform(this); - } - exp.env_stack.pop_back(); - return 0; - } - - Expression_Ptr Eval::operator()(Return_Ptr r) - { - return r->value()->perform(this); - } - - Expression_Ptr Eval::operator()(Warning_Ptr w) - { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; - Expression_Obj message = w->message()->perform(this); - Env* env = exp.environment(); - - // try to use generic function - if (env->has("@warn[f]")) { - - // add call stack entry - ctx.callee_stack.push_back({ - "@warn", - w->pstate().path, - w->pstate().line + 1, - w->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition_Ptr def = Cast((*env)["@warn[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - std::string result(unquote(message->to_sass())); - std::cerr << "WARNING: " << result << std::endl; - traces.push_back(Backtrace(w->pstate())); - std::cerr << traces_to_string(traces, " "); - std::cerr << std::endl; - ctx.c_options.output_style = outstyle; - traces.pop_back(); - return 0; - } - - Expression_Ptr Eval::operator()(Error_Ptr e) - { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; - Expression_Obj message = e->message()->perform(this); - Env* env = exp.environment(); - - // try to use generic function - if (env->has("@error[f]")) { - - // add call stack entry - ctx.callee_stack.push_back({ - "@error", - e->pstate().path, - e->pstate().line + 1, - e->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition_Ptr def = Cast((*env)["@error[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - std::string result(unquote(message->to_sass())); - ctx.c_options.output_style = outstyle; - error(result, e->pstate(), traces); - return 0; - } - - Expression_Ptr Eval::operator()(Debug_Ptr d) - { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; - Expression_Obj message = d->value()->perform(this); - Env* env = exp.environment(); - - // try to use generic function - if (env->has("@debug[f]")) { - - // add call stack entry - ctx.callee_stack.push_back({ - "@debug", - d->pstate().path, - d->pstate().line + 1, - d->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition_Ptr def = Cast((*env)["@debug[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - std::string cwd(ctx.cwd()); - std::string result(unquote(message->to_sass())); - std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); - std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); - ctx.c_options.output_style = outstyle; - - std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; - std::cerr << std::endl; - return 0; - } - - Expression_Ptr Eval::operator()(List_Ptr l) - { - // special case for unevaluated map - if (l->separator() == SASS_HASH) { - Map_Obj lm = SASS_MEMORY_NEW(Map, - l->pstate(), - l->length() / 2); - for (size_t i = 0, L = l->length(); i < L; i += 2) - { - Expression_Obj key = (*l)[i+0]->perform(this); - Expression_Obj val = (*l)[i+1]->perform(this); - // make sure the color key never displays its real name - key->is_delayed(true); // verified - *lm << std::make_pair(key, val); - } - if (lm->has_duplicate_key()) { - traces.push_back(Backtrace(l->pstate())); - throw Exception::DuplicateKeyError(traces, *lm, *l); - } - - lm->is_interpolant(l->is_interpolant()); - return lm->perform(this); - } - // check if we should expand it - if (l->is_expanded()) return l; - // regular case for unevaluated lists - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - ll->is_interpolant(l->is_interpolant()); - ll->from_selector(l->from_selector()); - ll->is_expanded(true); - return ll.detach(); - } - - Expression_Ptr Eval::operator()(Map_Ptr m) - { - if (m->is_expanded()) return m; - - // make sure we're not starting with duplicate keys. - // the duplicate key state will have been set in the parser phase. - if (m->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *m, *m); - } - - Map_Obj mm = SASS_MEMORY_NEW(Map, - m->pstate(), - m->length()); - for (auto key : m->keys()) { - Expression_Ptr ex_key = key->perform(this); - Expression_Ptr ex_val = m->at(key); - if (ex_val == NULL) continue; - ex_val = ex_val->perform(this); - *mm << std::make_pair(ex_key, ex_val); - } - - // check the evaluated keys aren't duplicates. - if (mm->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *mm, *m); - } - - mm->is_expanded(true); - return mm.detach(); - } - - Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) - { - - Expression_Obj lhs = b_in->left(); - Expression_Obj rhs = b_in->right(); - enum Sass_OP op_type = b_in->optype(); - - if (op_type == Sass_OP::AND) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (!*lhs) return lhs.detach(); - return rhs->perform(this); - } - else if (op_type == Sass_OP::OR) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (*lhs) return lhs.detach(); - return rhs->perform(this); - } - - // Evaluate variables as early o - while (Variable_Ptr l_v = Cast(lhs)) { - lhs = operator()(l_v); - } - while (Variable_Ptr r_v = Cast(rhs)) { - rhs = operator()(r_v); - } - - Binary_Expression_Obj b = b_in; - - // Evaluate sub-expressions early on - while (Binary_Expression_Ptr l_b = Cast(lhs)) { - if (!force && l_b->is_delayed()) break; - lhs = operator()(l_b); - } - while (Binary_Expression_Ptr r_b = Cast(rhs)) { - if (!force && r_b->is_delayed()) break; - rhs = operator()(r_b); - } - - // don't eval delayed expressions (the '/' when used as a separator) - if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { - b->right(b->right()->perform(this)); - b->left(b->left()->perform(this)); - return b.detach(); - } - - // specific types we know are final - // handle them early to avoid overhead - if (Number_Ptr l_n = Cast(lhs)) { - // lhs is number and rhs is number - if (Number_Ptr r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; - case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; - case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - // lhs is number and rhs is color - else if (Color_Ptr r_c = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - } - else if (Color_Ptr l_c = Cast(lhs)) { - // lhs is color and rhs is color - if (Color_Ptr r_c = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; - case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; - case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - // lhs is color and rhs is number - else if (Number_Ptr r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - } - - String_Schema_Obj ret_schema; - - // only the last item will be used to eval the binary expression - if (String_Schema_Ptr s_l = Cast(b->left())) { - if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), s_l->last(), b->right()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified - for (size_t i = 0; i < s_l->length() - 1; ++i) { - ret_schema->append(s_l->at(i)->perform(this)); - } - ret_schema->append(bin_ex->perform(this)); - return ret_schema->perform(this); - } - } - if (String_Schema_Ptr s_r = Cast(b->right())) { - - if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), b->left(), s_r->first()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified - ret_schema->append(bin_ex->perform(this)); - for (size_t i = 1; i < s_r->length(); ++i) { - ret_schema->append(s_r->at(i)->perform(this)); - } - return ret_schema->perform(this); - } - } - - // fully evaluate their values - if (op_type == Sass_OP::EQ || - op_type == Sass_OP::NEQ || - op_type == Sass_OP::GT || - op_type == Sass_OP::GTE || - op_type == Sass_OP::LT || - op_type == Sass_OP::LTE) - { - LOCAL_FLAG(force, true); - lhs->is_expanded(false); - lhs->set_delayed(false); - lhs = lhs->perform(this); - rhs->is_expanded(false); - rhs->set_delayed(false); - rhs = rhs->perform(this); - } - else { - lhs = lhs->perform(this); - } - - // not a logical connective, so go ahead and eval the rhs - rhs = rhs->perform(this); - AST_Node_Obj lu = lhs; - AST_Node_Obj ru = rhs; - - Expression::Concrete_Type l_type; - Expression::Concrete_Type r_type; - - // Is one of the operands an interpolant? - String_Schema_Obj s1 = Cast(b->left()); - String_Schema_Obj s2 = Cast(b->right()); - Binary_Expression_Obj b1 = Cast(b->left()); - Binary_Expression_Obj b2 = Cast(b->right()); - - bool schema_op = false; - - bool force_delay = (s2 && s2->is_left_interpolant()) || - (s1 && s1->is_right_interpolant()) || - (b1 && b1->is_right_interpolant()) || - (b2 && b2->is_left_interpolant()); - - if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) - { - if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || - op_type == Sass_OP::EQ) { - // If possible upgrade LHS to a number (for number to string compare) - if (String_Constant_Ptr str = Cast(lhs)) { - std::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { - lhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - // If possible upgrade RHS to a number (for string to number compare) - if (String_Constant_Ptr str = Cast(rhs)) { - std::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { - rhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - } - - To_Value to_value(ctx); - Value_Obj v_l = Cast(lhs->perform(&to_value)); - Value_Obj v_r = Cast(rhs->perform(&to_value)); - - if (force_delay) { - std::string str(""); - str += v_l->to_string(ctx.c_options); - if (b->op().ws_before) str += " "; - str += b->separator(); - if (b->op().ws_after) str += " "; - str += v_r->to_string(ctx.c_options); - String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); - val->is_interpolant(b->left()->has_interpolant()); - return val; - } - } - - // see if it's a relational expression - try { - switch(op_type) { - case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); - case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); - case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); - default: break; - } - } - catch (Exception::OperationError& err) - { - // throw Exception::Base(b->pstate(), err.what()); - traces.push_back(Backtrace(b->pstate())); - throw Exception::SassValueError(traces, b->pstate(), err); - } - - l_type = lhs->concrete_type(); - r_type = rhs->concrete_type(); - - // ToDo: throw error in op functions - // ToDo: then catch and re-throw them - Expression_Obj rv; - try { - ParserState pstate(b->pstate()); - if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { - Number_Ptr l_n = Cast(lhs); - Number_Ptr r_n = Cast(rhs); - l_n->reduce(); r_n->reduce(); - rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); - } - else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { - Number_Ptr l_n = Cast(lhs); - Color_Ptr r_c = Cast(rhs); - rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { - Color_Ptr l_c = Cast(lhs); - Number_Ptr r_n = Cast(rhs); - rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { - Color_Ptr l_c = Cast(lhs); - Color_Ptr r_c = Cast(rhs); - rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); - } - else { - To_Value to_value(ctx); - // this will leak if perform does not return a value! - Value_Obj v_l = Cast(lhs->perform(&to_value)); - Value_Obj v_r = Cast(rhs->perform(&to_value)); - bool interpolant = b->is_right_interpolant() || - b->is_left_interpolant() || - b->is_interpolant(); - if (op_type == Sass_OP::SUB) interpolant = false; - // if (op_type == Sass_OP::DIV) interpolant = true; - // check for type violations - if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_l->pstate())); - throw Exception::InvalidValue(traces, *v_l); - } - if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_r->pstate())); - throw Exception::InvalidValue(traces, *v_r); - } - Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress - if (String_Constant_Ptr str = Cast(ex)) - { - if (str->concrete_type() == Expression::STRING) - { - String_Constant_Ptr lstr = Cast(lhs); - String_Constant_Ptr rstr = Cast(rhs); - if (op_type != Sass_OP::SUB) { - if (String_Constant_Ptr org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } - } - } - } - ex->is_interpolant(b->is_interpolant()); - rv = ex; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b->pstate())); - // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(traces, b->pstate(), err); - } - - if (rv) { - if (schema_op) { - // XXX: this is never hit via spec tests - (*s2)[0] = rv; - rv = s2->perform(this); - } - } - - return rv.detach(); - - } - - Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) - { - Expression_Obj operand = u->operand()->perform(this); - if (u->optype() == Unary_Expression::NOT) { - Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); - result->value(!result->value()); - return result; - } - else if (Number_Obj nr = Cast(operand)) { - // negate value for minus unary expression - if (u->optype() == Unary_Expression::MINUS) { - Number_Obj cpy = SASS_MEMORY_COPY(nr); - cpy->value( - cpy->value() ); // negate value - return cpy.detach(); // return the copy - } - else if (u->optype() == Unary_Expression::SLASH) { - std::string str = '/' + nr->to_string(ctx.c_options); - return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); - } - // nothing for positive - return nr.detach(); - } - else { - // Special cases: +/- variables which evaluate to null ouput just +/-, - // but +/- null itself outputs the string - if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { - u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); - } - // Never apply unary opertions on colors @see #2140 - else if (Color_Ptr color = Cast(operand)) { - // Use the color name if this was eval with one - if (color->disp().length() > 0) { - operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); - u->operand(operand); - } - } - else { - u->operand(operand); - } - - return SASS_MEMORY_NEW(String_Quoted, - u->pstate(), - u->inspect()); - } - // unreachable - return u; - } - - Expression_Ptr Eval::operator()(Function_Call_Ptr c) - { - if (traces.size() > Constants::MaxCallStack) { - // XXX: this is never hit via spec tests - std::ostringstream stm; - stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str(), c->pstate(), traces); - } - std::string name(Util::normalize_underscores(c->name())); - std::string full_name(name + "[f]"); - // we make a clone here, need to implement that further - Arguments_Obj args = c->arguments(); - - Env* env = environment(); - if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { - if (!env->has("*[f]")) { - for (Argument_Obj arg : args->elements()) { - if (List_Obj ls = Cast(arg->value())) { - if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); - } - } - args = Cast(args->perform(this)); - Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, - c->pstate(), - c->name(), - args); - if (args->has_named_arguments()) { - error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); - } - String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, - c->pstate(), - lit->to_string(ctx.c_options)); - str->is_interpolant(c->is_interpolant()); - return str; - } else { - // call generic function - full_name = "*[f]"; - } - } - - // further delay for calls - if (full_name != "call[f]") { - args->set_delayed(false); // verified - } - if (full_name != "if[f]") { - args = Cast(args->perform(this)); - } - Definition_Ptr def = Cast((*env)[full_name]); - - if (c->func()) def = c->func()->definition(); - - if (def->is_overload_stub()) { - std::stringstream ss; - size_t L = args->length(); - // account for rest arguments - if (args->has_rest_argument() && args->length() > 0) { - // get the rest arguments list - List_Ptr rest = Cast(args->last()->value()); - // arguments before rest argument plus rest - if (rest) L += rest->length() - 1; - } - ss << full_name << L; - full_name = ss.str(); - std::string resolved_name(full_name); - if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); - def = Cast((*env)[resolved_name]); - } - - Expression_Obj result = c; - Block_Obj body = def->block(); - Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - - if (c->is_css()) return result.detach(); - - Parameters_Obj params = def->parameters(); - Env fn_env(def->environment()); - exp.env_stack.push_back(&fn_env); - - if (func || body) { - bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - std::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().path, - c->pstate().line + 1, - c->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - // eval the body if user-defined or special, invoke underlying CPP function if native - if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { - result = body->perform(this); - } - else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); - } - if (!result) { - error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); - } - ctx.callee_stack.pop_back(); - traces.pop_back(); - } - - // else if it's a user-defined c function - // convert call into C-API compatible form - else if (c_function) { - Sass_Function_Fn c_func = sass_function_get_function(c_function); - if (full_name == "*[f]") { - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); - Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); - new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); - new_args->concat(args); - args = new_args; - } - - // populates env with default values for params - std::string ff(c->name()); - bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - std::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().path, - c->pstate().line + 1, - c->pstate().column + 1, - SASS_CALLEE_C_FUNCTION, - { env } - }); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); - for(size_t i = 0; i < params->length(); i++) { - Parameter_Obj param = params->at(i); - std::string key = param->name(); - AST_Node_Obj node = fn_env.get_local(key); - Expression_Obj arg = Cast(node); - sass_list_set_value(c_args, i, arg->perform(&to_c)); - } - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - if (sass_value_get_tag(c_val) == SASS_ERROR) { - error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); - } else if (sass_value_get_tag(c_val) == SASS_WARNING) { - error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); - } - result = cval_to_astnode(c_val, traces, c->pstate()); - - ctx.callee_stack.pop_back(); - traces.pop_back(); - sass_delete_value(c_args); - if (c_val != c_args) - sass_delete_value(c_val); - } - - // link back to function definition - // only do this for custom functions - if (result->pstate().file == std::string::npos) - result->pstate(c->pstate()); - - result = result->perform(this); - result->is_interpolant(c->is_interpolant()); - exp.env_stack.pop_back(); - return result.detach(); - } - - Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) - { - Expression_Ptr evaluated_name = s->name()->perform(this); - Expression_Ptr evaluated_args = s->arguments()->perform(this); - String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); - ss->append(evaluated_name); - ss->append(evaluated_args); - return ss->perform(this); - } - - Expression_Ptr Eval::operator()(Variable_Ptr v) - { - Expression_Obj value = 0; - Env* env = environment(); - const std::string& name(v->name()); - EnvResult rv(env->find(name)); - if (rv.found) value = static_cast(rv.it->second.ptr()); - else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); - if (Argument_Ptr arg = Cast(value)) value = arg->value(); - if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag - value->is_interpolant(v->is_interpolant()); - if (force) value->is_expanded(false); - value->set_delayed(false); // verified - value = value->perform(this); - if(!force) rv.it->second = value; - return value.detach(); - } - - Expression_Ptr Eval::operator()(Color_Ptr c) - { - return c; - } - - Expression_Ptr Eval::operator()(Number_Ptr n) - { - return n; - } - - Expression_Ptr Eval::operator()(Boolean_Ptr b) - { - return b; - } - - void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { - - bool needs_closing_brace = false; - - if (Arguments_Ptr args = Cast(ex)) { - List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); - for(auto arg : args->elements()) { - ll->append(arg->value()); - } - ll->is_interpolant(args->is_interpolant()); - needs_closing_brace = true; - res += "("; - ex = ll; - } - if (Number_Ptr nr = Cast(ex)) { - Number reduced(nr); - reduced.reduce(); - if (!reduced.is_valid_css_unit()) { - traces.push_back(Backtrace(nr->pstate())); - throw Exception::InvalidValue(traces, *nr); - } - } - if (Argument_Ptr arg = Cast(ex)) { - ex = arg->value(); - } - if (String_Quoted_Ptr sq = Cast(ex)) { - if (was_itpl) { - bool was_interpolant = ex->is_interpolant(); - ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); - ex->is_interpolant(was_interpolant); - } - } - - if (Cast(ex)) { return; } - - // parent selector needs another go - if (Cast(ex)) { - // XXX: this is never hit via spec tests - ex = ex->perform(this); - } - - if (List_Ptr l = Cast(ex)) { - List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); - // this fixes an issue with bourbon sample, not really sure why - // if (l->size() && Cast((*l)[0])) { res += ""; } - for(Expression_Obj item : *l) { - item->is_interpolant(l->is_interpolant()); - std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); - bool is_null = Cast(item) != 0; // rl != "" - if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); - } - // Check indicates that we probably should not get a list - // here. Normally single list items are already unwrapped. - if (l->size() > 1) { - // string_to_output would fail "#{'_\a' '_\a'}"; - std::string str(ll->to_string(ctx.c_options)); - str = read_hex_escapes(str); // read escapes - newline_to_space(str); // replace directly - res += str; // append to result string - } else { - res += (ll->to_string(ctx.c_options)); - } - ll->is_interpolant(l->is_interpolant()); - } - - // Value - // Function_Call - // Selector_List - // String_Quoted - // String_Constant - // Parent_Selector - // Binary_Expression - else { - // ex = ex->perform(this); - if (into_quotes && ex->is_interpolant()) { - res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); - } else { - std::string str(ex ? ex->to_string(ctx.c_options) : ""); - if (into_quotes) str = read_hex_escapes(str); - res += str; // append to result string - } - } - - if (needs_closing_brace) res += ")"; - - } - - Expression_Ptr Eval::operator()(String_Schema_Ptr s) - { - size_t L = s->length(); - bool into_quotes = false; - if (L > 1) { - if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { - if (String_Constant_Ptr l = Cast((*s)[0])) { - if (String_Constant_Ptr r = Cast((*s)[L - 1])) { - if (r->value().size() > 0) { - if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; - if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; - } - } - } - } - } - bool was_quoted = false; - bool was_interpolant = false; - std::string res(""); - for (size_t i = 0; i < L; ++i) { - bool is_quoted = Cast((*s)[i]) != NULL; - if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - Expression_Obj ex = (*s)[i]->perform(this); - interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); - was_quoted = Cast((*s)[i]) != NULL; - was_interpolant = (*s)[i]->is_interpolant(); - - } - if (!s->is_interpolant()) { - if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); - return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); - } - // string schema seems to have a special unquoting behavior (also handles "nested" quotes) - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); - // if (s->is_interpolant()) str->quote_mark(0); - // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); - if (str->quote_mark()) str->quote_mark('*'); - else if (!is_in_comment) str->value(string_to_output(str->value())); - str->is_interpolant(s->is_interpolant()); - return str.detach(); - } - - - Expression_Ptr Eval::operator()(String_Constant_Ptr s) - { - return s; - } - - Expression_Ptr Eval::operator()(String_Quoted_Ptr s) - { - String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); - str->value(s->value()); - str->quote_mark(s->quote_mark()); - str->is_interpolant(s->is_interpolant()); - return str; - } - - Expression_Ptr Eval::operator()(Supports_Operator_Ptr c) - { - Expression_Ptr left = c->left()->perform(this); - Expression_Ptr right = c->right()->perform(this); - Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, - c->pstate(), - Cast(left), - Cast(right), - c->operand()); - return cc; - } - - Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) - { - Expression_Ptr condition = c->condition()->perform(this); - Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, - c->pstate(), - Cast(condition)); - return cc; - } - - Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) - { - Expression_Ptr feature = c->feature()->perform(this); - Expression_Ptr value = c->value()->perform(this); - Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, - c->pstate(), - feature, - value); - return cc; - } - - Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) - { - Expression_Ptr value = c->value()->perform(this); - Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, - c->pstate(), - value); - return cc; - } - - Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) - { - Expression_Obj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - Expression_Obj value = e->value(); - value = (value ? value->perform(this) : 0); - Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, - e->pstate(), - Cast(feature), - value); - return ee; - } - - Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) - { - String_Obj t = q->media_type(); - t = static_cast(t.isNull() ? 0 : t->perform(this)); - Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, - q->pstate(), - t, - q->length(), - q->is_negated(), - q->is_restricted()); - for (size_t i = 0, L = q->length(); i < L; ++i) { - qq->append(static_cast((*q)[i]->perform(this))); - } - return qq.detach(); - } - - Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) - { - Expression_Obj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - if (feature && Cast(feature)) { - feature = SASS_MEMORY_NEW(String_Quoted, - feature->pstate(), - Cast(feature)->value()); - } - Expression_Obj value = e->value(); - value = (value ? value->perform(this) : 0); - if (value && Cast(value)) { - // XXX: this is never hit via spec tests - value = SASS_MEMORY_NEW(String_Quoted, - value->pstate(), - Cast(value)->value()); - } - return SASS_MEMORY_NEW(Media_Query_Expression, - e->pstate(), - feature, - value, - e->is_interpolated()); - } - - Expression_Ptr Eval::operator()(Null_Ptr n) - { - return n; - } - - Expression_Ptr Eval::operator()(Argument_Ptr a) - { - Expression_Obj val = a->value()->perform(this); - bool is_rest_argument = a->is_rest_argument(); - bool is_keyword_argument = a->is_keyword_argument(); - - if (a->is_rest_argument()) { - if (val->concrete_type() == Expression::MAP) { - is_rest_argument = false; - is_keyword_argument = true; - } - else if(val->concrete_type() != Expression::LIST) { - List_Obj wrapper = SASS_MEMORY_NEW(List, - val->pstate(), - 0, - SASS_COMMA, - true); - wrapper->append(val); - val = wrapper; - } - } - return SASS_MEMORY_NEW(Argument, - a->pstate(), - val, - a->name(), - is_rest_argument, - is_keyword_argument); - } - - Expression_Ptr Eval::operator()(Arguments_Ptr a) - { - Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); - if (a->length() == 0) return aa.detach(); - for (size_t i = 0, L = a->length(); i < L; ++i) { - Expression_Obj rv = (*a)[i]->perform(this); - Argument_Ptr arg = Cast(rv); - if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { - aa->append(arg); - } - } - - if (a->has_rest_argument()) { - Expression_Obj rest = a->get_rest_argument()->perform(this); - Expression_Obj splat = Cast(rest)->value()->perform(this); - - Sass_Separator separator = SASS_COMMA; - List_Ptr ls = Cast(splat); - Map_Ptr ms = Cast(splat); - - List_Obj arglist = SASS_MEMORY_NEW(List, - splat->pstate(), - 0, - ls ? ls->separator() : separator, - true); - - if (ls && ls->is_arglist()) { - arglist->concat(ls); - } else if (ms) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); - } else if (ls) { - arglist->concat(ls); - } else { - arglist->append(splat); - } - if (arglist->length()) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); - } - } - - if (a->has_keyword_argument()) { - Expression_Obj rv = a->get_keyword_argument()->perform(this); - Argument_Ptr rvarg = Cast(rv); - Expression_Obj kwarg = rvarg->value()->perform(this); - - aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); - } - return aa.detach(); - } - - Expression_Ptr Eval::operator()(Comment_Ptr c) - { - return 0; - } - - inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) - { - return static_cast(n); - } - - // All the binary helpers. - - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) - { - using std::strlen; - using std::strcpy; - Expression_Ptr e = NULL; - switch (sass_value_get_tag(v)) { - case SASS_BOOLEAN: { - e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); - } break; - case SASS_NUMBER: { - e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); - } break; - case SASS_COLOR: { - e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); - } break; - case SASS_STRING: { - if (sass_string_is_quoted(v)) - e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); - else { - e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); - } - } break; - case SASS_LIST: { - List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); - for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { - l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); - } - l->is_bracketed(sass_list_get_is_bracketed(v)); - e = l; - } break; - case SASS_MAP: { - Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); - for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { - *m << std::make_pair( - cval_to_astnode(sass_map_get_key(v, i), traces, pstate), - cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); - } - e = m; - } break; - case SASS_NULL: { - e = SASS_MEMORY_NEW(Null, pstate); - } break; - case SASS_ERROR: { - error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); - } break; - case SASS_WARNING: { - error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); - } break; - default: break; - } - return e; - } - - Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) - { - std::vector rv; - Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); - sl->is_optional(s->is_optional()); - sl->media_block(s->media_block()); - sl->is_optional(s->is_optional()); - for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()((*s)[i])); - } - - // we should actually permutate parent first - // but here we have permutated the selector first - size_t round = 0; - while (round != std::string::npos) { - bool abort = true; - for (size_t i = 0, iL = rv.size(); i < iL; ++i) { - if (rv[i]->length() > round) { - sl->append((*rv[i])[round]); - abort = false; - } - } - if (abort) { - round = std::string::npos; - } else { - ++ round; - } - - } - return sl.detach(); - } - - - Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) - { - bool implicit_parent = !exp.old_at_root_without_rule; - if (is_in_selector_schema) exp.selector_stack.push_back(0); - Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); - if (is_in_selector_schema) exp.selector_stack.pop_back(); - for (size_t i = 0; i < resolved->length(); i++) { - Complex_Selector_Ptr is = resolved->at(i)->first(); - while (is) { - if (is->head()) { - is->head(operator()(is->head())); - } - is = is->tail(); - } - } - return resolved.detach(); - } - - Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) - { - for (size_t i = 0; i < s->length(); i++) { - Simple_Selector_Ptr ss = s->at(i); - // skip parents here (called via resolve_parent_refs) - if (ss == NULL || Cast(ss)) continue; - s->at(i) = Cast(ss->perform(this)); - } - return s; - } - - Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) - { - LOCAL_FLAG(is_in_selector_schema, true); - // the parser will look for a brace to end the selector - ctx.c_options.in_selector = true; // do not compress colors - Expression_Obj sel = s->contents()->perform(this); - std::string result_str(sel->to_string(ctx.c_options)); - ctx.c_options.in_selector = false; // flag temporary only - result_str = unquote(Util::rtrim(result_str)); - char* temp_cstr = sass_copy_c_string(result_str.c_str()); - ctx.strings.push_back(temp_cstr); // attach to context - Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); - p.last_media_block = s->media_block(); - // a selector schema may or may not connect to parent? - bool chroot = s->connect_parent() == false; - Selector_List_Obj sl = p.parse_selector_list(chroot); - auto vec_str_rend = ctx.strings.rend(); - auto vec_str_rbegin = ctx.strings.rbegin(); - // remove the first item searching from the back - // we cannot assume our item is still the last one - // order is not important, so we can optimize this - auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); - // undefined behavior if not found! - if (it != vec_str_rend) { - // overwrite with last item - *it = ctx.strings.back(); - // remove last one from vector - ctx.strings.pop_back(); - // free temporary copy - free(temp_cstr); - } - flag_is_in_selector_schema.reset(); - return operator()(sl); - } - - Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) - { - if (Selector_List_Obj pr = selector()) { - exp.selector_stack.pop_back(); - Selector_List_Obj rv = operator()(pr); - exp.selector_stack.push_back(rv); - return rv.detach(); - } else { - return SASS_MEMORY_NEW(Null, p->pstate()); - } - } - - Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) - { - return s; - } - - // hotfix to avoid invalid nested `:not` selectors - // probably the wrong place, but this should ultimately - // be fixed by implement superselector correctly for `:not` - // first use of "find" (ATM only implemented for selectors) - bool hasNotSelector(AST_Node_Obj obj) { - if (Wrapped_Selector_Ptr w = Cast(obj)) { - return w->name() == ":not"; - } - return false; - } - - Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) - { - - if (s->name() == ":not") { - if (exp.selector_stack.back()) { - if (s->selector()->find(hasNotSelector)) { - s->selector()->clear(); - s->name(" "); - } else if (s->selector()->length() == 1) { - Complex_Selector_Ptr cs = s->selector()->at(0); - if (cs->tail()) { - s->selector()->clear(); - s->name(" "); - } - } else if (s->selector()->length() > 1) { - s->selector()->clear(); - s->name(" "); - } - } - } - return s; - }; - -} diff --git a/src/libsass/expand.cpp b/src/libsass/expand.cpp deleted file mode 100644 index d8dc03f14..000000000 --- a/src/libsass/expand.cpp +++ /dev/null @@ -1,817 +0,0 @@ -#include "sass.hpp" -#include -#include - -#include "ast.hpp" -#include "expand.hpp" -#include "bind.hpp" -#include "eval.hpp" -#include "backtrace.hpp" -#include "context.hpp" -#include "parser.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - // simple endless recursion protection - const size_t maxRecursion = 500; - - Expand::Expand(Context& ctx, Env* env, std::vector* stack) - : ctx(ctx), - traces(ctx.traces), - eval(Eval(*this)), - recursions(0), - in_keyframes(false), - at_root_without_rule(false), - old_at_root_without_rule(false), - env_stack(std::vector()), - block_stack(std::vector()), - call_stack(std::vector()), - selector_stack(std::vector()), - media_block_stack(std::vector()) - { - env_stack.push_back(0); - env_stack.push_back(env); - block_stack.push_back(0); - call_stack.push_back(0); - if (stack == NULL) { selector_stack.push_back(0); } - else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } - media_block_stack.push_back(0); - } - - Env* Expand::environment() - { - if (env_stack.size() > 0) - return env_stack.back(); - return 0; - } - - Selector_List_Obj Expand::selector() - { - if (selector_stack.size() > 0) - return selector_stack.back(); - return 0; - } - - // blocks create new variable scopes - Block_Ptr Expand::operator()(Block_Ptr b) - { - // create new local environment - // set the current env as parent - Env env(environment()); - // copy the block object (add items later) - Block_Obj bb = SASS_MEMORY_NEW(Block, - b->pstate(), - b->length(), - b->is_root()); - // setup block and env stack - this->block_stack.push_back(bb); - this->env_stack.push_back(&env); - // operate on block - // this may throw up! - this->append_block(b); - // revert block and env stack - this->block_stack.pop_back(); - this->env_stack.pop_back(); - // return copy - return bb.detach(); - } - - Statement_Ptr Expand::operator()(Ruleset_Ptr r) - { - LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); - - if (in_keyframes) { - Block_Ptr bb = operator()(r->block()); - Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); - if (r->selector()) { - if (Selector_List_Ptr s = r->selector()) { - selector_stack.push_back(0); - k->name(s->eval(eval)); - selector_stack.pop_back(); - } - } - return k.detach(); - } - - // reset when leaving scope - LOCAL_FLAG(at_root_without_rule, false); - - // `&` is allowed in `@at-root`! - bool has_parent_selector = false; - for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { - Selector_List_Obj ll = selector_stack.at(i); - has_parent_selector = ll != 0 && ll->length() > 0; - } - - Selector_List_Obj sel = r->selector(); - if (sel) sel = sel->eval(eval); - - // check for parent selectors in base level rules - if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { - if (Selector_List_Ptr selector_list = Cast(r->selector())) { - for (Complex_Selector_Obj complex_selector : selector_list->elements()) { - Complex_Selector_Ptr tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - Parent_Selector_Ptr ptr = Cast(header); - if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); - } - tail = tail->tail(); - } - } - } - } - else { - if (sel->length() == 0 || sel->has_parent_ref()) { - if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); - } - } - } - - // do not connect parent again - sel->remove_parent_selectors(); - selector_stack.push_back(sel); - Env env(environment()); - if (block_stack.back()->is_root()) { - env_stack.push_back(&env); - } - sel->set_media_block(media_block_stack.back()); - Block_Obj blk = 0; - if (r->block()) blk = operator()(r->block()); - Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, - r->pstate(), - sel, - blk); - selector_stack.pop_back(); - if (block_stack.back()->is_root()) { - env_stack.pop_back(); - } - - rr->is_root(r->is_root()); - rr->tabs(r->tabs()); - - return rr; - } - - Statement_Ptr Expand::operator()(Supports_Block_Ptr f) - { - Expression_Obj condition = f->condition()->perform(&eval); - Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, - f->pstate(), - Cast(condition), - operator()(f->block())); - return ff.detach(); - } - - Statement_Ptr Expand::operator()(Media_Block_Ptr m) - { - Media_Block_Obj cpy = SASS_MEMORY_COPY(m); - // Media_Blocks are prone to have circular references - // Copy could leak memory if it does not get picked up - // Looks like we are able to reset block reference for copy - // Good as it will ensure a low memory overhead for this fix - // So this is a cheap solution with a minimal price - ctx.ast_gc.push_back(cpy); cpy->block(0); - Expression_Obj mq = eval(m->media_queries()); - std::string str_mq(mq->to_string(ctx.c_options)); - char* str = sass_copy_c_string(str_mq.c_str()); - ctx.strings.push_back(str); - Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); - mq = p.parse_media_queries(); // re-assign now - cpy->media_queries(mq); - media_block_stack.push_back(cpy); - Block_Obj blk = operator()(m->block()); - Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - mq, - blk); - media_block_stack.pop_back(); - mm->tabs(m->tabs()); - return mm; - } - - Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) - { - Block_Obj ab = a->block(); - Expression_Obj ae = a->expression(); - - if (ae) ae = ae->perform(&eval); - else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); - - LOCAL_FLAG(at_root_without_rule, true); - LOCAL_FLAG(in_keyframes, false); - - ; - - Block_Obj bb = ab ? operator()(ab) : NULL; - At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, - a->pstate(), - bb, - Cast(ae)); - return aa.detach(); - } - - Statement_Ptr Expand::operator()(Directive_Ptr a) - { - LOCAL_FLAG(in_keyframes, a->is_keyframes()); - Block_Ptr ab = a->block(); - Selector_List_Ptr as = a->selector(); - Expression_Ptr av = a->value(); - selector_stack.push_back(0); - if (av) av = av->perform(&eval); - if (as) as = eval(as); - selector_stack.pop_back(); - Block_Ptr bb = ab ? operator()(ab) : NULL; - Directive_Ptr aa = SASS_MEMORY_NEW(Directive, - a->pstate(), - a->keyword(), - as, - bb, - av); - return aa; - } - - Statement_Ptr Expand::operator()(Declaration_Ptr d) - { - Block_Obj ab = d->block(); - String_Obj old_p = d->property(); - Expression_Obj prop = old_p->perform(&eval); - String_Obj new_p = Cast(prop); - // we might get a color back - if (!new_p) { - std::string str(prop->to_string(ctx.c_options)); - new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); - } - Expression_Obj value = d->value(); - if (value) value = value->perform(&eval); - Block_Obj bb = ab ? operator()(ab) : NULL; - if (!bb) { - if (!value || (value->is_invisible() && !d->is_important())) return 0; - } - Declaration_Ptr decl = SASS_MEMORY_NEW(Declaration, - d->pstate(), - new_p, - value, - d->is_important(), - d->is_custom_property(), - bb); - decl->tabs(d->tabs()); - return decl; - } - - Statement_Ptr Expand::operator()(Assignment_Ptr a) - { - Env* env = environment(); - const std::string& var(a->variable()); - if (a->is_global()) { - if (a->is_default()) { - if (env->has_global(var)) { - Expression_Obj e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Obj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(&eval)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); - } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - Expression_Obj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(&eval)); - } - else { - env->set_local(var, a->value()->perform(&eval)); - } - } - else { - env->set_lexical(var, a->value()->perform(&eval)); - } - return 0; - } - - Statement_Ptr Expand::operator()(Import_Ptr imp) - { - Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); - if (imp->import_queries() && imp->import_queries()->size()) { - Expression_Obj ex = imp->import_queries()->perform(&eval); - result->import_queries(Cast(ex)); - } - for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { - result->urls().push_back(imp->urls()[i]->perform(&eval)); - } - // all resources have been dropped for Input_Stubs - // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} - return result.detach(); - } - - Statement_Ptr Expand::operator()(Import_Stub_Ptr i) - { - traces.push_back(Backtrace(i->pstate())); - // get parent node from call stack - AST_Node_Obj parent = call_stack.back(); - if (Cast(parent) == NULL) { - error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); - } - // we don't seem to need that actually afterall - Sass_Import_Entry import = sass_make_import( - i->imp_path().c_str(), - i->abs_path().c_str(), - 0, 0 - ); - ctx.import_stack.push_back(import); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); - block_stack.back()->append(trace); - block_stack.push_back(trace_block); - - const std::string& abs_path(i->resource().abs_path); - append_block(ctx.sheets.at(abs_path).root); - sass_delete_import(ctx.import_stack.back()); - ctx.import_stack.pop_back(); - block_stack.pop_back(); - traces.pop_back(); - return 0; - } - - Statement_Ptr Expand::operator()(Warning_Ptr w) - { - // eval handles this too, because warnings may occur in functions - w->perform(&eval); - return 0; - } - - Statement_Ptr Expand::operator()(Error_Ptr e) - { - // eval handles this too, because errors may occur in functions - e->perform(&eval); - return 0; - } - - Statement_Ptr Expand::operator()(Debug_Ptr d) - { - // eval handles this too, because warnings may occur in functions - d->perform(&eval); - return 0; - } - - Statement_Ptr Expand::operator()(Comment_Ptr c) - { - if (ctx.output_style() == COMPRESSED) { - // comments should not be evaluated in compact - // https://github.com/sass/libsass/issues/2359 - if (!c->is_important()) return NULL; - } - eval.is_in_comment = true; - Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); - eval.is_in_comment = false; - // TODO: eval the text, once we're parsing/storing it as a String_Schema - return rv; - } - - Statement_Ptr Expand::operator()(If_Ptr i) - { - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(i); - Expression_Obj rv = i->predicate()->perform(&eval); - if (*rv) { - append_block(i->block()); - } - else { - Block_Ptr alt = i->alternative(); - if (alt) append_block(alt); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // For does not create a new env scope - // But iteration vars are reset afterwards - Statement_Ptr Expand::operator()(For_Ptr f) - { - std::string variable(f->variable()); - Expression_Obj low = f->lower_bound()->perform(&eval); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); - } - Expression_Obj high = f->upper_bound()->perform(&eval); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); - } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - std::stringstream msg; msg << "Incompatible units: '" - << sass_start->unit() << "' and '" - << sass_end->unit() << "'."; - error(msg.str(), low->pstate(), traces); - } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(f); - Block_Ptr body = f->block(); - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Statement_Ptr Expand::operator()(Each_Ptr e) - { - std::vector variables(e->variables()); - Expression_Obj expr = e->list()->perform(&eval); - List_Obj list = 0; - Map_Obj map; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); - } - else if (Selector_List_Ptr ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); - list = Cast(rv); - } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); - } - else { - list = Cast(expr); - } - // remember variables and then reset them - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(e); - Block_Ptr body = e->block(); - - if (map) { - for (auto key : map->keys()) { - Expression_Obj k = key->perform(&eval); - Expression_Obj v = map->at(key)->perform(&eval); - - if (variables.size() == 1) { - List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(k); - variable->append(v); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], k); - env.set_local(variables[1], v); - } - append_block(body); - } - } - else { - // bool arglist = list->is_arglist(); - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Obj item = list->at(i); - // unwrap value if the expression is an argument - if (Argument_Obj arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List_Obj scalars = Cast(item)) { - if (variables.size() == 1) { - List_Obj var = scalars; - // if (arglist) var = (*scalars)[0]; - env.set_local(variables[0], var); - } else { - for (size_t j = 0, K = variables.size(); j < K; ++j) { - Expression_Obj res = j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) - : (*scalars)[j]->perform(&eval); - env.set_local(variables[j], res); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } - } - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement_Ptr Expand::operator()(While_Ptr w) - { - Expression_Obj pred = w->predicate(); - Block_Ptr body = w->block(); - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(w); - Expression_Obj cond = pred->perform(&eval); - while (!cond->is_false()) { - append_block(body); - cond = pred->perform(&eval); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement_Ptr Expand::operator()(Return_Ptr r) - { - error("@return may only be used within a function", r->pstate(), traces); - return 0; - } - - - void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { - - if (Selector_List_Obj sl = Cast(s)) { - for (Complex_Selector_Obj complex_selector : sl->elements()) { - Complex_Selector_Obj tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - if (Cast(header) == NULL) continue; // skip all others - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); - } - tail = tail->tail(); - } - } - } - - - Selector_List_Obj contextualized = Cast(s->perform(&eval)); - if (contextualized == false) return; - for (auto complex_sel : contextualized->elements()) { - Complex_Selector_Obj c = complex_sel; - if (!c->head() || c->tail()) { - std::string sel_str(contextualized->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); - } - Compound_Selector_Obj target = c->head(); - if (contextualized->is_optional()) target->is_optional(true); - for (size_t i = 0, L = extender->length(); i < L; ++i) { - Complex_Selector_Obj sel = (*extender)[i]; - if (!(sel->head() && sel->head()->length() > 0 && - Cast((*sel->head())[0]))) - { - Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); - hh->media_block((*extender)[i]->media_block()); - Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); - ssel->media_block((*extender)[i]->media_block()); - if (sel->has_line_feed()) ssel->has_line_feed(true); - Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); - ps->media_block((*extender)[i]->media_block()); - hh->append(ps); - ssel->tail(sel); - ssel->head(hh); - sel = ssel; - } - // if (c->has_line_feed()) sel->has_line_feed(true); - ctx.subset_map.put(target, std::make_pair(sel, target)); - } - } - - } - - Statement* Expand::operator()(Extension_Ptr e) - { - if (Selector_List_Ptr extender = selector()) { - Selector_List_Ptr sl = e->selector(); - // abort on invalid selector - if (sl == NULL) return NULL; - if (Selector_Schema_Ptr schema = sl->schema()) { - if (schema->has_real_parent_ref()) { - // put root block on stack again (ignore parents) - // selector schema must not connect in eval! - block_stack.push_back(block_stack.at(1)); - sl = eval(sl->schema()); - block_stack.pop_back(); - } else { - selector_stack.push_back(0); - sl = eval(sl->schema()); - selector_stack.pop_back(); - } - } - for (Complex_Selector_Obj cs : sl->elements()) { - if (!cs.isNull() && !cs->head().isNull()) { - cs->head()->media_block(media_block_stack.back()); - } - } - selector_stack.push_back(0); - expand_selector_list(sl, extender); - selector_stack.pop_back(); - } - return 0; - } - - Statement_Ptr Expand::operator()(Definition_Ptr d) - { - Env* env = environment(); - Definition_Obj dd = SASS_MEMORY_COPY(d); - env->local_frame()[d->name() + - (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; - - if (d->type() == Definition::FUNCTION && ( - Prelexer::calc_fn_call(d->name().c_str()) || - d->name() == "element" || - d->name() == "expression" || - d->name() == "url" - )) { - deprecated( - "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", - "This name conflicts with an existing CSS function with special parse rules.", - false, d->pstate() - ); - } - - // set the static link so we can have lexical scoping - dd->environment(env); - return 0; - } - - Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) - { - if (recursions > maxRecursion) { - throw Exception::StackError(traces, *c); - } - - recursions ++; - - Env* env = environment(); - std::string full_name(c->name() + "[m]"); - if (!env->has(full_name)) { - error("no mixin named " + c->name(), c->pstate(), traces); - } - Definition_Obj def = Cast((*env)[full_name]); - Block_Obj body = def->block(); - Parameters_Obj params = def->parameters(); - - if (c->block() && c->name() != "@content" && !body->has_content()) { - error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); - } - Expression_Obj rv = c->arguments()->perform(&eval); - Arguments_Obj args = Cast(rv); - std::string msg(", in mixin `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().path, - c->pstate().line + 1, - c->pstate().column + 1, - SASS_CALLEE_MIXIN, - { env } - }); - - Env new_env(def->environment()); - env_stack.push_back(&new_env); - if (c->block()) { - // represent mixin content blocks as thunks/closures - Definition_Obj thunk = SASS_MEMORY_NEW(Definition, - c->pstate(), - "@content", - SASS_MEMORY_NEW(Parameters, c->pstate()), - c->block(), - Definition::MIXIN); - thunk->environment(env); - new_env.local_frame()["@content[m]"] = thunk; - } - - bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); - - env->set_global("is_in_mixin", bool_true); - if (Block_Ptr pr = block_stack.back()) { - trace_block->is_root(pr->is_root()); - } - block_stack.push_back(trace_block); - for (auto bb : body->elements()) { - if (Ruleset_Ptr r = Cast(bb)) { - r->is_root(trace_block->is_root()); - } - Statement_Obj ith = bb->perform(this); - if (ith) trace->block()->append(ith); - } - block_stack.pop_back(); - env->del_global("is_in_mixin"); - - ctx.callee_stack.pop_back(); - env_stack.pop_back(); - traces.pop_back(); - - recursions --; - return trace.detach(); - } - - Statement_Ptr Expand::operator()(Content_Ptr c) - { - Env* env = environment(); - // convert @content directives into mixin calls to the underlying thunk - if (!env->has("@content[m]")) return 0; - - if (block_stack.back()->is_root()) { - selector_stack.push_back(0); - } - - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, - c->pstate(), - "@content", - SASS_MEMORY_NEW(Arguments, c->pstate())); - - Trace_Obj trace = Cast(call->perform(this)); - - if (block_stack.back()->is_root()) { - selector_stack.pop_back(); - } - - return trace.detach(); - } - - // produce an error if something is not implemented - inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) - { - std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); - String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); - error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); - return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); - } - - // process and add to last block on stack - inline void Expand::append_block(Block_Ptr b) - { - if (b->is_root()) call_stack.push_back(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr stm = b->at(i); - Statement_Obj ith = stm->perform(this); - if (ith) block_stack.back()->append(ith); - } - if (b->is_root()) call_stack.pop_back(); - } - -} diff --git a/src/libsass/file.cpp b/src/libsass/file.cpp deleted file mode 100644 index ab2065194..000000000 --- a/src/libsass/file.cpp +++ /dev/null @@ -1,485 +0,0 @@ -#include "sass.hpp" -#ifdef _WIN32 -# ifdef __MINGW32__ -# ifndef off64_t -# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ -# endif -# endif -# include -# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#else -# include -#endif -#include -#include -#include -#include -#include -#include -#include "file.hpp" -#include "context.hpp" -#include "prelexer.hpp" -#include "utf8_string.hpp" -#include "sass_functions.hpp" -#include "sass2scss.h" - -#ifdef _WIN32 -# include - -# ifdef _MSC_VER -# include -inline static std::string wstring_to_string(const std::wstring& wstr) -{ - std::wstring_convert, wchar_t> wchar_converter; - return wchar_converter.to_bytes(wstr); -} -# else // mingw(/gcc) does not support C++11's codecvt yet. -inline static std::string wstring_to_string(const std::wstring &wstr) -{ - int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); - std::string strTo(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); - return strTo; -} -# endif -#endif - -namespace Sass { - namespace File { - - // return the current directory - // always with forward slashes - // always with trailing slash - std::string get_cwd() - { - const size_t wd_len = 4096; - #ifndef _WIN32 - char wd[wd_len]; - char* pwd = getcwd(wd, wd_len); - // we should check error for more detailed info (e.g. ENOENT) - // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS - if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); - std::string cwd = pwd; - #else - wchar_t wd[wd_len]; - wchar_t* pwd = _wgetcwd(wd, wd_len); - if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); - std::string cwd = wstring_to_string(pwd); - //convert backslashes to forward slashes - replace(cwd.begin(), cwd.end(), '\\', '/'); - #endif - if (cwd[cwd.length() - 1] != '/') cwd += '/'; - return cwd; - } - - // test if path exists and is a file - bool file_exists(const std::string& path) - { - #ifdef _WIN32 - wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - std::string abspath(join_paths(get_cwd(), path)); - std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); - std::replace(wpath.begin(), wpath.end(), '/', '\\'); - DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); - if (rv > 32767) throw Exception::OperationError("Path is too long"); - if (rv == 0) throw Exception::OperationError("Path could not be resolved"); - DWORD dwAttrib = GetFileAttributesW(resolved); - return (dwAttrib != INVALID_FILE_ATTRIBUTES && - (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); - #else - struct stat st_buf; - return (stat (path.c_str(), &st_buf) == 0) && - (!S_ISDIR (st_buf.st_mode)); - #endif - } - - // return if given path is absolute - // works with *nix and windows paths - bool is_absolute_path(const std::string& path) - { - #ifdef _WIN32 - if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; - #endif - size_t i = 0; - // check if we have a protocol - if (path[i] && Prelexer::is_alpha(path[i])) { - // skip over all alphanumeric characters - while (path[i] && Prelexer::is_alnum(path[i])) ++i; - i = i && path[i] == ':' ? i + 1 : 0; - } - return path[i] == '/'; - } - - // helper function to find the last directory seperator - inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) - { - size_t pos; - size_t pos_p = path.find_last_of('/', limit); - #ifdef _WIN32 - size_t pos_w = path.find_last_of('\\', limit); - #else - size_t pos_w = std::string::npos; - #endif - if (pos_p != std::string::npos && pos_w != std::string::npos) { - pos = std::max(pos_p, pos_w); - } - else if (pos_p != std::string::npos) { - pos = pos_p; - } - else { - pos = pos_w; - } - return pos; - } - - // return only the directory part of path - std::string dir_name(const std::string& path) - { - size_t pos = find_last_folder_separator(path); - if (pos == std::string::npos) return ""; - else return path.substr(0, pos+1); - } - - // return only the filename part of path - std::string base_name(const std::string& path) - { - size_t pos = find_last_folder_separator(path); - if (pos == std::string::npos) return path; - else return path.substr(pos+1); - } - - // do a logical clean up of the path - // no physical check on the filesystem - std::string make_canonical_path (std::string path) - { - - // declarations - size_t pos; - - #ifdef _WIN32 - //convert backslashes to forward slashes - replace(path.begin(), path.end(), '\\', '/'); - #endif - - pos = 0; // remove all self references inside the path string - while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); - - // remove all leading and trailing self references - while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); - while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); - - - size_t proto = 0; - // check if we have a protocol - if (path[proto] && Prelexer::is_alpha(path[proto])) { - // skip over all alphanumeric characters - while (path[proto] && Prelexer::is_alnum(path[proto++])) {} - // then skip over the mandatory colon - if (proto && path[proto] == ':') ++ proto; - } - - // then skip over start slashes - while (path[proto++] == '/') {} - - pos = proto; // collapse multiple delimiters into a single one - while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1); - - return path; - - } - - // join two path segments cleanly together - // but only if right side is not absolute yet - std::string join_paths(std::string l, std::string r) - { - - #ifdef _WIN32 - // convert Windows backslashes to URL forward slashes - replace(l.begin(), l.end(), '\\', '/'); - replace(r.begin(), r.end(), '\\', '/'); - #endif - - if (l.empty()) return r; - if (r.empty()) return l; - - if (is_absolute_path(r)) return r; - if (l[l.length()-1] != '/') l += '/'; - - // this does a logical cleanup of the right hand path - // Note that this does collapse x/../y sections into y. - // This is by design. If /foo on your system is a symlink - // to /bar/baz, then /foo/../cd is actually /bar/cd, - // not /cd as a naive ../ removal would give you. - // will only work on leading double dot dirs on rhs - // therefore it is safe if lhs is already resolved cwd - while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { - size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); - bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); - bool is_self = pos + 3 == L && (l[pos+1] == '.'); - if (!is_self && !is_slash) r = r.substr(3); - else if (pos == std::string::npos) break; - l = l.substr(0, pos == std::string::npos ? pos : pos + 1); - } - - return l + r; - } - - std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path) - { - // magic algorith goes here!! - - // if the file is outside this directory show the absolute path - if (rel_path.substr(0, 3) == "../") { - return orig_path; - } - // this seems to work most of the time - return abs_path == orig_path ? abs_path : rel_path; - } - - // create an absolute path by resolving relative paths with cwd - std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd) - { - return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); - } - - // create a path that is relative to the given base directory - // path and base will first be resolved against cwd to make them absolute - std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd) - { - - std::string abs_path = rel2abs(path, cwd); - std::string abs_base = rel2abs(base, cwd); - - size_t proto = 0; - // check if we have a protocol - if (path[proto] && Prelexer::is_alpha(path[proto])) { - // skip over all alphanumeric characters - while (path[proto] && Prelexer::is_alnum(path[proto++])) {} - // then skip over the mandatory colon - if (proto && path[proto] == ':') ++ proto; - } - - // distinguish between windows absolute paths and valid protocols - // we assume that protocols must at least have two chars to be valid - if (proto && path[proto++] == '/' && proto > 3) return path; - - #ifdef _WIN32 - // absolute link must have a drive letter, and we know that we - // can only create relative links if both are on the same drive - if (abs_base[0] != abs_path[0]) return abs_path; - #endif - - std::string stripped_uri = ""; - std::string stripped_base = ""; - - size_t index = 0; - size_t minSize = std::min(abs_path.size(), abs_base.size()); - for (size_t i = 0; i < minSize; ++i) { - #ifdef FS_CASE_SENSITIVE - if (abs_path[i] != abs_base[i]) break; - #else - // compare the charactes in a case insensitive manner - // windows fs is only case insensitive in ascii ranges - if (tolower(abs_path[i]) != tolower(abs_base[i])) break; - #endif - if (abs_path[i] == '/') index = i + 1; - } - for (size_t i = index; i < abs_path.size(); ++i) { - stripped_uri += abs_path[i]; - } - for (size_t i = index; i < abs_base.size(); ++i) { - stripped_base += abs_base[i]; - } - - size_t left = 0; - size_t directories = 0; - for (size_t right = 0; right < stripped_base.size(); ++right) { - if (stripped_base[right] == '/') { - if (stripped_base.substr(left, 2) != "..") { - ++directories; - } - else if (directories > 1) { - --directories; - } - else { - directories = 0; - } - left = right + 1; - } - } - - std::string result = ""; - for (size_t i = 0; i < directories; ++i) { - result += "../"; - } - result += stripped_uri; - - return result; - } - - // Resolution order for ambiguous imports: - // (1) filename as given - // (2) underscore + given - // (3) underscore + given + extension - // (4) given + extension - std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) - { - std::string filename = join_paths(root, file); - // split the filename - std::string base(dir_name(file)); - std::string name(base_name(file)); - std::vector includes; - // create full path (maybe relative) - std::string rel_path(join_paths(base, name)); - std::string abs_path(join_paths(root, rel_path)); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test variation with underscore - rel_path = join_paths(base, "_" + name); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test exts plus underscore - for(auto ext : exts) { - rel_path = join_paths(base, "_" + name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); - } - // next test plain name with exts - for(auto ext : exts) { - rel_path = join_paths(base, name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); - } - // nothing found - return includes; - } - - std::vector find_files(const std::string& file, const std::vector paths) - { - std::vector includes; - for (std::string path : paths) { - std::string abs_path(join_paths(path, file)); - if (file_exists(abs_path)) includes.push_back(abs_path); - } - return includes; - } - - std::vector find_files(const std::string& file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - // struct Sass_Options* options = sass_compiler_get_options(compiler); - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const std::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - std::vector paths(1 + incs.size()); - paths.push_back(dir_name(import->abs_path)); - paths.insert(paths.end(), incs.begin(), incs.end()); - // dispatch to find files in paths - return find_files(file, paths); - } - - // helper function to search one file in all include paths - // this is normally not used internally by libsass (C-API sugar) - std::string find_file(const std::string& file, const std::vector paths) - { - if (file.empty()) return file; - auto res = find_files(file, paths); - return res.empty() ? "" : res.front(); - } - - // helper function to resolve a filename - std::string find_include(const std::string& file, const std::vector paths) - { - // search in every include path for a match - for (size_t i = 0, S = paths.size(); i < S; ++i) - { - std::vector resolved(resolve_includes(paths[i], file)); - if (resolved.size()) return resolved[0].abs_path; - } - // nothing found - return std::string(""); - } - - // try to load the given filename - // returned memory must be freed - // will auto convert .sass files - char* read_file(const std::string& path) - { - #ifdef _WIN32 - BYTE* pBuffer; - DWORD dwBytes; - wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - std::string abspath(join_paths(get_cwd(), path)); - std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); - std::replace(wpath.begin(), wpath.end(), '/', '\\'); - DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); - if (rv > 32767) throw Exception::OperationError("Path is too long"); - if (rv == 0) throw Exception::OperationError("Path could not be resolved"); - HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hFile == INVALID_HANDLE_VALUE) return 0; - DWORD dwFileLength = GetFileSize(hFile, NULL); - if (dwFileLength == INVALID_FILE_SIZE) return 0; - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); - ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); - pBuffer[dwFileLength+0] = '\0'; - pBuffer[dwFileLength+1] = '\0'; - CloseHandle(hFile); - // just convert from unsigned char* - char* contents = (char*) pBuffer; - #else - struct stat st; - if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0; - std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - char* contents = 0; - if (file.is_open()) { - size_t size = file.tellg(); - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - contents = (char*) malloc((size+2)*sizeof(char)); - file.seekg(0, std::ios::beg); - file.read(contents, size); - contents[size+0] = '\0'; - contents[size+1] = '\0'; - file.close(); - } - #endif - std::string extension; - if (path.length() > 5) { - extension = path.substr(path.length() - 5, 5); - } - for(size_t i=0; i split_path_list(const char* str) - { - std::vector paths; - if (str == NULL) return paths; - // find delimiter via prelexer (return zero at end) - const char* end = Prelexer::find_first(str); - // search until null delimiter - while (end) { - // add path from current position to delimiter - paths.push_back(std::string(str, end - str)); - str = end + 1; // skip delimiter - end = Prelexer::find_first(str); - } - // add path from current position to end - paths.push_back(std::string(str)); - // return back - return paths; - } - - } -} diff --git a/src/libsass/file.hpp b/src/libsass/file.hpp deleted file mode 100644 index a043bea7a..000000000 --- a/src/libsass/file.hpp +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef SASS_FILE_H -#define SASS_FILE_H - -#include -#include - -#include "sass/context.h" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - namespace File { - - // return the current directory - // always with forward slashes - std::string get_cwd(); - - // test if path exists and is a file - bool file_exists(const std::string& file); - - // return if given path is absolute - // works with *nix and windows paths - bool is_absolute_path(const std::string& path); - - // return only the directory part of path - std::string dir_name(const std::string& path); - - // return only the filename part of path - std::string base_name(const std::string&); - - // do a locigal clean up of the path - // no physical check on the filesystem - std::string make_canonical_path (std::string path); - - // join two path segments cleanly together - // but only if right side is not absolute yet - std::string join_paths(std::string root, std::string name); - - // if the relative path is outside of the cwd we want want to - // show the absolute path in console messages - std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path); - - // create an absolute path by resolving relative paths with cwd - std::string rel2abs(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); - - // create a path that is relative to the given base directory - // path and base will first be resolved against cwd to make them absolute - std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); - - // helper function to resolve a filename - // searching without variations in all paths - std::string find_file(const std::string& file, struct Sass_Compiler* options); - std::string find_file(const std::string& file, const std::vector paths); - - // helper function to resolve a include filename - // this has the original resolve logic for sass include - std::string find_include(const std::string& file, const std::vector paths); - - // split a path string delimited by semicolons or colons (OS dependent) - std::vector split_path_list(const char* paths); - - // try to load the given filename - // returned memory must be freed - // will auto convert .sass files - char* read_file(const std::string& file); - - } - - // requested import - class Importer { - public: - // requested import path - std::string imp_path; - // parent context path - std::string ctx_path; - // base derived from context path - // this really just acts as a cache - std::string base_path; - public: - Importer(std::string imp_path, std::string ctx_path) - : imp_path(File::make_canonical_path(imp_path)), - ctx_path(File::make_canonical_path(ctx_path)), - base_path(File::dir_name(ctx_path)) - { } - }; - - // a resolved include (final import) - class Include : public Importer { - public: - // resolved absolute path - std::string abs_path; - // is a deprecated file type - bool deprecated; - public: - Include(const Importer& imp, std::string abs_path, bool deprecated) - : Importer(imp), abs_path(abs_path), deprecated(deprecated) - { } - Include(const Importer& imp, std::string abs_path) - : Importer(imp), abs_path(abs_path), deprecated(false) - { } - }; - - // a loaded resource - class Resource { - public: - // the file contents - char* contents; - // conected sourcemap - char* srcmap; - public: - Resource(char* contents, char* srcmap) - : contents(contents), srcmap(srcmap) - { } - }; - - // parsed stylesheet from loaded resource - class StyleSheet : public Resource { - public: - // parsed root block - Block_Obj root; - public: - StyleSheet(const Resource& res, Block_Obj root) - : Resource(res), root(root) - { } - }; - - namespace File { - - static std::vector defaultExtensions = { ".scss", ".sass" }; - - std::vector resolve_includes(const std::string& root, const std::string& file, - const std::vector& exts = defaultExtensions); - - - } - -} - -#endif diff --git a/src/libsass/functions.cpp b/src/libsass/functions.cpp deleted file mode 100644 index c9999fc3a..000000000 --- a/src/libsass/functions.cpp +++ /dev/null @@ -1,2234 +0,0 @@ -#include "sass.hpp" -#include "functions.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "parser.hpp" -#include "constants.hpp" -#include "inspect.hpp" -#include "extend.hpp" -#include "eval.hpp" -#include "util.hpp" -#include "expand.hpp" -#include "operators.hpp" -#include "utf8_string.hpp" -#include "sass/base.h" -#include "utf8.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __MINGW32__ -#include "windows.h" -#include "wincrypt.h" -#endif - -#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) -#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) - -// return a number object (copied since we want to have reduced units) -#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy - -// special function for weird hsla percent (10px == 10% == 10 != 0.1) -#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double - -// macros for common ranges (u mean unsigned or upper, r for full range) -#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double -#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double -#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double -#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double -#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double -#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double - -// macros for color related inputs (rbg and alpha/opacity values) -#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double -#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double - -namespace Sass { - using std::stringstream; - using std::endl; - - Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) - { - Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); - sig_parser.lex(); - std::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - ParserState("[built-in function]"), - sig, - name, - params, - func, - false); - } - - Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) - { - using namespace Prelexer; - - const char* sig = sass_function_get_signature(c_func); - Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); - // allow to overload generic callback plus @warn, @error and @debug with custom functions - sig_parser.lex < alternatives < identifier, exactly <'*'>, - exactly < Constants::warn_kwd >, - exactly < Constants::error_kwd >, - exactly < Constants::debug_kwd > - > >(); - std::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - ParserState("[c function]"), - sig, - name, - params, - c_func, - false, true); - } - - std::string function_name(Signature sig) - { - std::string str(sig); - return str.substr(0, str.find('(')); - } - - namespace Functions { - - inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) - { - try { - throw; - } - catch (utf8::invalid_code_point) { - std::string msg("utf8::invalid_code_point"); - error(msg, pstate, traces); - } - catch (utf8::not_enough_room) { - std::string msg("utf8::not_enough_room"); - error(msg, pstate, traces); - } - catch (utf8::invalid_utf8) { - std::string msg("utf8::invalid_utf8"); - error(msg, pstate, traces); - } - catch (...) { throw; } - } - - template - T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - T* val = Cast(env[argname]); - if (!val) { - std::string msg("argument `"); - msg += argname; - msg += "` of `"; - msg += sig; - msg += "` must be a "; - msg += T::type_name(); - error(msg, pstate, traces); - } - return val; - } - - Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Map_Ptr val = Cast(env[argname]); - if (val) return val; - - List_Ptr lval = Cast(env[argname]); - if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); - - // fallback on get_arg for error handling - val = get_arg(argname, env, sig, pstate, traces); - return val; - } - - double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - double v = tmpnr.value(); - if (!(lo <= v && v <= hi)) { - std::stringstream msg; - msg << "argument `" << argname << "` of `" << sig << "` must be between "; - msg << lo << " and " << hi; - error(msg.str(), pstate, traces); - } - return v; - } - - Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - val = SASS_MEMORY_COPY(val); - val->reduce(); - return val; - } - - double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - /* - if (tmpnr.unit() == "%") { - tmpnr.value(tmpnr.value() / 100); - tmpnr.numerators.clear(); - } else { - if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); - } - */ - return tmpnr.value(); - } - - double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr.value(); - } - - double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 255.0); - } - } - - - inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value(), 0.0), 100.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 1.0); - } - } - - #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) - - template - T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); - - template <> - Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { - Expression_Obj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << argname << ": null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - return Parser::parse_selector(exp_src.c_str(), ctx, traces); - } - - template <> - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { - Expression_Obj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); - if (sel_list->length() == 0) return NULL; - Complex_Selector_Obj first = sel_list->first(); - if (!first->tail()) return first->head(); - return first->tail()->head(); - } - - #ifdef __MINGW32__ - uint64_t GetSeed() - { - HCRYPTPROV hp = 0; - BYTE rb[8]; - CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); - CryptGenRandom(hp, sizeof(rb), rb); - CryptReleaseContext(hp, 0); - - uint64_t seed; - memcpy(&seed, &rb[0], sizeof(seed)); - - return seed; - } - #else - uint64_t GetSeed() - { - std::random_device rd; - return rd(); - } - #endif - - // note: the performance of many implementations of - // random_device degrades sharply once the entropy pool - // is exhausted. For practical use, random_device is - // generally only used to seed a PRNG such as mt19937. - static std::mt19937 rand(static_cast(GetSeed())); - - // features - static std::set features { - "global-variable-shadowing", - "extend-selector-pseudoclass", - "at-error", - "units-level-3", - "custom-property" - }; - - //////////////// - // RGB FUNCTIONS - //////////////// - - inline bool special_number(String_Constant_Ptr s) { - if (s) { - std::string calc("calc("); - std::string var("var("); - std::string ss(s->value()); - return std::equal(calc.begin(), calc.end(), ss.begin()) || - std::equal(var.begin(), var.end(), ss.begin()); - } - return false; - } - - Signature rgb_sig = "rgb($red, $green, $blue)"; - BUILT_IN(rgb) - { - if ( - special_number(Cast(env["$red"])) || - special_number(Cast(env["$green"])) || - special_number(Cast(env["$blue"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue")); - } - - Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; - BUILT_IN(rgba_4) - { - if ( - special_number(Cast(env["$red"])) || - special_number(Cast(env["$green"])) || - special_number(Cast(env["$blue"])) || - special_number(Cast(env["$alpha"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue"), - ALPHA_NUM("$alpha")); - } - - Signature rgba_2_sig = "rgba($color, $alpha)"; - BUILT_IN(rgba_2) - { - if ( - special_number(Cast(env["$color"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$color"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - Color_Ptr c_arg = ARG("$color", Color); - - if ( - special_number(Cast(env["$alpha"])) - ) { - std::stringstream strm; - strm << "rgba(" - << (int)c_arg->r() << ", " - << (int)c_arg->g() << ", " - << (int)c_arg->b() << ", " - << env["$alpha"]->to_string() - << ")"; - return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); - } - - Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); - new_c->a(ALPHA_NUM("$alpha")); - new_c->disp(""); - return new_c; - } - - Signature red_sig = "red($color)"; - BUILT_IN(red) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } - - Signature green_sig = "green($color)"; - BUILT_IN(green) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } - - Signature blue_sig = "blue($color)"; - BUILT_IN(blue) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } - - Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { - double p = weight/100; - double w = 2*p - 1; - double a = color1->a() - color2->a(); - - double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; - double w2 = 1 - w1; - - return SASS_MEMORY_NEW(Color, - pstate, - Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), - Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), - Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), - color1->a()*p + color2->a()*(1-p)); - } - - Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; - BUILT_IN(mix) - { - Color_Obj color1 = ARG("$color-1", Color); - Color_Obj color2 = ARG("$color-2", Color); - double weight = DARG_U_PRCT("$weight"); - return colormix(ctx, pstate, color1, color2, weight); - - } - - //////////////// - // HSL FUNCTIONS - //////////////// - - // RGB to HSL helper function - struct HSL { double h; double s; double l; }; - HSL rgb_to_hsl(double r, double g, double b) - { - - // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV - r /= 255.0; g /= 255.0; b /= 255.0; - - double max = std::max(r, std::max(g, b)); - double min = std::min(r, std::min(g, b)); - double delta = max - min; - - double h = 0; - double s; - double l = (max + min) / 2.0; - - if (NEAR_EQUAL(max, min)) { - h = s = 0; // achromatic - } - else { - if (l < 0.5) s = delta / (max + min); - else s = delta / (2.0 - max - min); - - if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); - else if (g == max) h = (b - r) / delta + 2; - else if (b == max) h = (r - g) / delta + 4; - } - - HSL hsl_struct; - hsl_struct.h = h / 6 * 360; - hsl_struct.s = s * 100; - hsl_struct.l = l * 100; - - return hsl_struct; - } - - // hue to RGB helper function - double h_to_rgb(double m1, double m2, double h) { - while (h < 0) h += 1; - while (h > 1) h -= 1; - if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; - if (h*2.0 < 1) return m2; - if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; - return m1; - } - - Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) - { - h /= 360.0; - s /= 100.0; - l /= 100.0; - - if (l < 0) l = 0; - if (s < 0) s = 0; - if (l > 1) l = 1; - if (s > 1) s = 1; - while (h < 0) h += 1; - while (h > 1) h -= 1; - - // if saturation is exacly zero, we loose - // information for hue, since it will evaluate - // to zero if converted back from rgb. Setting - // saturation to a very tiny number solves this. - if (s == 0) s = 1e-10; - - // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. - double m2; - if (l <= 0.5) m2 = l*(s+1.0); - else m2 = (l+s)-(l*s); - double m1 = (l*2.0)-m2; - // round the results -- consider moving this into the Color constructor - double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); - double g = (h_to_rgb(m1, m2, h) * 255.0); - double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); - - return SASS_MEMORY_NEW(Color, pstate, r, g, b, a); - } - - Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; - BUILT_IN(hsl) - { - if ( - special_number(Cast(env["$hue"])) || - special_number(Cast(env["$saturation"])) || - special_number(Cast(env["$lightness"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ")" - ); - } - - return hsla_impl(ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - 1.0, - ctx, - pstate); - } - - Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; - BUILT_IN(hsla) - { - if ( - special_number(Cast(env["$hue"])) || - special_number(Cast(env["$saturation"])) || - special_number(Cast(env["$lightness"])) || - special_number(Cast(env["$alpha"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - return hsla_impl(ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - ARGVAL("$alpha"), - ctx, - pstate); - } - - Signature hue_sig = "hue($color)"; - BUILT_IN(hue) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); - } - - Signature saturation_sig = "saturation($color)"; - BUILT_IN(saturation) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); - } - - Signature lightness_sig = "lightness($color)"; - BUILT_IN(lightness) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); - } - - Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; - BUILT_IN(adjust_hue) - { - Color_Ptr rgb_color = ARG("$color", Color); - double degrees = ARGVAL("$degrees"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h + degrees, - hsl_color.s, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature lighten_sig = "lighten($color, $amount)"; - BUILT_IN(lighten) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - //Check lightness is not negative before lighten it - double hslcolorL = hsl_color.l; - if (hslcolorL < 0) { - hslcolorL = 0; - } - - return hsla_impl(hsl_color.h, - hsl_color.s, - hslcolorL + amount, - rgb_color->a(), - ctx, - pstate); - } - - Signature darken_sig = "darken($color, $amount)"; - BUILT_IN(darken) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - //Check lightness if not over 100, before darken it - double hslcolorL = hsl_color.l; - if (hslcolorL > 100) { - hslcolorL = 100; - } - - return hsla_impl(hsl_color.h, - hsl_color.s, - hslcolorL - amount, - rgb_color->a(), - ctx, - pstate); - } - - Signature saturate_sig = "saturate($color, $amount: false)"; - BUILT_IN(saturate) - { - // CSS3 filter function overload: pass literal through directly - if (!Cast(env["$amount"])) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); - } - - double amount = DARG_U_PRCT("$amount"); - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - double hslcolorS = hsl_color.s + amount; - - // Saturation cannot be below 0 or above 100 - if (hslcolorS < 0) { - hslcolorS = 0; - } - if (hslcolorS > 100) { - hslcolorS = 100; - } - - return hsla_impl(hsl_color.h, - hslcolorS, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature desaturate_sig = "desaturate($color, $amount)"; - BUILT_IN(desaturate) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - double hslcolorS = hsl_color.s - amount; - - // Saturation cannot be below 0 or above 100 - if (hslcolorS <= 0) { - hslcolorS = 0; - } - if (hslcolorS > 100) { - hslcolorS = 100; - } - - return hsla_impl(hsl_color.h, - hslcolorS, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature grayscale_sig = "grayscale($color)"; - BUILT_IN(grayscale) - { - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); - } - - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h, - 0.0, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature complement_sig = "complement($color)"; - BUILT_IN(complement) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h - 180.0, - hsl_color.s, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature invert_sig = "invert($color, $weight: 100%)"; - BUILT_IN(invert) - { - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); - } - - double weight = DARG_U_PRCT("$weight"); - Color_Ptr rgb_color = ARG("$color", Color); - Color_Obj inv = SASS_MEMORY_NEW(Color, - pstate, - 255 - rgb_color->r(), - 255 - rgb_color->g(), - 255 - rgb_color->b(), - rgb_color->a()); - return colormix(ctx, pstate, inv, rgb_color, weight); - } - - //////////////////// - // OPACITY FUNCTIONS - //////////////////// - Signature alpha_sig = "alpha($color)"; - Signature opacity_sig = "opacity($color)"; - BUILT_IN(alpha) - { - String_Constant_Ptr ie_kwd = Cast(env["$color"]); - if (ie_kwd) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); - } - - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); - } - - return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); - } - - Signature opacify_sig = "opacify($color, $amount)"; - Signature fade_in_sig = "fade-in($color, $amount)"; - BUILT_IN(opacify) - { - Color_Ptr color = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - double alpha = std::min(color->a() + amount, 1.0); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - - Signature transparentize_sig = "transparentize($color, $amount)"; - Signature fade_out_sig = "fade-out($color, $amount)"; - BUILT_IN(transparentize) - { - Color_Ptr color = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - double alpha = std::max(color->a() - amount, 0.0); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - - //////////////////////// - // OTHER COLOR FUNCTIONS - //////////////////////// - - Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(adjust_color) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); - } - if (rgb) { - double rr = r ? DARG_R_BYTE("$red") : 0; - double gg = g ? DARG_R_BYTE("$green") : 0; - double bb = b ? DARG_R_BYTE("$blue") : 0; - double aa = a ? DARG_R_FACT("$alpha") : 0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r() + rr, - color->g() + gg, - color->b() + bb, - color->a() + aa); - } - if (hsl) { - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - double ss = s ? DARG_R_PRCT("$saturation") : 0; - double ll = l ? DARG_R_PRCT("$lightness") : 0; - double aa = a ? DARG_R_FACT("$alpha") : 0; - return hsla_impl(hsl_struct.h + (h ? h->value() : 0), - hsl_struct.s + ss, - hsl_struct.l + ll, - color->a() + aa, - ctx, - pstate); - } - if (a) { - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - color->a() + (a ? a->value() : 0)); - } - error("not enough arguments for `adjust-color'", pstate, traces); - // unreachable - return color; - } - - Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(scale_color) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); - } - if (rgb) { - double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; - double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; - double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), - color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), - color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), - color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); - } - if (hsl) { - double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; - double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; - double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); - hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); - hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); - double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); - return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); - } - if (a) { - double ascale = (DARG_R_PRCT("$alpha")) / 100.0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); - } - error("not enough arguments for `scale-color'", pstate, traces); - // unreachable - return color; - } - - Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(change_color) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); - } - if (rgb) { - return SASS_MEMORY_NEW(Color, - pstate, - r ? DARG_U_BYTE("$red") : color->r(), - g ? DARG_U_BYTE("$green") : color->g(), - b ? DARG_U_BYTE("$blue") : color->b(), - a ? DARG_U_BYTE("$alpha") : color->a()); - } - if (hsl) { - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - if (h) hsl_struct.h = std::fmod(h->value(), 360.0); - if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); - if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); - double alpha = a ? DARG_U_FACT("$alpha") : color->a(); - return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); - } - if (a) { - double alpha = DARG_U_FACT("$alpha"); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - error("not enough arguments for `change-color'", pstate, traces); - // unreachable - return color; - } - - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - Signature ie_hex_str_sig = "ie-hex-str($color)"; - BUILT_IN(ie_hex_str) - { - Color_Ptr c = ARG("$color", Color); - double r = cap_channel<0xff>(c->r()); - double g = cap_channel<0xff>(c->g()); - double b = cap_channel<0xff>(c->b()); - double a = cap_channel<1> (c->a()) * 255; - - std::stringstream ss; - ss << '#' << std::setw(2) << std::setfill('0'); - ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); - - std::string result(ss.str()); - for (size_t i = 0, L = result.length(); i < L; ++i) { - result[i] = std::toupper(result[i]); - } - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - /////////////////// - // STRING FUNCTIONS - /////////////////// - - Signature unquote_sig = "unquote($string)"; - BUILT_IN(sass_unquote) - { - AST_Node_Obj arg = env["$string"]; - if (String_Quoted_Ptr string_quoted = Cast(arg)) { - String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); - // remember if the string was quoted (color tokens) - result->is_delayed(true); // delay colors - return result; - } - else if (String_Constant_Ptr str = Cast(arg)) { - return str; - } - else if (Expression_Ptr ex = Cast(arg)) { - Sass_Output_Style oldstyle = ctx.c_options.output_style; - ctx.c_options.output_style = SASS_STYLE_NESTED; - std::string val(arg->to_string(ctx.c_options)); - val = Cast(arg) ? "null" : val; - ctx.c_options.output_style = oldstyle; - - deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); - return ex; - } - throw std::runtime_error("Invalid Data Type for unquote"); - } - - Signature quote_sig = "quote($string)"; - BUILT_IN(sass_quote) - { - AST_Node_Obj arg = env["$string"]; - // only set quote mark to true if already a string - if (String_Quoted_Ptr qstr = Cast(arg)) { - qstr->quote_mark('*'); - return qstr; - } - // all other nodes must be converted to a string node - std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); - String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); - result->quote_mark('*'); - return result; - } - - - Signature str_length_sig = "str-length($string)"; - BUILT_IN(str_length) - { - size_t len = std::string::npos; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - len = UTF_8::code_point_count(s->value(), 0, s->value().size()); - - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)len); - } - - Signature str_insert_sig = "str-insert($string, $insert, $index)"; - BUILT_IN(str_insert) - { - std::string str; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - str = s->value(); - str = unquote(str); - String_Constant_Ptr i = ARG("$insert", String_Constant); - std::string ins = i->value(); - ins = unquote(ins); - double index = ARGVAL("$index"); - size_t len = UTF_8::code_point_count(str, 0, str.size()); - - if (index > 0 && index <= len) { - // positive and within string length - str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); - } - else if (index > len) { - // positive and past string length - str += ins; - } - else if (index == 0) { - str = ins + str; - } - else if (std::abs(index) <= len) { - // negative and within string length - index += len + 1; - str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); - } - else { - // negative and past string length - str = ins + str; - } - - if (String_Quoted_Ptr ss = Cast(s)) { - if (ss->quote_mark()) str = quote(str); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - Signature str_index_sig = "str-index($string, $substring)"; - BUILT_IN(str_index) - { - size_t index = std::string::npos; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - String_Constant_Ptr t = ARG("$substring", String_Constant); - std::string str = s->value(); - str = unquote(str); - std::string substr = t->value(); - substr = unquote(substr); - - size_t c_index = str.find(substr); - if(c_index == std::string::npos) { - return SASS_MEMORY_NEW(Null, pstate); - } - index = UTF_8::code_point_count(str, 0, c_index) + 1; - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)index); - } - - Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; - BUILT_IN(str_slice) - { - std::string newstr; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - double start_at = ARGVAL("$start-at"); - double end_at = ARGVAL("$end-at"); - String_Quoted_Ptr ss = Cast(s); - - std::string str = unquote(s->value()); - - size_t size = utf8::distance(str.begin(), str.end()); - - if (!Cast(env["$end-at"])) { - end_at = -1; - } - - if (end_at == 0 || (end_at + size) < 0) { - if (ss && ss->quote_mark()) newstr = quote(""); - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - if (end_at < 0) { - end_at += size + 1; - if (end_at == 0) end_at = 1; - } - if (end_at > size) { end_at = (double)size; } - if (start_at < 0) { - start_at += size + 1; - if (start_at < 0) start_at = 0; - } - else if (start_at == 0) { ++ start_at; } - - if (start_at <= end_at) - { - std::string::iterator start = str.begin(); - utf8::advance(start, start_at - 1, str.end()); - std::string::iterator end = start; - utf8::advance(end, end_at - start_at + 1, str.end()); - newstr = std::string(start, end); - } - if (ss) { - if(ss->quote_mark()) newstr = quote(newstr); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - Signature to_upper_case_sig = "to-upper-case($string)"; - BUILT_IN(to_upper_case) - { - String_Constant_Ptr s = ARG("$string", String_Constant); - std::string str = s->value(); - - for (size_t i = 0, L = str.length(); i < L; ++i) { - if (Sass::Util::isAscii(str[i])) { - str[i] = std::toupper(str[i]); - } - } - - if (String_Quoted_Ptr ss = Cast(s)) { - String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - Signature to_lower_case_sig = "to-lower-case($string)"; - BUILT_IN(to_lower_case) - { - String_Constant_Ptr s = ARG("$string", String_Constant); - std::string str = s->value(); - - for (size_t i = 0, L = str.length(); i < L; ++i) { - if (Sass::Util::isAscii(str[i])) { - str[i] = std::tolower(str[i]); - } - } - - if (String_Quoted_Ptr ss = Cast(s)) { - String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - /////////////////// - // NUMBER FUNCTIONS - /////////////////// - - Signature percentage_sig = "percentage($number)"; - BUILT_IN(percentage) - { - Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); - return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); - } - - Signature round_sig = "round($number)"; - BUILT_IN(round) - { - Number_Obj r = ARGN("$number"); - r->value(Sass::round(r->value(), ctx.c_options.precision)); - r->pstate(pstate); - return r.detach(); - } - - Signature ceil_sig = "ceil($number)"; - BUILT_IN(ceil) - { - Number_Obj r = ARGN("$number"); - r->value(std::ceil(r->value())); - r->pstate(pstate); - return r.detach(); - } - - Signature floor_sig = "floor($number)"; - BUILT_IN(floor) - { - Number_Obj r = ARGN("$number"); - r->value(std::floor(r->value())); - r->pstate(pstate); - return r.detach(); - } - - Signature abs_sig = "abs($number)"; - BUILT_IN(abs) - { - Number_Obj r = ARGN("$number"); - r->value(std::abs(r->value())); - r->pstate(pstate); - return r.detach(); - } - - Signature min_sig = "min($numbers...)"; - BUILT_IN(min) - { - List_Ptr arglist = ARG("$numbers", List); - Number_Obj least = NULL; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); - } - if (least) { - if (*xi < *least) least = xi; - } else least = xi; - } - return least.detach(); - } - - Signature max_sig = "max($numbers...)"; - BUILT_IN(max) - { - List_Ptr arglist = ARG("$numbers", List); - Number_Obj greatest = NULL; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); - } - if (greatest) { - if (*greatest < *xi) greatest = xi; - } else greatest = xi; - } - return greatest.detach(); - } - - Signature random_sig = "random($limit:false)"; - BUILT_IN(random) - { - AST_Node_Obj arg = env["$limit"]; - Value_Ptr v = Cast(arg); - Number_Ptr l = Cast(arg); - Boolean_Ptr b = Cast(arg); - if (l) { - double lv = l->value(); - if (lv < 1) { - stringstream err; - err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; - error(err.str(), pstate, traces); - } - bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; - if (!eq_int) { - stringstream err; - err << "Expected $limit to be an integer but got " << lv << " for `random'"; - error(err.str(), pstate, traces); - } - std::uniform_real_distribution<> distributor(1, lv + 1); - uint_fast32_t distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, (double)distributed); - } - else if (b) { - std::uniform_real_distribution<> distributor(0, 1); - double distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, distributed); - } else if (v) { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); - } else { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); - } - } - - ///////////////// - // LIST FUNCTIONS - ///////////////// - - Signature length_sig = "length($list)"; - BUILT_IN(length) - { - if (Selector_List_Ptr sl = Cast(env["$list"])) { - return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); - } - Expression_Ptr v = ARG("$list", Expression); - if (v->concrete_type() == Expression::MAP) { - Map_Ptr map = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); - } - if (v->concrete_type() == Expression::SELECTOR) { - if (Compound_Selector_Ptr h = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (Selector_List_Ptr ls = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); - } else { - return SASS_MEMORY_NEW(Number, pstate, 1); - } - } - - List_Ptr list = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, - pstate, - (double)(list ? list->size() : 1)); - } - - Signature nth_sig = "nth($list, $n)"; - BUILT_IN(nth) - { - double nr = ARGVAL("$n"); - Map_Ptr m = Cast(env["$list"]); - if (Selector_List_Ptr sl = Cast(env["$list"])) { - size_t len = m ? m->length() : sl->length(); - bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - // return (*sl)[static_cast(index)]; - Listize listize; - return (*sl)[static_cast(index)]->perform(&listize); - } - List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); - // if the argument isn't a list, then wrap it in a singleton list - if (!m && !l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - size_t len = m ? m->length() : l->length(); - bool empty = m ? m->empty() : l->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - - if (m) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(m->keys()[static_cast(index)]); - l->append(m->at(m->keys()[static_cast(index)])); - return l.detach(); - } - else { - Expression_Obj rv = l->value_at_index(static_cast(index)); - rv->set_delayed(false); - return rv.detach(); - } - } - - Signature set_nth_sig = "set-nth($list, $n, $value)"; - BUILT_IN(set_nth) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Number_Obj n = ARG("$n", Number); - Expression_Obj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); - if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - result->append(((i == index) ? v : (*l)[i])); - } - return result; - } - - Signature index_sig = "index($list, $value)"; - BUILT_IN(index) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Expression_Obj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - for (size_t i = 0, L = l->length(); i < L; ++i) { - if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); - } - return SASS_MEMORY_NEW(Null, pstate); - } - - Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; - BUILT_IN(join) - { - Map_Obj m1 = Cast(env["$list1"]); - Map_Obj m2 = Cast(env["$list2"]); - List_Obj l1 = Cast(env["$list1"]); - List_Obj l2 = Cast(env["$list2"]); - String_Constant_Obj sep = ARG("$separator", String_Constant); - enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); - Value* bracketed = ARG("$bracketed", Value); - bool is_bracketed = (l1 ? l1->is_bracketed() : false); - if (!l1) { - l1 = SASS_MEMORY_NEW(List, pstate, 1); - l1->append(ARG("$list1", Expression)); - sep_val = (l2 ? l2->separator() : SASS_SPACE); - is_bracketed = (l2 ? l2->is_bracketed() : false); - } - if (!l2) { - l2 = SASS_MEMORY_NEW(List, pstate, 1); - l2->append(ARG("$list2", Expression)); - } - if (m1) { - l1 = m1->to_list(pstate); - sep_val = SASS_COMMA; - } - if (m2) { - l2 = m2->to_list(pstate); - } - size_t len = l1->length() + l2->length(); - std::string sep_str = unquote(sep->value()); - if (sep_str == "space") sep_val = SASS_SPACE; - else if (sep_str == "comma") sep_val = SASS_COMMA; - else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - String_Constant_Obj bracketed_as_str = Cast(bracketed); - bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; - if (!bracketed_is_auto) { - is_bracketed = !bracketed->is_false(); - } - List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); - result->concat(l1); - result->concat(l2); - return result.detach(); - } - - Signature append_sig = "append($list, $val, $separator: auto)"; - BUILT_IN(append) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Expression_Obj v = ARG("$val", Expression); - if (Selector_List_Ptr sl = Cast(env["$list"])) { - Listize listize; - l = Cast(sl->perform(&listize)); - } - String_Constant_Obj sep = ARG("$separator", String_Constant); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - List_Ptr result = SASS_MEMORY_COPY(l); - std::string sep_str(unquote(sep->value())); - if (sep_str != "auto") { // check default first - if (sep_str == "space") result->separator(SASS_SPACE); - else if (sep_str == "comma") result->separator(SASS_COMMA); - else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - } - if (l->is_arglist()) { - result->append(SASS_MEMORY_NEW(Argument, - v->pstate(), - v, - "", - false, - false)); - - } else { - result->append(v); - } - return result; - } - - Signature zip_sig = "zip($lists...)"; - BUILT_IN(zip) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); - size_t shortest = 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - List_Obj ith = Cast(arglist->value_at_index(i)); - Map_Obj mith = Cast(arglist->value_at_index(i)); - if (!ith) { - if (mith) { - ith = mith->to_list(pstate); - } else { - ith = SASS_MEMORY_NEW(List, pstate, 1); - ith->append(arglist->value_at_index(i)); - } - if (arglist->is_arglist()) { - Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX - arg->value(ith); - } else { - (*arglist)[i] = ith; - } - } - shortest = (i ? std::min(shortest, ith->length()) : ith->length()); - } - List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); - size_t L = arglist->length(); - for (size_t i = 0; i < shortest; ++i) { - List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); - for (size_t j = 0; j < L; ++j) { - zipper->append(Cast(arglist->value_at_index(j))->at(i)); - } - zippers->append(zipper); - } - return zippers; - } - - Signature list_separator_sig = "list_separator($list)"; - BUILT_IN(list_separator) - { - List_Obj l = Cast(env["$list"]); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l->separator() == SASS_COMMA ? "comma" : "space"); - } - - ///////////////// - // MAP FUNCTIONS - ///////////////// - - Signature map_get_sig = "map-get($map, $key)"; - BUILT_IN(map_get) - { - // leaks for "map-get((), foo)" if not Obj - // investigate why this is (unexpected) - Map_Obj m = ARGM("$map", Map, ctx); - Expression_Obj v = ARG("$key", Expression); - try { - Expression_Obj val = m->at(v); - if (!val) return SASS_MEMORY_NEW(Null, pstate); - val->set_delayed(false); - return val.detach(); - } catch (const std::out_of_range&) { - return SASS_MEMORY_NEW(Null, pstate); - } - catch (...) { throw; } - } - - Signature map_has_key_sig = "map-has-key($map, $key)"; - BUILT_IN(map_has_key) - { - Map_Obj m = ARGM("$map", Map, ctx); - Expression_Obj v = ARG("$key", Expression); - return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); - } - - Signature map_keys_sig = "map-keys($map)"; - BUILT_IN(map_keys) - { - Map_Obj m = ARGM("$map", Map, ctx); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(key); - } - return result; - } - - Signature map_values_sig = "map-values($map)"; - BUILT_IN(map_values) - { - Map_Obj m = ARGM("$map", Map, ctx); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(m->at(key)); - } - return result; - } - - Signature map_merge_sig = "map-merge($map1, $map2)"; - BUILT_IN(map_merge) - { - Map_Obj m1 = ARGM("$map1", Map, ctx); - Map_Obj m2 = ARGM("$map2", Map, ctx); - - size_t len = m1->length() + m2->length(); - Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); - // concat not implemented for maps - *result += m1; - *result += m2; - return result; - } - - Signature map_remove_sig = "map-remove($map, $keys...)"; - BUILT_IN(map_remove) - { - bool remove; - Map_Obj m = ARGM("$map", Map, ctx); - List_Obj arglist = ARG("$keys", List); - Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, 1); - for (auto key : m->keys()) { - remove = false; - for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { - remove = Operators::eq(key, arglist->value_at_index(j)); - } - if (!remove) *result << std::make_pair(key, m->at(key)); - } - return result; - } - - Signature keywords_sig = "keywords($args)"; - BUILT_IN(keywords) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy - Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); - for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { - Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX - std::string name = std::string(arg->name()); - name = name.erase(0, 1); // sanitize name (remove dollar sign) - *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, - pstate, name), - arg->value()); - } - return result.detach(); - } - - ////////////////////////// - // INTROSPECTION FUNCTIONS - ////////////////////////// - - Signature type_of_sig = "type-of($value)"; - BUILT_IN(type_of) - { - Expression_Ptr v = ARG("$value", Expression); - return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); - } - - Signature unit_sig = "unit($number)"; - BUILT_IN(unit) - { - Number_Obj arg = ARGN("$number"); - std::string str(quote(arg->unit(), '"')); - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - Signature unitless_sig = "unitless($number)"; - BUILT_IN(unitless) - { - Number_Obj arg = ARGN("$number"); - bool unitless = arg->is_unitless(); - return SASS_MEMORY_NEW(Boolean, pstate, unitless); - } - - Signature comparable_sig = "comparable($number-1, $number-2)"; - BUILT_IN(comparable) - { - Number_Obj n1 = ARGN("$number-1"); - Number_Obj n2 = ARGN("$number-2"); - if (n1->is_unitless() || n2->is_unitless()) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - // normalize into main units - n1->normalize(); n2->normalize(); - Units &lhs_unit = *n1, &rhs_unit = *n2; - bool is_comparable = (lhs_unit == rhs_unit); - return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); - } - - Signature variable_exists_sig = "variable-exists($name)"; - BUILT_IN(variable_exists) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature global_variable_exists_sig = "global-variable-exists($name)"; - BUILT_IN(global_variable_exists) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature function_exists_sig = "function-exists($name)"; - BUILT_IN(function_exists) - { - String_Constant_Ptr ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); - } - - std::string name = Util::normalize_underscores(unquote(ss->value())); - - if(d_env.has_global(name+"[f]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature mixin_exists_sig = "mixin-exists($name)"; - BUILT_IN(mixin_exists) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global(s+"[m]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature feature_exists_sig = "feature-exists($name)"; - BUILT_IN(feature_exists) - { - std::string s = unquote(ARG("$name", String_Constant)->value()); - - if(features.find(s) == features.end()) { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - } - - Signature call_sig = "call($name, $args...)"; - BUILT_IN(call) - { - std::string name; - Function_Ptr ff = Cast(env["$name"]); - String_Constant_Ptr ss = Cast(env["$name"]); - - if (ss) { - name = Util::normalize_underscores(unquote(ss->value())); - std::cerr << "DEPRECATION WARNING: "; - std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; - std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl; - std::cerr << std::endl; - } else if (ff) { - name = ff->name(); - } - - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); - - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - // std::string full_name(name + "[f]"); - // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; - // Parameters_Ptr params = def ? def->parameters() : 0; - // size_t param_size = params ? params->length() : 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj expr = arglist->value_at_index(i); - // if (params && params->has_rest_parameter()) { - // Parameter_Obj p = param_size > i ? (*params)[i] : 0; - // List_Ptr list = Cast(expr); - // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; - // } - if (arglist->is_arglist()) { - Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX - args->append(SASS_MEMORY_NEW(Argument, - pstate, - expr, - arg ? arg->name() : "", - arg ? arg->is_rest_argument() : false, - arg ? arg->is_keyword_argument() : false)); - } else { - args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); - } - } - Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); - Expand expand(ctx, &d_env, &selector_stack); - func->via_call(true); // calc invoke is allowed - if (ff) func->func(ff); - return func->perform(&expand.eval); - } - - //////////////////// - // BOOLEAN FUNCTIONS - //////////////////// - - Signature not_sig = "not($value)"; - BUILT_IN(sass_not) - { - return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); - } - - Signature if_sig = "if($condition, $if-true, $if-false)"; - // BUILT_IN(sass_if) - // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } - BUILT_IN(sass_if) - { - Expand expand(ctx, &d_env, &selector_stack); - Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); - bool is_true = !cond->is_false(); - Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); - res = res->perform(&expand.eval); - res->set_delayed(false); // clone? - return res.detach(); - } - - ////////////////////////// - // MISCELLANEOUS FUNCTIONS - ////////////////////////// - - // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) - // unquoted_string(value.to_sass) - - Signature inspect_sig = "inspect($value)"; - BUILT_IN(inspect) - { - Expression_Ptr v = ARG("$value", Expression); - if (v->concrete_type() == Expression::NULL_VAL) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); - } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); - } else if (v->concrete_type() == Expression::STRING) { - return v; - } else { - // ToDo: fix to_sass for nested parentheses - Sass_Output_Style old_style; - old_style = ctx.c_options.output_style; - ctx.c_options.output_style = TO_SASS; - Emitter emitter(ctx.c_options); - Inspect i(emitter); - i.in_declaration = false; - v->perform(&i); - ctx.c_options.output_style = old_style; - return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); - } - // return v; - } - Signature selector_nest_sig = "selector-nest($selectors...)"; - BUILT_IN(selector_nest) - { - List_Ptr arglist = ARG("$selectors", List); - - // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); - - // Parse args into vector of selectors - std::vector parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Obj str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - std::vector::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector exploded; - selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); - selector_stack.pop_back(); - for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { - exploded.push_back((*rv)[m]); - } - result->elements(exploded); - } - - Listize listize; - return result->perform(&listize); - } - - Signature selector_append_sig = "selector-append($selectors...)"; - BUILT_IN(selector_append) - { - List_Ptr arglist = ARG("$selectors", List); - - // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); - - // Parse args into vector of selectors - std::vector parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-append'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - std::vector::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector newElements; - - // For every COMPLEX_SELECTOR in `result` - // For every COMPLEX_SELECTOR in `child` - // let parentSeqClone equal a copy of result->elements[i] - // let childSeq equal child->elements[j] - // Append all of childSeq head elements into parentSeqClone - // Set the innermost tail of parentSeqClone, to childSeq's tail - // Replace result->elements with newElements - for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { - for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { - Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); - Complex_Selector_Obj childSeq = (*child)[j]; - Complex_Selector_Obj base = childSeq->tail(); - - // Must be a simple sequence - if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { - std::string msg("Can't append \""); - msg += childSeq->to_string(); - msg += "\" to \""; - msg += parentSeqClone->to_string(); - msg += "\" for `selector-append'"; - error(msg, pstate, traces); - } - - // Cannot be a Universal selector - Element_Selector_Obj pType = Cast(childSeq->head()->first()); - if(pType && pType->name() == "*") { - std::string msg("Can't append \""); - msg += childSeq->to_string(); - msg += "\" to \""; - msg += parentSeqClone->to_string(); - msg += "\" for `selector-append'"; - error(msg, pstate, traces); - } - - // TODO: Add check for namespace stuff - - // append any selectors in childSeq's head - parentSeqClone->innermost()->head()->concat(base->head()); - - // Set parentSeqClone new tail - parentSeqClone->innermost()->tail( base->tail() ); - - newElements.push_back(parentSeqClone); - } - } - - result->elements(newElements); - } - - Listize listize; - return result->perform(&listize); - } - - Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; - BUILT_IN(selector_unify) - { - Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); - Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); - - Selector_List_Obj result = selector1->unify_with(selector2); - Listize listize; - return result->perform(&listize); - } - - Signature simple_selectors_sig = "simple-selectors($selector)"; - BUILT_IN(simple_selectors) - { - Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); - - List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); - - for (size_t i = 0, L = sel->length(); i < L; ++i) { - Simple_Selector_Obj ss = (*sel)[i]; - std::string ss_string = ss->to_string() ; - - l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); - } - - return l; - } - - Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; - BUILT_IN(selector_extend) - { - Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); - Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); - - Subset_Map subset_map; - extender->populate_extends(extendee, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, false); - - Listize listize; - return result->perform(&listize); - } - - Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; - BUILT_IN(selector_replace) - { - Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); - Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); - Subset_Map subset_map; - replacement->populate_extends(original, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, true); - - Listize listize; - return result->perform(&listize); - } - - Signature selector_parse_sig = "selector-parse($selector)"; - BUILT_IN(selector_parse) - { - Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - - Listize listize; - return sel->perform(&listize); - } - - Signature is_superselector_sig = "is-superselector($super, $sub)"; - BUILT_IN(is_superselector) - { - Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); - Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); - bool result = sel_sup->is_superselector_of(sel_sub); - return SASS_MEMORY_NEW(Boolean, pstate, result); - } - - Signature unique_id_sig = "unique-id()"; - BUILT_IN(unique_id) - { - std::stringstream ss; - std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 - uint_fast32_t distributed = static_cast(distributor(rand)); - ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; - return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); - } - - Signature is_bracketed_sig = "is-bracketed($list)"; - BUILT_IN(is_bracketed) - { - Value_Obj value = ARG("$list", Value); - List_Obj list = Cast(value); - return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); - } - - Signature content_exists_sig = "content-exists()"; - BUILT_IN(content_exists) - { - if (!d_env.has_global("is_in_mixin")) { - error("Cannot call content-exists() except within a mixin.", pstate, traces); - } - return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); - } - - Signature get_function_sig = "get-function($name, $css: false)"; - BUILT_IN(get_function) - { - String_Constant_Ptr ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); - } - - std::string name = Util::normalize_underscores(unquote(ss->value())); - std::string full_name = name + "[f]"; - - Boolean_Obj css = ARG("$css", Boolean); - if (!css->is_false()) { - Definition_Ptr def = SASS_MEMORY_NEW(Definition, - pstate, - name, - SASS_MEMORY_NEW(Parameters, pstate), - SASS_MEMORY_NEW(Block, pstate, 0, false), - Definition::FUNCTION); - return SASS_MEMORY_NEW(Function, pstate, def, true); - } - - - if (!d_env.has_global(full_name)) { - error("Function not found: " + name, pstate, traces); - } - - Definition_Ptr def = Cast(d_env[full_name]); - return SASS_MEMORY_NEW(Function, pstate, def, false); - } - } -} diff --git a/src/libsass/implementations.md b/src/libsass/implementations.md deleted file mode 100644 index 5239adcde..000000000 --- a/src/libsass/implementations.md +++ /dev/null @@ -1,65 +0,0 @@ -There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. - -### C -* [sassc](https://github.com/hcatlin/sassc) - -### Crystal -* [sass.cr](https://github.com/straight-shoota/sass.cr) - -### Elixir -* [sass.ex](https://github.com/scottdavis/sass.ex) - -### Go -* [go-libsass](https://github.com/wellington/go-libsass) -* [go_sass](https://github.com/suapapa/go_sass) -* [go-sass](https://github.com/SamWhited/go-sass) - -### Haskell -* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) -* [hSass](https://github.com/jakubfijalkowski/hsass) - -### Java -* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) -* [jsass](https://github.com/bit3/jsass) - -### JavaScript -* [sass.js](https://github.com/medialize/sass.js) - -### Lua -* [lua-sass](https://github.com/craigbarnes/lua-sass) - -### .NET -* [libsass-net](https://github.com/darrenkopp/libsass-net) -* [NSass](https://github.com/TBAPI-0KA/NSass) -* [Sass.Net](https://github.com/andyalm/Sass.Net) -* [SharpScss](https://github.com/xoofx/SharpScss) -* [LibSassHost](https://github.com/Taritsyn/LibSassHost) - -### Nim -* [nim-sass](https://github.com/zacharycarter/nim-sass) - -### node.js -* [node-sass](https://github.com/sass/node-sass) - -### Perl -* [CSS::Sass](https://github.com/caldwell/CSS-Sass) -* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) - -### PHP -* [sassphp](https://github.com/sensational/sassphp) -* [php-sass](https://github.com/lesstif/php-sass) - -### Python -* [libsass-python](https://github.com/dahlia/libsass-python) -* [SassPython](https://github.com/marianoguerra/SassPython) -* [pylibsass](https://github.com/rsenk330/pylibsass) -* [python-scss](https://github.com/pistolero/python-scss) - -### Ruby -* [sassruby](https://github.com/hcatlin/sassruby) - -### Scala -* [Sass-Scala](https://github.com/kkung/Sass-Scala) - -### Tcl -* [tclsass](https://github.com/flightaware/tclsass) diff --git a/src/libsass/inspect.cpp b/src/libsass/inspect.cpp deleted file mode 100644 index b4a66fab8..000000000 --- a/src/libsass/inspect.cpp +++ /dev/null @@ -1,1138 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include -#include - -#include "ast.hpp" -#include "inspect.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "color_maps.hpp" -#include "utf8/checked.h" - -namespace Sass { - - Inspect::Inspect(const Emitter& emi) - : Emitter(emi) - { } - Inspect::~Inspect() { } - - // statements - void Inspect::operator()(Block_Ptr block) - { - if (!block->is_root()) { - add_open_mapping(block); - append_scope_opener(); - } - if (output_style() == NESTED) indentation += block->tabs(); - for (size_t i = 0, L = block->length(); i < L; ++i) { - (*block)[i]->perform(this); - } - if (output_style() == NESTED) indentation -= block->tabs(); - if (!block->is_root()) { - append_scope_closer(); - add_close_mapping(block); - } - - } - - void Inspect::operator()(Ruleset_Ptr ruleset) - { - if (ruleset->selector()) { - opt.in_selector = true; - ruleset->selector()->perform(this); - opt.in_selector = false; - } - if (ruleset->block()) { - ruleset->block()->perform(this); - } - } - - void Inspect::operator()(Keyframe_Rule_Ptr rule) - { - if (rule->name()) rule->name()->perform(this); - if (rule->block()) rule->block()->perform(this); - } - - void Inspect::operator()(Bubble_Ptr bubble) - { - append_indentation(); - append_token("::BUBBLE", bubble); - append_scope_opener(); - bubble->node()->perform(this); - append_scope_closer(); - } - - void Inspect::operator()(Media_Block_Ptr media_block) - { - append_indentation(); - append_token("@media", media_block); - append_mandatory_space(); - in_media_block = true; - media_block->media_queries()->perform(this); - in_media_block = false; - media_block->block()->perform(this); - } - - void Inspect::operator()(Supports_Block_Ptr feature_block) - { - append_indentation(); - append_token("@supports", feature_block); - append_mandatory_space(); - feature_block->condition()->perform(this); - feature_block->block()->perform(this); - } - - void Inspect::operator()(At_Root_Block_Ptr at_root_block) - { - append_indentation(); - append_token("@at-root ", at_root_block); - append_mandatory_space(); - if(at_root_block->expression()) at_root_block->expression()->perform(this); - if(at_root_block->block()) at_root_block->block()->perform(this); - } - - void Inspect::operator()(Directive_Ptr at_rule) - { - append_indentation(); - append_token(at_rule->keyword(), at_rule); - if (at_rule->selector()) { - append_mandatory_space(); - bool was_wrapped = in_wrapped; - in_wrapped = true; - at_rule->selector()->perform(this); - in_wrapped = was_wrapped; - } - if (at_rule->value()) { - append_mandatory_space(); - at_rule->value()->perform(this); - } - if (at_rule->block()) { - at_rule->block()->perform(this); - } - else { - append_delimiter(); - } - } - - void Inspect::operator()(Declaration_Ptr dec) - { - if (dec->value()->concrete_type() == Expression::NULL_VAL) return; - bool was_decl = in_declaration; - in_declaration = true; - LOCAL_FLAG(in_custom_property, dec->is_custom_property()); - - if (output_style() == NESTED) - indentation += dec->tabs(); - append_indentation(); - if (dec->property()) - dec->property()->perform(this); - append_colon_separator(); - - if (dec->value()->concrete_type() == Expression::SELECTOR) { - Listize listize; - Expression_Obj ls = dec->value()->perform(&listize); - ls->perform(this); - } else { - dec->value()->perform(this); - } - - if (dec->is_important()) { - append_optional_space(); - append_string("!important"); - } - append_delimiter(); - if (output_style() == NESTED) - indentation -= dec->tabs(); - in_declaration = was_decl; - } - - void Inspect::operator()(Assignment_Ptr assn) - { - append_token(assn->variable(), assn); - append_colon_separator(); - assn->value()->perform(this); - if (assn->is_default()) { - append_optional_space(); - append_string("!default"); - } - append_delimiter(); - } - - void Inspect::operator()(Import_Ptr import) - { - if (!import->urls().empty()) { - append_token("@import", import); - append_mandatory_space(); - - import->urls().front()->perform(this); - if (import->urls().size() == 1) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); - } - } - append_delimiter(); - for (size_t i = 1, S = import->urls().size(); i < S; ++i) { - append_mandatory_linefeed(); - append_token("@import", import); - append_mandatory_space(); - - import->urls()[i]->perform(this); - if (import->urls().size() - 1 == i) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); - } - } - append_delimiter(); - } - } - } - - void Inspect::operator()(Import_Stub_Ptr import) - { - append_indentation(); - append_token("@import", import); - append_mandatory_space(); - append_string(import->imp_path()); - append_delimiter(); - } - - void Inspect::operator()(Warning_Ptr warning) - { - append_indentation(); - append_token("@warn", warning); - append_mandatory_space(); - warning->message()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Error_Ptr error) - { - append_indentation(); - append_token("@error", error); - append_mandatory_space(); - error->message()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Debug_Ptr debug) - { - append_indentation(); - append_token("@debug", debug); - append_mandatory_space(); - debug->value()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Comment_Ptr comment) - { - in_comment = true; - comment->text()->perform(this); - in_comment = false; - } - - void Inspect::operator()(If_Ptr cond) - { - append_indentation(); - append_token("@if", cond); - append_mandatory_space(); - cond->predicate()->perform(this); - cond->block()->perform(this); - if (cond->alternative()) { - append_optional_linefeed(); - append_indentation(); - append_string("else"); - cond->alternative()->perform(this); - } - } - - void Inspect::operator()(For_Ptr loop) - { - append_indentation(); - append_token("@for", loop); - append_mandatory_space(); - append_string(loop->variable()); - append_string(" from "); - loop->lower_bound()->perform(this); - append_string(loop->is_inclusive() ? " through " : " to "); - loop->upper_bound()->perform(this); - loop->block()->perform(this); - } - - void Inspect::operator()(Each_Ptr loop) - { - append_indentation(); - append_token("@each", loop); - append_mandatory_space(); - append_string(loop->variables()[0]); - for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { - append_comma_separator(); - append_string(loop->variables()[i]); - } - append_string(" in "); - loop->list()->perform(this); - loop->block()->perform(this); - } - - void Inspect::operator()(While_Ptr loop) - { - append_indentation(); - append_token("@while", loop); - append_mandatory_space(); - loop->predicate()->perform(this); - loop->block()->perform(this); - } - - void Inspect::operator()(Return_Ptr ret) - { - append_indentation(); - append_token("@return", ret); - append_mandatory_space(); - ret->value()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Extension_Ptr extend) - { - append_indentation(); - append_token("@extend", extend); - append_mandatory_space(); - extend->selector()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Definition_Ptr def) - { - append_indentation(); - if (def->type() == Definition::MIXIN) { - append_token("@mixin", def); - append_mandatory_space(); - } else { - append_token("@function", def); - append_mandatory_space(); - } - append_string(def->name()); - def->parameters()->perform(this); - def->block()->perform(this); - } - - void Inspect::operator()(Mixin_Call_Ptr call) - { - append_indentation(); - append_token("@include", call); - append_mandatory_space(); - append_string(call->name()); - if (call->arguments()) { - call->arguments()->perform(this); - } - if (call->block()) { - append_optional_space(); - call->block()->perform(this); - } - if (!call->block()) append_delimiter(); - } - - void Inspect::operator()(Content_Ptr content) - { - append_indentation(); - append_token("@content", content); - append_delimiter(); - } - - void Inspect::operator()(Map_Ptr map) - { - if (output_style() == TO_SASS && map->empty()) { - append_string("()"); - return; - } - if (map->empty()) return; - if (map->is_invisible()) return; - bool items_output = false; - append_string("("); - for (auto key : map->keys()) { - if (items_output) append_comma_separator(); - key->perform(this); - append_colon_separator(); - LOCAL_FLAG(in_space_array, true); - LOCAL_FLAG(in_comma_array, true); - map->at(key)->perform(this); - items_output = true; - } - append_string(")"); - } - - std::string Inspect::lbracket(List_Ptr list) { - return list->is_bracketed() ? "[" : "("; - } - - std::string Inspect::rbracket(List_Ptr list) { - return list->is_bracketed() ? "]" : ")"; - } - - void Inspect::operator()(List_Ptr list) - { - if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { - append_string(lbracket(list)); - append_string(rbracket(list)); - return; - } - std::string sep(list->separator() == SASS_SPACE ? " " : ","); - if ((output_style() != COMPRESSED) && sep == ",") sep += " "; - else if (in_media_block && sep != " ") sep += " "; // verified - if (list->empty()) return; - bool items_output = false; - - bool was_space_array = in_space_array; - bool was_comma_array = in_comma_array; - // if the list is bracketed, always include the left bracket - if (list->is_bracketed()) { - append_string(lbracket(list)); - } - // probably ruby sass eqivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(lbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(lbracket(list)); - } - - if (list->separator() == SASS_SPACE) in_space_array = true; - else if (list->separator() == SASS_COMMA) in_comma_array = true; - - for (size_t i = 0, L = list->size(); i < L; ++i) { - if (list->separator() == SASS_HASH) - { sep[0] = i % 2 ? ':' : ','; } - Expression_Obj list_item = list->at(i); - if (output_style() != TO_SASS) { - if (list_item->is_invisible()) { - // this fixes an issue with "" in a list - if (!Cast(list_item)) { - continue; - } - } - } - if (items_output) { - append_string(sep); - } - if (items_output && sep != " ") - append_optional_space(); - list_item->perform(this); - items_output = true; - } - - in_comma_array = was_comma_array; - in_space_array = was_space_array; - - // if the list is bracketed, always include the right bracket - if (list->is_bracketed()) { - if (list->separator() == SASS_COMMA && list->size() == 1) { - append_string(","); - } - append_string(rbracket(list)); - } - // probably ruby sass eqivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(","); - append_string(rbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(rbracket(list)); - } - - } - - void Inspect::operator()(Binary_Expression_Ptr expr) - { - expr->left()->perform(this); - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_before - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - - )) append_string(" "); - switch (expr->optype()) { - case Sass_OP::AND: append_string("&&"); break; - case Sass_OP::OR: append_string("||"); break; - case Sass_OP::EQ: append_string("=="); break; - case Sass_OP::NEQ: append_string("!="); break; - case Sass_OP::GT: append_string(">"); break; - case Sass_OP::GTE: append_string(">="); break; - case Sass_OP::LT: append_string("<"); break; - case Sass_OP::LTE: append_string("<="); break; - case Sass_OP::ADD: append_string("+"); break; - case Sass_OP::SUB: append_string("-"); break; - case Sass_OP::MUL: append_string("*"); break; - case Sass_OP::DIV: append_string("/"); break; - case Sass_OP::MOD: append_string("%"); break; - default: break; // shouldn't get here - } - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_after - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - )) append_string(" "); - expr->right()->perform(this); - } - - void Inspect::operator()(Unary_Expression_Ptr expr) - { - if (expr->optype() == Unary_Expression::PLUS) append_string("+"); - else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); - else append_string("-"); - expr->operand()->perform(this); - } - - void Inspect::operator()(Function_Call_Ptr call) - { - append_token(call->name(), call); - call->arguments()->perform(this); - } - - void Inspect::operator()(Function_Call_Schema_Ptr call) - { - call->name()->perform(this); - call->arguments()->perform(this); - } - - void Inspect::operator()(Variable_Ptr var) - { - append_token(var->name(), var); - } - - void Inspect::operator()(Number_Ptr n) - { - - std::string res; - - // reduce units - n->reduce(); - - // check if the fractional part of the value equals to zero - // neat trick from http://stackoverflow.com/a/1521682/1550314 - // double int_part; bool is_int = modf(value, &int_part) == 0.0; - - // this all cannot be done with one run only, since fixed - // output differs from normal output and regular output - // can contain scientific notation which we do not want! - - // first sample - std::stringstream ss; - ss.precision(12); - ss << n->value(); - - // check if we got scientific notation in result - if (ss.str().find_first_of("e") != std::string::npos) { - ss.clear(); ss.str(std::string()); - ss.precision(std::max(12, opt.precision)); - ss << std::fixed << n->value(); - } - - std::string tmp = ss.str(); - size_t pos_point = tmp.find_first_of(".,"); - size_t pos_fract = tmp.find_last_not_of("0"); - bool is_int = pos_point == pos_fract || - pos_point == std::string::npos; - - // reset stream for another run - ss.clear(); ss.str(std::string()); - - // take a shortcut for integers - if (is_int) - { - ss.precision(0); - ss << std::fixed << n->value(); - res = std::string(ss.str()); - } - // process floats - else - { - // do we have have too much precision? - if (pos_fract < opt.precision + pos_point) - { ss.precision((int)(pos_fract - pos_point)); } - else { ss.precision(opt.precision); } - // round value again - ss << std::fixed << n->value(); - res = std::string(ss.str()); - // maybe we truncated up to decimal point - size_t pos = res.find_last_not_of("0"); - // handle case where we have a "0" - if (pos == std::string::npos) { - res = "0.0"; - } else { - bool at_dec_point = res[pos] == '.' || - res[pos] == ','; - // don't leave a blank point - if (at_dec_point) ++ pos; - res.resize (pos + 1); - } - } - - // some final cosmetics - if (res == "0.0") res = "0"; - else if (res == "") res = "0"; - else if (res == "-0") res = "0"; - else if (res == "-0.0") res = "0"; - else if (opt.output_style == COMPRESSED) - { - // check if handling negative nr - size_t off = res[0] == '-' ? 1 : 0; - // remove leading zero from floating point in compressed mode - if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); - } - - // add unit now - res += n->unit(); - - // output the final token - append_token(res, n); - } - - // helper function for serializing colors - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - void Inspect::operator()(Color_Ptr c) - { - // output the final token - std::stringstream ss; - - // original color name - // maybe an unknown token - std::string name = c->disp(); - - if (opt.in_selector && name != "") { - append_token(name, c); - return; - } - - // resolved color - std::string res_name = name; - - double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); - double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); - double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); - double a = cap_channel<1> (c->a()); - - // get color from given name (if one was given at all) - if (name != "" && name_to_color(name)) { - Color_Ptr_Const n = name_to_color(name); - r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); - g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); - b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); - a = cap_channel<1> (n->a()); - } - // otherwise get the possible resolved color name - else { - double numval = r * 0x10000 + g * 0x100 + b; - if (color_to_name(numval)) - res_name = color_to_name(numval); - } - - std::stringstream hexlet; - // dart sass compressed all colors in regular css always - // ruby sass and libsass does it only when not delayed - // since color math is going to be removed, this can go too - bool compressed = opt.output_style == COMPRESSED; - hexlet << '#' << std::setw(1) << std::setfill('0'); - // create a short color hexlet if there is any need for it - if (compressed && is_color_doublet(r, g, b) && a == 1) { - hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); - hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); - hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); - } else { - hexlet << std::hex << std::setw(2) << static_cast(r); - hexlet << std::hex << std::setw(2) << static_cast(g); - hexlet << std::hex << std::setw(2) << static_cast(b); - } - - if (compressed && !c->is_delayed()) name = ""; - if (opt.output_style == INSPECT && a >= 1) { - append_token(hexlet.str(), c); - return; - } - - // retain the originally specified color definition if unchanged - if (name != "") { - ss << name; - } - else if (a >= 1) { - if (res_name != "") { - if (compressed && hexlet.str().size() < res_name.size()) { - ss << hexlet.str(); - } else { - ss << res_name; - } - } - else { - ss << hexlet.str(); - } - } - else { - ss << "rgba("; - ss << static_cast(r) << ","; - if (!compressed) ss << " "; - ss << static_cast(g) << ","; - if (!compressed) ss << " "; - ss << static_cast(b) << ","; - if (!compressed) ss << " "; - ss << a << ')'; - } - - append_token(ss.str(), c); - - } - - void Inspect::operator()(Boolean_Ptr b) - { - // output the final token - append_token(b->value() ? "true" : "false", b); - } - - void Inspect::operator()(String_Schema_Ptr ss) - { - // Evaluation should turn these into String_Constants, - // so this method is only for inspection purposes. - for (size_t i = 0, L = ss->length(); i < L; ++i) { - if ((*ss)[i]->is_interpolant()) append_string("#{"); - (*ss)[i]->perform(this); - if ((*ss)[i]->is_interpolant()) append_string("}"); - } - } - - void Inspect::operator()(String_Constant_Ptr s) - { - append_token(s->value(), s); - } - - void Inspect::operator()(String_Quoted_Ptr s) - { - if (const char q = s->quote_mark()) { - append_token(quote(s->value(), q), s); - } else { - append_token(s->value(), s); - } - } - - void Inspect::operator()(Custom_Error_Ptr e) - { - append_token(e->message(), e); - } - - void Inspect::operator()(Custom_Warning_Ptr w) - { - append_token(w->message(), w); - } - - void Inspect::operator()(Supports_Operator_Ptr so) - { - - if (so->needs_parens(so->left())) append_string("("); - so->left()->perform(this); - if (so->needs_parens(so->left())) append_string(")"); - - if (so->operand() == Supports_Operator::AND) { - append_mandatory_space(); - append_token("and", so); - append_mandatory_space(); - } else if (so->operand() == Supports_Operator::OR) { - append_mandatory_space(); - append_token("or", so); - append_mandatory_space(); - } - - if (so->needs_parens(so->right())) append_string("("); - so->right()->perform(this); - if (so->needs_parens(so->right())) append_string(")"); - } - - void Inspect::operator()(Supports_Negation_Ptr sn) - { - append_token("not", sn); - append_mandatory_space(); - if (sn->needs_parens(sn->condition())) append_string("("); - sn->condition()->perform(this); - if (sn->needs_parens(sn->condition())) append_string(")"); - } - - void Inspect::operator()(Supports_Declaration_Ptr sd) - { - append_string("("); - sd->feature()->perform(this); - append_string(": "); - sd->value()->perform(this); - append_string(")"); - } - - void Inspect::operator()(Supports_Interpolation_Ptr sd) - { - sd->value()->perform(this); - } - - void Inspect::operator()(Media_Query_Ptr mq) - { - size_t i = 0; - if (mq->media_type()) { - if (mq->is_negated()) append_string("not "); - else if (mq->is_restricted()) append_string("only "); - mq->media_type()->perform(this); - } - else { - (*mq)[i++]->perform(this); - } - for (size_t L = mq->length(); i < L; ++i) { - append_string(" and "); - (*mq)[i]->perform(this); - } - } - - void Inspect::operator()(Media_Query_Expression_Ptr mqe) - { - if (mqe->is_interpolated()) { - mqe->feature()->perform(this); - } - else { - append_string("("); - mqe->feature()->perform(this); - if (mqe->value()) { - append_string(": "); // verified - mqe->value()->perform(this); - } - append_string(")"); - } - } - - void Inspect::operator()(At_Root_Query_Ptr ae) - { - if (ae->feature()) { - append_string("("); - ae->feature()->perform(this); - if (ae->value()) { - append_colon_separator(); - ae->value()->perform(this); - } - append_string(")"); - } - } - - void Inspect::operator()(Function_Ptr f) - { - append_token("get-function", f); - append_string("("); - append_string(quote(f->name())); - append_string(")"); - } - - void Inspect::operator()(Null_Ptr n) - { - // output the final token - append_token("null", n); - } - - // parameters and arguments - void Inspect::operator()(Parameter_Ptr p) - { - append_token(p->name(), p); - if (p->default_value()) { - append_colon_separator(); - p->default_value()->perform(this); - } - else if (p->is_rest_parameter()) { - append_string("..."); - } - } - - void Inspect::operator()(Parameters_Ptr p) - { - append_string("("); - if (!p->empty()) { - (*p)[0]->perform(this); - for (size_t i = 1, L = p->length(); i < L; ++i) { - append_comma_separator(); - (*p)[i]->perform(this); - } - } - append_string(")"); - } - - void Inspect::operator()(Argument_Ptr a) - { - if (!a->name().empty()) { - append_token(a->name(), a); - append_colon_separator(); - } - if (!a->value()) return; - // Special case: argument nulls can be ignored - if (a->value()->concrete_type() == Expression::NULL_VAL) { - return; - } - if (a->value()->concrete_type() == Expression::STRING) { - String_Constant_Ptr s = Cast(a->value()); - if (s) s->perform(this); - } else { - a->value()->perform(this); - } - if (a->is_rest_argument()) { - append_string("..."); - } - } - - void Inspect::operator()(Arguments_Ptr a) - { - append_string("("); - if (!a->empty()) { - (*a)[0]->perform(this); - for (size_t i = 1, L = a->length(); i < L; ++i) { - append_string(", "); // verified - // Sass Bug? append_comma_separator(); - (*a)[i]->perform(this); - } - } - append_string(")"); - } - - void Inspect::operator()(Selector_Schema_Ptr s) - { - opt.in_selector = true; - s->contents()->perform(this); - opt.in_selector = false; - } - - void Inspect::operator()(Parent_Selector_Ptr p) - { - if (p->is_real_parent_ref()) append_string("&"); - } - - void Inspect::operator()(Placeholder_Selector_Ptr s) - { - append_token(s->name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); - - } - - void Inspect::operator()(Element_Selector_Ptr s) - { - append_token(s->ns_name(), s); - } - - void Inspect::operator()(Class_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); - } - - void Inspect::operator()(Id_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); - } - - void Inspect::operator()(Attribute_Selector_Ptr s) - { - append_string("["); - add_open_mapping(s); - append_token(s->ns_name(), s); - if (!s->matcher().empty()) { - append_string(s->matcher()); - if (s->value() && *s->value()) { - s->value()->perform(this); - } - } - add_close_mapping(s); - if (s->modifier() != 0) { - append_mandatory_space(); - append_char(s->modifier()); - } - append_string("]"); - } - - void Inspect::operator()(Pseudo_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->expression()) { - append_string("("); - s->expression()->perform(this); - append_string(")"); - } - } - - void Inspect::operator()(Wrapped_Selector_Ptr s) - { - if (s->name() == " ") { - append_string(""); - } else { - bool was = in_wrapped; - in_wrapped = true; - append_token(s->name(), s); - append_string("("); - bool was_comma_array = in_comma_array; - in_comma_array = false; - s->selector()->perform(this); - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; - } - } - - void Inspect::operator()(Compound_Selector_Ptr s) - { - for (size_t i = 0, L = s->length(); i < L; ++i) { - (*s)[i]->perform(this); - } - if (s->has_line_break()) { - if (output_style() != COMPACT) { - append_optional_linefeed(); - } - } - } - - void Inspect::operator()(Complex_Selector_Ptr c) - { - Compound_Selector_Obj head = c->head(); - Complex_Selector_Obj tail = c->tail(); - Complex_Selector::Combinator comb = c->combinator(); - - if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { - if (tail) tail->perform(this); - return; - } - - if (c->has_line_feed()) { - if (!(c->has_parent_ref())) { - append_optional_linefeed(); - append_indentation(); - } - } - - if (head && head->length() != 0) head->perform(this); - bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); - bool is_tail = head && !head->is_empty_reference() && tail; - if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; - - switch (comb) { - case Complex_Selector::ANCESTOR_OF: - if (is_tail) append_mandatory_space(); - break; - case Complex_Selector::PARENT_OF: - append_optional_space(); - append_string(">"); - append_optional_space(); - break; - case Complex_Selector::ADJACENT_TO: - append_optional_space(); - append_string("+"); - append_optional_space(); - break; - case Complex_Selector::REFERENCE: - append_mandatory_space(); - append_string("/"); - c->reference()->perform(this); - append_string("/"); - append_mandatory_space(); - break; - case Complex_Selector::PRECEDES: - if (is_empty) append_optional_space(); - else append_mandatory_space(); - append_string("~"); - if (tail) append_mandatory_space(); - else append_optional_space(); - break; - default: break; - } - if (tail && comb != Complex_Selector::ANCESTOR_OF) { - if (c->has_line_break()) append_optional_linefeed(); - } - if (tail) tail->perform(this); - if (!tail && c->has_line_break()) { - if (output_style() == COMPACT) { - append_mandatory_space(); - } - } - } - - void Inspect::operator()(Selector_List_Ptr g) - { - - if (g->empty()) { - if (output_style() == TO_SASS) { - append_token("()", g); - } - return; - } - - - bool was_comma_array = in_comma_array; - // probably ruby sass eqivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string("("); - } - else if (!in_declaration && in_comma_array) { - append_string("("); - } - - if (in_declaration) in_comma_array = true; - - for (size_t i = 0, L = g->length(); i < L; ++i) { - if (!in_wrapped && i == 0) append_indentation(); - if ((*g)[i] == 0) continue; - schedule_mapping(g->at(i)->last()); - // add_open_mapping((*g)[i]->last()); - (*g)[i]->perform(this); - // add_close_mapping((*g)[i]->last()); - if (i < L - 1) { - scheduled_space = 0; - append_comma_separator(); - } - } - - in_comma_array = was_comma_array; - // probably ruby sass eqivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string(",)"); - } - else if (!in_declaration && in_comma_array) { - append_string(")"); - } - - } - - void Inspect::fallback_impl(AST_Node_Ptr n) - { - } - -} diff --git a/src/libsass/operators.cpp b/src/libsass/operators.cpp deleted file mode 100644 index 02e303738..000000000 --- a/src/libsass/operators.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include "sass.hpp" -#include "operators.hpp" - -namespace Sass { - - namespace Operators { - - inline double add(double x, double y) { return x + y; } - inline double sub(double x, double y) { return x - y; } - inline double mul(double x, double y) { return x * y; } - inline double div(double x, double y) { return x / y; } // x/0 checked by caller - - inline double mod(double x, double y) { // x/0 checked by caller - if ((x > 0 && y < 0) || (x < 0 && y > 0)) { - double ret = std::fmod(x, y); - return ret ? ret + y : ret; - } else { - return std::fmod(x, y); - } - } - - typedef double (*bop)(double, double); - bop ops[Sass_OP::NUM_OPS] = { - 0, 0, // and, or - 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte - add, sub, mul, div, mod - }; - - /* static function, has no pstate or traces */ - bool eq(Expression_Obj lhs, Expression_Obj rhs) - { - // operation is undefined if one is not a number - if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); - // use compare operator from ast node - return *lhs == *rhs; - } - - /* static function, throws OperationError, has no pstate or traces */ - bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) - { - // can only compare numbers!? - Number_Obj l = Cast(lhs); - Number_Obj r = Cast(rhs); - // operation is undefined if one is not a number - if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); - // use compare operator from ast node - return *l < *r; - } - - /* static functions, throws OperationError, has no pstate or traces */ - bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } - bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } - bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } - bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } - bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - enum Sass_OP op = operand.operand; - - String_Quoted_Ptr lqstr = Cast(&lhs); - String_Quoted_Ptr rqstr = Cast(&rhs); - - std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); - std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); - - if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - - std::string sep; - switch (op) { - case Sass_OP::ADD: sep = ""; break; - case Sass_OP::SUB: sep = "-"; break; - case Sass_OP::DIV: sep = "/"; break; - case Sass_OP::EQ: sep = "=="; break; - case Sass_OP::NEQ: sep = "!="; break; - case Sass_OP::LT: sep = "<"; break; - case Sass_OP::GT: sep = ">"; break; - case Sass_OP::LTE: sep = "<="; break; - case Sass_OP::GTE: sep = ">="; break; - default: - throw Exception::UndefinedOperation(&lhs, &rhs, op); - break; - } - - if (op == Sass_OP::ADD) { - // create string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); - } - - // add whitespace around operator - // but only if result is not delayed - if (sep != "" && delayed == false) { - if (operand.ws_before) sep = " " + sep; - if (operand.ws_after) sep = sep + " "; - } - - if (op == Sass_OP::SUB || op == Sass_OP::DIV) { - if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); - if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); - } - - return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - if (lhs.a() != rhs.a()) { - throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); - } - if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) { - throw Exception::ZeroDivisionError(lhs, rhs); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lhs.r(), rhs.r()), - ops[op](lhs.g(), rhs.g()), - ops[op](lhs.b(), rhs.b()), - lhs.a()); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - double lval = lhs.value(); - double rval = rhs.value(); - - if (op == Sass_OP::MOD && rval == 0) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); - } - - if (op == Sass_OP::DIV && rval == 0) { - std::string result(lval ? "Infinity" : "NaN"); - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - size_t l_n_units = lhs.numerators.size(); - size_t l_d_units = lhs.numerators.size(); - size_t r_n_units = rhs.denominators.size(); - size_t r_d_units = rhs.denominators.size(); - // optimize out the most common and simplest case - if (l_n_units == r_n_units && l_d_units == r_d_units) { - if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { - if (lhs.numerators == rhs.numerators) { - if (lhs.denominators == rhs.denominators) { - Number_Ptr v = SASS_MEMORY_COPY(&lhs); - v->value(ops[op](lval, rval)); - return v; - } - } - } - } - - Number_Obj v = SASS_MEMORY_COPY(&lhs); - - if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerators = rhs.numerators; - v->denominators = rhs.denominators; - } - - if (op == Sass_OP::MUL) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->reduce(); - } - else if (op == Sass_OP::DIV) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->reduce(); - } - else { - Number ln(lhs), rn(rhs); - ln.reduce(); rn.reduce(); - double f(rn.convert_factor(ln)); - v->value(ops[op](lval, rn.value() * f)); - } - - v->pstate(pstate); - return v.detach(); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - double lval = lhs.value(); - switch (op) { - case Sass_OP::ADD: - case Sass_OP::MUL: { - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lval, rhs.r()), - ops[op](lval, rhs.g()), - ops[op](lval, rhs.b()), - rhs.a()); - } - case Sass_OP::SUB: - case Sass_OP::DIV: { - std::string color(rhs.to_string(opt)); - return SASS_MEMORY_NEW(String_Quoted, - pstate, - lhs.to_string(opt) - + sass_op_separator(op) - + color); - } - default: break; - } - throw Exception::UndefinedOperation(&lhs, &rhs, op); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - double rval = rhs.value(); - if (op == Sass_OP::DIV && rval == 0) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(lhs, rhs); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lhs.r(), rval), - ops[op](lhs.g(), rval), - ops[op](lhs.b(), rval), - lhs.a()); - } - - } - -} diff --git a/src/libsass/parser.cpp b/src/libsass/parser.cpp deleted file mode 100644 index ee51d56b6..000000000 --- a/src/libsass/parser.cpp +++ /dev/null @@ -1,3137 +0,0 @@ -#include "sass.hpp" -#include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "constants.hpp" -#include "util.hpp" -#include "prelexer.hpp" -#include "color_maps.hpp" -#include "sass/functions.h" -#include "error_handling.hpp" - -// Notes about delayed: some ast nodes can have delayed evaluation so -// they can preserve their original semantics if needed. This is most -// prominently exhibited by the division operation, since it is not -// only a valid operation, but also a valid css statement (i.e. for -// fonts, as in `16px/24px`). When parsing lists and expression we -// unwrap single items from lists and other operations. A nested list -// must not be delayed, only the items of the first level sometimes -// are delayed (as with argument lists). To achieve this we need to -// pass status to the list parser, so this can be set correctly. -// Another case with delayed values are colors. In compressed mode -// only processed values get compressed (other are left as written). - -#include -#include -#include -#include - -namespace Sass { - using namespace Constants; - using namespace Prelexer; - - Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - pstate.offset.column = 0; - pstate.offset.line = 0; - Parser p(ctx, pstate, traces); - p.source = source ? source : beg; - p.position = beg ? beg : p.source; - p.end = p.position + strlen(p.position); - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - p.block_stack.push_back(root); - root->is_root(true); - return p; - } - - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - pstate.offset.column = 0; - pstate.offset.line = 0; - Parser p(ctx, pstate, traces); - p.source = source ? source : beg; - p.position = beg ? beg : p.source; - p.end = end ? end : p.position + strlen(p.position); - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - p.block_stack.push_back(root); - root->is_root(true); - return p; - } - - void Parser::advanceToNextToken() { - lex < css_comments >(false); - // advance to position - pstate += pstate.offset; - pstate.offset.column = 0; - pstate.offset.line = 0; - } - - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); - // ToDo: ruby sass errors on parent references - // ToDo: remap the source-map entries somehow - return p.parse_selector_list(false); - } - - bool Parser::peek_newline(const char* start) - { - return peek_linefeed(start ? start : position) - && ! peek_css>(start); - } - - Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - Parser p(ctx, pstate, traces); - p.source = source ? source : t.begin; - p.position = t.begin ? t.begin : p.source; - p.end = t.end ? t.end : p.position + strlen(p.position); - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - p.block_stack.push_back(root); - root->is_root(true); - return p; - } - - /* main entry point to parse root block */ - Block_Obj Parser::parse() - { - - // consume unicode BOM - read_bom(); - - // scan the input to find invalid utf8 sequences - const char* it = utf8::find_invalid(position, end); - - // report invalid utf8 - if (it != end) { - pstate += Offset::init(position, it); - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); - } - - // create a block AST node to hold children - Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); - - // check seems a bit esoteric but works - if (ctx.resources.size() == 1) { - // apply headers only on very first include - ctx.apply_custom_headers(root, path, pstate); - } - - // parse children nodes - block_stack.push_back(root); - parse_block_nodes(true); - block_stack.pop_back(); - - // update final position - root->update_pstate(pstate); - - if (position != end) { - css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); - } - - return root; - } - - - // convenience function for block parsing - // will create a new block ad-hoc for you - // this is the base block parsing function - Block_Obj Parser::parse_css_block(bool is_root) - { - - // parse comments before block - // lex < optional_css_comments >(); - - // lex mandatory opener or error out - if (!lex_css < exactly<'{'> >()) { - css_error("Invalid CSS", " after ", ": expected \"{\", was "); - } - // create new block and push to the selector stack - Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); - block_stack.push_back(block); - - if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - - if (!lex_css < exactly<'}'> >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - - // update for end position - // this seems to be done somewhere else - // but that fixed selector schema issue - // block->update_pstate(pstate); - - // parse comments after block - // lex < optional_css_comments >(); - - block_stack.pop_back(); - - return block; - } - - // convenience function for block parsing - // will create a new block ad-hoc for you - // also updates the `in_at_root` flag - Block_Obj Parser::parse_block(bool is_root) - { - return parse_css_block(is_root); - } - - // the main block parsing function - // parses stuff between `{` and `}` - bool Parser::parse_block_nodes(bool is_root) - { - - // loop until end of string - while (position < end) { - - // we should be able to refactor this - parse_block_comments(); - lex < css_whitespace >(); - - if (lex < exactly<';'> >()) continue; - if (peek < end_of_file >()) return true; - if (peek < exactly<'}'> >()) return true; - - if (parse_block_node(is_root)) continue; - - parse_block_comments(); - - if (lex_css < exactly<';'> >()) continue; - if (peek_css < end_of_file >()) return true; - if (peek_css < exactly<'}'> >()) return true; - - // illegal sass - return false; - } - // return success - return true; - } - - // parser for a single node in a block - // semicolons must be lexed beforehand - bool Parser::parse_block_node(bool is_root) { - - Block_Obj block = block_stack.back(); - - parse_block_comments(); - - // throw away white-space - // includes line comments - lex < css_whitespace >(); - - Lookahead lookahead_result; - - // also parse block comments - - // first parse everything that is allowed in functions - if (lex < variable >(true)) { block->append(parse_assignment()); } - else if (lex < kwd_err >(true)) { block->append(parse_error()); } - else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } - else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } - else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } - else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } - else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } - else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } - else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } - - // parse imports to process later - else if (lex < kwd_import >(true)) { - Scope parent = stack.empty() ? Scope::Rules : stack.back(); - if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { - if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 - error("Import directives may not be used within control directives or mixins."); - } - } - // this puts the parsed doc into sheets - // import stub will fetch this in expand - Import_Obj imp = parse_import(); - // if it is a url, we only add the statement - if (!imp->urls().empty()) block->append(imp); - // process all resources now (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); - } - } - - else if (lex < kwd_extend >(true)) { - Lookahead lookahead = lookahead_for_include(position); - if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - Selector_List_Obj target; - if (!lookahead.has_interpolants) { - target = parse_selector_list(true); - } - else { - target = SASS_MEMORY_NEW(Selector_List, pstate); - target->schema(parse_selector_schema(lookahead.found, true)); - } - - block->append(SASS_MEMORY_NEW(Extension, pstate, target)); - } - - // selector may contain interpolations which need delayed evaluation - else if ( - !(lookahead_result = lookahead_for_selector(position)).error && - !lookahead_result.is_custom_property - ) - { - block->append(parse_ruleset(lookahead_result)); - } - - // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } - else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } - else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } - else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } - else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } - else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } - else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } - - // ignore the @charset directive for now - else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } - - // generic at keyword (keep last) - else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } - else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } - else if (lex< at_keyword >(true)) { block->append(parse_directive()); } - - else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { - lex< css_whitespace >(); - if (position >= end) return true; - css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); - } - // parse a declaration - else - { - // ToDo: how does it handle parse errors? - // maybe we are expected to parse something? - Declaration_Obj decl = parse_declaration(); - decl->tabs(indentation); - block->append(decl); - // maybe we have a "sub-block" - if (peek< exactly<'{'> >()) { - if (decl->is_indented()) ++ indentation; - // parse a propset that rides on the declaration's property - stack.push_back(Scope::Properties); - decl->block(parse_block()); - stack.pop_back(); - if (decl->is_indented()) -- indentation; - } - } - // something matched - return true; - } - // EO parse_block_nodes - - // parse imports inside the - Import_Obj Parser::parse_import() - { - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - std::vector> to_import; - bool first = true; - do { - while (lex< block_comment >()); - if (lex< quoted_string >()) { - to_import.push_back(std::pair(std::string(lexed), 0)); - } - else if (lex< uri_prefix >()) { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); - - if (lex< quoted_string >()) { - Expression_Obj quoted_url = parse_string(); - args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); - } - else if (String_Obj string_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); - } - else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { - Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); - } - else { - error("malformed URL"); - } - if (!lex< exactly<')'> >()) error("URI is missing ')'"); - to_import.push_back(std::pair("", result)); - } - else { - if (first) error("@import directive requires a url or quoted path"); - else error("expecting another url or quoted path in @import list"); - } - first = false; - } while (lex_css< exactly<','> >()); - - if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { - List_Obj import_queries = parse_media_queries(); - imp->import_queries(import_queries); - } - - for(auto location : to_import) { - if (location.second) { - imp->urls().push_back(location.second); - } - // check if custom importers want to take over the handling - else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { - // nobody wants it, so we do our import - ctx.import_url(imp, location.first, path); - } - } - - return imp; - } - - Definition_Obj Parser::parse_definition(Definition::Type which_type) - { - std::string which_str(lexed); - if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); - std::string name(Util::normalize_underscores(lexed)); - if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) - { error("Invalid function name \"" + name + "\"."); } - ParserState source_position_of_def = pstate; - Parameters_Obj params = parse_parameters(); - if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); - else stack.push_back(Scope::Function); - Block_Obj body = parse_block(); - stack.pop_back(); - return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); - } - - Parameters_Obj Parser::parse_parameters() - { - Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - params->append(parse_parameter()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - } - return params; - } - - Parameter_Obj Parser::parse_parameter() - { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); - } - while (lex< alternatives < spaces, block_comment > >()); - lex < variable >(); - std::string name(Util::normalize_underscores(lexed)); - ParserState pos = pstate; - Expression_Obj val; - bool is_rest = false; - while (lex< alternatives < spaces, block_comment > >()); - if (lex< exactly<':'> >()) { // there's a default value - while (lex< block_comment >()); - val = parse_space_list(); - } - else if (lex< exactly< ellipsis > >()) { - is_rest = true; - } - return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); - } - - Arguments_Obj Parser::parse_arguments() - { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - args->append(parse_argument()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } - return args; - } - - Argument_Obj Parser::parse_argument() - { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { - position += 2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - - Argument_Obj arg; - if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { - lex_css< variable >(); - std::string name(Util::normalize_underscores(lexed)); - ParserState p = pstate; - lex_css< exactly<':'> >(); - Expression_Obj val = parse_space_list(); - arg = SASS_MEMORY_NEW(Argument, p, val, name); - } - else { - bool is_arglist = false; - bool is_keyword = false; - Expression_Obj val = parse_space_list(); - List_Ptr l = Cast(val); - if (lex_css< exactly< ellipsis > >()) { - if (val->concrete_type() == Expression::MAP || ( - (l != NULL && l->separator() == SASS_HASH) - )) is_keyword = true; - else is_arglist = true; - } - arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); - } - return arg; - } - - Assignment_Obj Parser::parse_assignment() - { - std::string name(Util::normalize_underscores(lexed)); - ParserState var_source_position = pstate; - if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); - if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - Expression_Obj val; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.has_interpolants && lookahead.found) { - val = parse_value_schema(lookahead.found); - } else { - val = parse_list(); - } - bool is_default = false; - bool is_global = false; - while (peek< alternatives < default_flag, global_flag > >()) { - if (lex< default_flag >()) is_default = true; - else if (lex< global_flag >()) is_global = true; - } - return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); - } - - // a ruleset connects a selector and a block - Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) - { - NESTING_GUARD(nestings); - // inherit is_root from parent block - Block_Obj parent = block_stack.back(); - bool is_root = parent && parent->is_root(); - // make sure to move up the the last position - lex < optional_css_whitespace >(false, true); - // create the connector object (add parts later) - Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); - // parse selector static or as schema to be evaluated later - if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); - else { - Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); - list->schema(parse_selector_schema(lookahead.position, false)); - ruleset->selector(list); - } - // then parse the inner block - stack.push_back(Scope::Rules); - ruleset->block(parse_block()); - stack.pop_back(); - // update for end position - ruleset->update_pstate(pstate); - ruleset->block()->update_pstate(pstate); - // need this info for sanity checks - ruleset->is_root(is_root); - // return AST Node - return ruleset; - } - - // parse a selector schema that will be evaluated in the eval stage - // uses a string schema internally to do the actual schema handling - // in the eval stage we will be re-parse it into an actual selector - Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) - { - NESTING_GUARD(nestings); - // move up to the start - lex< optional_spaces >(); - const char* i = position; - // selector schema re-uses string schema implementation - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - // the selector schema is pretty much just a wrapper for the string schema - Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); - selector_schema->connect_parent(chroot == false); - selector_schema->media_block(last_media_block); - - // process until end - while (i < end_of_selector) { - // try to parse mutliple interpolants - if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { - // accumulate the preceding segment if the position has advanced - if (i < p) { - std::string parsed(i, p); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate += Offset(parsed); - str->update_pstate(pstate); - schema->append(str); - } - - // skip over all nested inner interpolations up to our own delimiter - const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); - // check if the interpolation never ends of only contains white-space (error out) - if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { - position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - // pass inner expression to the parser to resolve nested interpolations - pstate.add(p, p+2); - Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); - // set status on the list expression - interpolant->is_interpolant(true); - // schema->has_interpolants(true); - // add to the string schema - schema->append(interpolant); - // advance parser state - pstate.add(p+2, j); - // advance position - i = j; - } - // no more interpolants have been found - // add the last segment if there is one - else { - // make sure to add the last bits of the string up to the end (if any) - if (i < end_of_selector) { - std::string parsed(i, end_of_selector); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate += Offset(parsed); - str->update_pstate(pstate); - i = end_of_selector; - schema->append(str); - } - // exit loop - } - } - // EO until eos - - // update position - position = i; - - // update for end position - selector_schema->update_pstate(pstate); - schema->update_pstate(pstate); - - after_token = before_token = pstate; - - // return parsed result - return selector_schema.detach(); - } - // EO parse_selector_schema - - void Parser::parse_charset_directive() - { - lex < - sequence < - quoted_string, - optional_spaces, - exactly <';'> - > - >(); - } - - // called after parsing `kwd_include_directive` - Mixin_Call_Obj Parser::parse_include_directive() - { - // lex identifier into `lexed` var - lex_identifier(); // may error out - // normalize underscores to hyphens - std::string name(Util::normalize_underscores(lexed)); - // create the initial mixin call object - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); - // parse mandatory arguments - call->arguments(parse_arguments()); - // parse optional block - if (peek < exactly <'{'> >()) { - call->block(parse_block()); - } - // return ast node - return call.detach(); - } - // EO parse_include_directive - - // parse a list of complex selectors - // this is the main entry point for most - Selector_List_Obj Parser::parse_selector_list(bool chroot) - { - bool reloop; - bool had_linefeed = false; - NESTING_GUARD(nestings); - Complex_Selector_Obj sel; - Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); - group->media_block(last_media_block); - - if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - - do { - reloop = false; - - had_linefeed = had_linefeed || peek_newline(); - - if (peek_css< alternatives < class_char < selector_list_delims > > >()) - break; // in case there are superfluous commas at the end - - // now parse the complex selector - sel = parse_complex_selector(chroot); - - if (!sel) return group.detach(); - - sel->has_line_feed(had_linefeed); - - had_linefeed = false; - - while (peek_css< exactly<','> >()) - { - lex< css_comments >(false); - // consume everything up and including the comma separator - reloop = lex< exactly<','> >() != 0; - // remember line break (also between some commas) - had_linefeed = had_linefeed || peek_newline(); - // remember line break (also between some commas) - } - group->append(sel); - } - while (reloop); - while (lex_css< kwd_optional >()) { - group->is_optional(true); - } - // update for end position - group->update_pstate(pstate); - if (sel) sel->last()->has_line_break(false); - return group.detach(); - } - // EO parse_selector_list - - // a complex selector combines a compound selector with another - // complex selector, with one of four combinator operations. - // the compound selector (head) is optional, since the combinator - // can come first in the whole selector sequence (like `> DIV'). - Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) - { - - NESTING_GUARD(nestings); - String_Obj reference = 0; - lex < block_comment >(); - advanceToNextToken(); - Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); - - if (peek < end_of_file >()) return 0; - - // parse the left hand side - Compound_Selector_Obj lhs; - // special case if it starts with combinator ([+~>]) - if (!peek_css< class_char < selector_combinator_ops > >()) { - // parse the left hand side - lhs = parse_compound_selector(); - } - - - // parse combinator between lhs and rhs - Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; - if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; - else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; - else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; - else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { - // comments are allowed, but not spaces? - combinator = Complex_Selector::REFERENCE; - if (!lex < re_reference_combinator >()) return 0; - reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? - } - - if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; - - // lex < block_comment >(); - sel->head(lhs); - sel->combinator(combinator); - sel->media_block(last_media_block); - - if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); - // has linfeed after combinator? - sel->has_line_break(peek_newline()); - // sel->has_line_feed(has_line_feed); - - // check if we got the abort condition (ToDo: optimize) - if (!peek_css< class_char < complex_selector_delims > >()) { - // parse next selector in sequence - sel->tail(parse_complex_selector(true)); - } - - // add a parent selector if we are not in a root - // also skip adding parent ref if we only have refs - if (!sel->has_parent_ref() && !chroot) { - // create the objects to wrap parent selector reference - Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); - Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); - parent->media_block(last_media_block); - head->media_block(last_media_block); - // add simple selector - head->append(parent); - // selector may not have any head yet - if (!sel->head()) { sel->head(head); } - // otherwise we need to create a new complex selector and set the old one as its tail - else { - sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); - sel->media_block(last_media_block); - } - // peek for linefeed and remember result on head - // if (peek_newline()) head->has_line_break(true); - } - - sel->update_pstate(pstate); - // complex selector - return sel; - } - // EO parse_complex_selector - - // parse one compound selector, which is basically - // a list of simple selectors (directly adjacent) - // lex them exactly (without skipping white-space) - Compound_Selector_Obj Parser::parse_compound_selector() - { - // init an empty compound selector wrapper - Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); - seq->media_block(last_media_block); - - // skip initial white-space - lex< css_whitespace >(); - - // parse list - while (true) - { - // remove all block comments (don't skip white-space) - lex< delimited_by< slash_star, star_slash, false > >(false); - // parse functional - if (match < re_pseudo_selector >()) - { - seq->append(parse_simple_selector()); - } - // parse parent selector - else if (lex< exactly<'&'> >(false)) - { - // this produces a linefeed!? - seq->has_parent_reference(true); - seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); - // parent selector only allowed at start - // upcoming Sass may allow also trailing - if (seq->length() > 1) { - ParserState state(pstate); - Simple_Selector_Obj cur = (*seq)[seq->length()-1]; - Simple_Selector_Obj prev = (*seq)[seq->length()-2]; - std::string sel(prev->to_string({ NESTED, 5 })); - std::string found(cur->to_string({ NESTED, 5 })); - if (lex < identifier >()) { found += std::string(lexed); } - error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" - "\"" + found + "\" may only be used at the beginning of a compound selector.", state); - } - } - // parse type selector - else if (lex< re_type_selector >(false)) - { - seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); - } - // peek for abort conditions - else if (peek< spaces >()) break; - else if (peek< end_of_file >()) { break; } - else if (peek_css < class_char < selector_combinator_ops > >()) break; - else if (peek_css < class_char < complex_selector_delims > >()) break; - // otherwise parse another simple selector - else { - Simple_Selector_Obj sel = parse_simple_selector(); - if (!sel) return 0; - seq->append(sel); - } - } - - if (seq && !peek_css>>()) { - seq->has_line_break(peek_newline()); - } - - // EO while true - return seq; - - } - // EO parse_compound_selector - - Simple_Selector_Obj Parser::parse_simple_selector() - { - lex < css_comments >(false); - if (lex< class_name >()) { - return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); - } - else if (lex< id_name >()) { - return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); - } - else if (lex< alternatives < variable, number, static_reference_combinator > >()) { - return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); - } - else if (peek< pseudo_not >()) { - return parse_negated_selector(); - } - else if (peek< re_pseudo_selector >()) { - return parse_pseudo_selector(); - } - else if (peek< exactly<':'> >()) { - return parse_pseudo_selector(); - } - else if (lex < exactly<'['> >()) { - return parse_attribute_selector(); - } - else if (lex< placeholder >()) { - Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); - sel->media_block(last_media_block); - return sel; - } - else { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - // failed - return 0; - } - - Wrapped_Selector_Obj Parser::parse_negated_selector() - { - lex< pseudo_not >(); - std::string name(lexed); - ParserState nsource_position = pstate; - Selector_List_Obj negated = parse_selector_list(true); - if (!lex< exactly<')'> >()) { - error("negated selector is missing ')'"); - } - name.erase(name.size() - 1); - return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); - } - - // a pseudo selector often starts with one or two colons - // it can contain more selectors inside parentheses - Simple_Selector_Obj Parser::parse_pseudo_selector() { - if (lex< sequence< - optional < pseudo_prefix >, - // we keep the space within the name, strange enough - // ToDo: refactor output to schedule the space for it - // or do we really want to keep the real white-space? - sequence< identifier, optional < block_comment >, exactly<'('> > - > >()) - { - - std::string name(lexed); - name.erase(name.size() - 1); - ParserState p = pstate; - - // specially parse static stuff - // ToDo: really everything static? - if (peek_css < - sequence < - alternatives < - static_value, - binomial - >, - optional_css_whitespace, - exactly<')'> - > - >() - ) { - lex_css< alternatives < static_value, binomial > >(); - String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (lex_css< exactly<')'> >()) { - expr->can_compress_whitespace(true); - return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); - } - } - else if (Selector_List_Obj wrapped = parse_selector_list(true)) { - if (wrapped && lex_css< exactly<')'> >()) { - return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); - } - } - - } - // EO if pseudo selector - - else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { - return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); - } - else if(lex < pseudo_prefix >()) { - css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); - } - - css_error("Invalid CSS", " after ", ": expected \")\", was "); - - // unreachable statement - return 0; - } - - const char* Parser::re_attr_sensitive_close(const char* src) - { - return alternatives < exactly<']'>, exactly<'/'> >(src); - } - - const char* Parser::re_attr_insensitive_close(const char* src) - { - return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); - } - - Attribute_Selector_Obj Parser::parse_attribute_selector() - { - ParserState p = pstate; - if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); - std::string name(lexed); - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); - } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); - } - if (!lex_css< alternatives< exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match > >()) { - error("invalid operator in attribute selector for " + name); - } - std::string matcher(lexed); - - String_Obj value = 0; - if (lex_css< identifier >()) { - value = SASS_MEMORY_NEW(String_Constant, p, lexed); - } - else if (lex_css< quoted_string >()) { - value = parse_interpolated_chunk(lexed, true); // needed! - } - else { - error("expected a string constant or identifier in attribute selector for " + name); - } - - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); - } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); - } - error("unterminated attribute selector for " + name); - return NULL; // to satisfy compilers (error must not return) - } - - /* parse block comment and add to block */ - void Parser::parse_block_comments() - { - Block_Obj block = block_stack.back(); - - while (lex< block_comment >()) { - bool is_important = lexed.begin[2] == '!'; - // flag on second param is to skip loosely over comments - String_Obj contents = parse_interpolated_chunk(lexed, true, false); - block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); - } - } - - Declaration_Obj Parser::parse_declaration() { - String_Obj prop; - bool is_custom_property = false; - if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { - const std::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = parse_identifier_schema(); - } - else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { - const std::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - else { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - bool is_indented = true; - const std::string property(lexed); - if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); - if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); - if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty - if (is_custom_property) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); - } - lex < css_comments >(false); - if (peek_css< static_value >()) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); - } - else { - Expression_Obj value; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.found) { - if (lookahead.has_interpolants) { - value = parse_value_schema(lookahead.found); - } else { - value = parse_list(DELAYED); - } - } - else { - value = parse_list(DELAYED); - if (List_Ptr list = Cast(value)) { - if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } - } - lex < css_comments >(false); - Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); - decl->is_indented(is_indented); - decl->update_pstate(pstate); - return decl; - } - } - - // parse +/- and return false if negative - // this is never hit via spec tests - bool Parser::parse_number_prefix() - { - bool positive = true; - while(true) { - if (lex < block_comment >()) continue; - if (lex < number_prefix >()) continue; - if (lex < exactly < '-' > >()) { - positive = !positive; - continue; - } - break; - } - return positive; - } - - Expression_Obj Parser::parse_map() - { - NESTING_GUARD(nestings); - Expression_Obj key = parse_list(); - List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); - - // it's not a map so return the lexed value as a list value - if (!lex_css< exactly<':'> >()) - { return key; } - - List_Obj l = Cast(key); - if (l && l->separator() == SASS_COMMA) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - - Expression_Obj value = parse_space_list(); - - map->append(key); - map->append(value); - - while (lex_css< exactly<','> >()) - { - // allow trailing commas - #495 - if (peek_css< exactly<')'> >(position)) - { break; } - - key = parse_space_list(); - - if (!(lex< exactly<':'> >())) - { css_error("Invalid CSS", " after ", ": expected \":\", was "); } - - value = parse_space_list(); - - map->append(key); - map->append(value); - } - - ParserState ps = map->pstate(); - ps.offset = pstate - ps + pstate.offset; - map->pstate(ps); - - return map; - } - - Expression_Obj Parser::parse_bracket_list() - { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); - } - - bool has_paren = peek_css< exactly<'('> >() != NULL; - - // now try to parse a space list - Expression_Obj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - List_Obj l = Cast(list); - if (!l || l->is_bracketed() || has_paren) { - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); - bracketed_list->append(list); - return bracketed_list; - } - l->is_bracketed(true); - return l; - } - - // if we got so far, we actually do have a comma list - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); - // wrap the first expression - bracketed_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - bracketed_list->append(parse_space_list()); - } - // return the list - return bracketed_list; - } - - // parse list returns either a space separated list, - // a comma separated list or any bare expression found. - // so to speak: we unwrap items from lists if possible here! - Expression_Obj Parser::parse_list(bool delayed) - { - NESTING_GUARD(nestings); - return parse_comma_list(delayed); - } - - // will return singletons unwrapped - Expression_Obj Parser::parse_comma_list(bool delayed) - { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0); - } - - // now try to parse a space list - Expression_Obj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - // set_delay doesn't apply to list children - // so this will only undelay single values - if (!delayed) list->set_delayed(false); - return list; - } - - // if we got so far, we actually do have a comma list - List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); - // wrap the first expression - comma_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - comma_list->append(parse_space_list()); - } - // return the list - return comma_list; - } - // EO parse_comma_list - - // will return singletons unwrapped - Expression_Obj Parser::parse_space_list() - { - NESTING_GUARD(nestings); - Expression_Obj disj1 = parse_disjunction(); - // if it's a singleton, return it (don't wrap it) - if (peek_css< space_list_terminator >(position) - ) { - return disj1; } - - List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); - space_list->append(disj1); - - while ( - !(peek_css< space_list_terminator >(position)) && - peek_css< optional_css_whitespace >() != end - ) { - // the space is parsed implicitly? - space_list->append(parse_disjunction()); - } - // return the list - return space_list; - } - // EO parse_space_list - - // parse logical OR operation - Expression_Obj Parser::parse_disjunction() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parse the left hand side conjunction - Expression_Obj conj = parse_conjunction(); - // parse multiple right hand sides - std::vector operands; - while (lex_css< kwd_or >()) - operands.push_back(parse_conjunction()); - // if it's a singleton, return it directly - if (operands.size() == 0) return conj; - // fold all operands into one binary expression - Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_disjunction - - // parse logical AND operation - Expression_Obj Parser::parse_conjunction() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parse the left hand side relation - Expression_Obj rel = parse_relation(); - // parse multiple right hand sides - std::vector operands; - while (lex_css< kwd_and >()) { - operands.push_back(parse_relation()); - } - // if it's a singleton, return it directly - if (operands.size() == 0) return rel; - // fold all operands into one binary expression - Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_conjunction - - // parse comparison operations - Expression_Obj Parser::parse_relation() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parse the left hand side expression - Expression_Obj lhs = parse_expression(); - std::vector operands; - std::vector operators; - // if it's a singleton, return it (don't wrap it) - while (peek< alternatives < - kwd_eq, - kwd_neq, - kwd_gte, - kwd_gt, - kwd_lte, - kwd_lt - > >(position)) - { - // is directly adjancent to expression? - bool left_ws = peek < css_comments >() != NULL; - // parse the operator - enum Sass_OP op - = lex() ? Sass_OP::EQ - : lex() ? Sass_OP::NEQ - : lex() ? Sass_OP::GTE - : lex() ? Sass_OP::LTE - : lex() ? Sass_OP::GT - : lex() ? Sass_OP::LT - // we checked the possibilities on top of fn - : Sass_OP::EQ; - // is directly adjacent to expression? - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ op, left_ws, right_ws }); - operands.push_back(parse_expression()); - } - // we are called recursively for list, so we first - // fold inner binary expression which has delayed - // correctly set to zero. After folding we also unwrap - // single nested items. So we cannot set delay on the - // returned result here, as we have lost nestings ... - Expression_Obj ex = fold_operands(lhs, operands, operators); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // parse_relation - - // parse expression valid for operations - // called from parse_relation - // called from parse_for_directive - // called from parse_media_expression - // parse addition and subtraction operations - Expression_Obj Parser::parse_expression() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parses multiple add and subtract operations - // NOTE: make sure that identifiers starting with - // NOTE: dashes do NOT count as subtract operation - Expression_Obj lhs = parse_operators(); - // if it's a singleton, return it (don't wrap it) - if (!(peek_css< exactly<'+'> >(position) || - // condition is a bit misterious, but some combinations should not be counted as operations - (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || - (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || - peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) - { return lhs; } - - std::vector operands; - std::vector operators; - bool left_ws = peek < css_comments >() != NULL; - while ( - lex_css< exactly<'+'> >() || - - ( - ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) - && lex_css< sequence< negate< digit >, exactly<'-'> > >() - ) - - ) { - - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); - operands.push_back(parse_operators()); - left_ws = peek < css_comments >() != NULL; - } - - if (operands.size() == 0) return lhs; - Expression_Obj ex = fold_operands(lhs, operands, operators); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - - // parse addition and subtraction operations - Expression_Obj Parser::parse_operators() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - Expression_Obj factor = parse_factor(); - // if it's a singleton, return it (don't wrap it) - std::vector operands; // factors - std::vector operators; // ops - // lex operations to apply to lhs - const char* left_ws = peek < css_comments >(); - while (lex_css< class_char< static_ops > >()) { - const char* right_ws = peek < css_comments >(); - switch(*lexed.begin) { - case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; - case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; - case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; - default: throw std::runtime_error("unknown static op parsed"); - } - operands.push_back(parse_factor()); - left_ws = peek < css_comments >(); - } - // operands and operators to binary expression - Expression_Obj ex = fold_operands(factor, operands, operators); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_operators - - - // called from parse_operators - // called from parse_value_schema - Expression_Obj Parser::parse_factor() - { - NESTING_GUARD(nestings); - lex < css_comments >(false); - if (lex_css< exactly<'('> >()) { - // parse_map may return a list - Expression_Obj value = parse_map(); - // lex the expected closing parenthesis - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); - // expression can be evaluated - return value; - } - else if (lex_css< exactly<'['> >()) { - // explicit bracketed - Expression_Obj value = parse_bracket_list(); - // lex the expected closing square bracket - if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); - return value; - } - // string may be interpolated - // if (lex< quoted_string >()) { - // return &parse_string(); - // } - else if (peek< ie_property >()) { - return parse_ie_property(); - } - else if (peek< ie_keyword_arg >()) { - return parse_ie_keyword_arg(); - } - else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { - return parse_calc_function(); - } - else if (lex < functional_schema >()) { - return parse_function_call_schema(); - } - else if (lex< identifier_schema >()) { - String_Obj string = parse_identifier_schema(); - if (String_Schema_Ptr schema = Cast(string)) { - if (lex < exactly < '(' > >()) { - schema->append(parse_list()); - lex < exactly < ')' > >(); - } - } - return string; - } - else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { - return parse_url_function_string(); - } - else if (peek< re_functional >()) { - return parse_function_call(); - } - else if (lex< exactly<'+'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'-'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'/'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< sequence< kwd_not > >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - // this whole branch is never hit via spec tests - else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { - if (parse_number_prefix()) return parse_value(); // prefix is positive - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); - if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else { - return parse_value(); - } - } - - bool number_has_zero(const std::string& parsed) - { - size_t L = parsed.length(); - return !( (L > 0 && parsed.substr(0, 1) == ".") || - (L > 1 && parsed.substr(0, 2) == "0.") || - (L > 1 && parsed.substr(0, 2) == "-.") || - (L > 2 && parsed.substr(0, 3) == "-0.") ); - } - - Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) - { - Number_Ptr nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "", - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) - { - Number_Ptr nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "%", - true); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) - { - size_t L = parsed.length(); - size_t num_pos = parsed.find_first_not_of(" \n\r\t"); - if (num_pos == std::string::npos) num_pos = L; - size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); - if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { - unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); - } - if (unit_pos == std::string::npos) unit_pos = L; - const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); - Number_Ptr nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(num.c_str()), - Token(number(parsed.c_str())), - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) - { - Color_Ptr color = NULL; - if (parsed[0] != '#') { - return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); - } - // chop off the '#' - std::string hext(parsed.substr(1)); - if (parsed.length() == 4) { - std::string r(2, parsed[1]); - std::string g(2, parsed[2]); - std::string b(2, parsed[3]); - color = SASS_MEMORY_NEW(Color, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 7) { - std::string r(parsed.substr(1,2)); - std::string g(parsed.substr(3,2)); - std::string b(parsed.substr(5,2)); - color = SASS_MEMORY_NEW(Color, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 9) { - std::string r(parsed.substr(1,2)); - std::string g(parsed.substr(3,2)); - std::string b(parsed.substr(5,2)); - std::string a(parsed.substr(7,2)); - color = SASS_MEMORY_NEW(Color, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - static_cast(strtol(a.c_str(), NULL, 16)) / 255, - parsed); - } - color->is_interpolant(false); - color->is_delayed(false); - return color; - } - - Value_Ptr Parser::color_or_string(const std::string& lexed) const - { - if (auto color = name_to_color(lexed)) { - auto c = SASS_MEMORY_NEW(Color, color); - c->is_delayed(true); - c->pstate(pstate); - c->disp(lexed); - return c; - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - - // parse one value for a list - Expression_Obj Parser::parse_value() - { - lex< css_comments >(false); - if (lex< ampersand >()) - { - if (match< ampersand >()) { - warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); - } - return SASS_MEMORY_NEW(Parent_Selector, pstate); } - - if (lex< kwd_important >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } - - // parse `10%4px` into separated items and not a schema - if (lex< sequence < percentage, lookahead < number > > >()) - { return lexed_percentage(lexed); } - - if (lex< sequence < number, lookahead< sequence < op, number > > > >()) - { return lexed_number(lexed); } - - // string may be interpolated - if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) - { return parse_string(); } - - if (const char* stop = peek< value_schema >()) - { return parse_value_schema(stop); } - - // string may be interpolated - if (lex< quoted_string >()) - { return parse_string(); } - - if (lex< kwd_true >()) - { return SASS_MEMORY_NEW(Boolean, pstate, true); } - - if (lex< kwd_false >()) - { return SASS_MEMORY_NEW(Boolean, pstate, false); } - - if (lex< kwd_null >()) - { return SASS_MEMORY_NEW(Null, pstate); } - - if (lex< identifier >()) { - return color_or_string(lexed); - } - - if (lex< percentage >()) - { return lexed_percentage(lexed); } - - // match hex number first because 0x000 looks like a number followed by an identifier - if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) - { return lexed_hex_color(lexed); } - - if (lex< hexa >()) - { - std::string s = lexed.to_string(); - - deprecated( - "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in", - "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.", - true, pstate - ); - - return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); - } - - if (lex< sequence < exactly <'#'>, identifier > >()) - { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } - - // also handle the 10em- foo special case - // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression - if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) - { return lexed_dimension(lexed); } - - if (lex< sequence< static_component, one_plus< strict_identifier > > >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - - if (lex< number >()) - { return lexed_number(lexed); } - - if (lex< variable >()) - { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } - - // Special case handling for `%` proceeding an interpolant. - if (lex< sequence< exactly<'%'>, optional< percentage > > >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - - // unreachable statement - return 0; - } - - // this parses interpolation inside other strings - // means the result should later be quoted again - String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) - { - const char* i = chunk.begin; - // see if there any interpolants - const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - - if (!p) { - String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); - if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); - return str_quoted; - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); - schema->is_interpolant(true); - while (i < chunk.end) { - p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace - if (j) { --j; - // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside string constant " + chunk.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - // check if we need quotes here (was not sure after merge) - if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); - break; - } - ++ i; - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value(bool top_level) - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - String_Schema_Obj tok; - if (!(tok = parse_css_variable_value_token(top_level))) { - return NULL; - } - - schema->concat(tok); - while ((tok = parse_css_variable_value_token(top_level))) { - schema->concat(tok); - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if ( - (top_level && lex< css_variable_top_level_value >(false)) || - (!top_level && lex< css_variable_value >(false)) - ) { - Token str(lexed); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); - } - else if (Expression_Obj tok = lex_interpolation()) { - if (String_Schema_Ptr s = Cast(tok)) { - schema->concat(s); - } else { - schema->append(tok); - } - } - else if (lex< quoted_string >()) { - Expression_Obj tok = parse_string(); - if (String_Schema_Ptr s = Cast(tok)) { - schema->concat(s); - } else { - schema->append(tok); - } - } - else { - if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { - if (lex< exactly<'('> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); - } - else if (lex< exactly<'['> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); - } - else if (lex< exactly<'{'> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); - } - } - } - - return schema->length() > 0 ? schema.detach() : NULL; - } - - Value_Obj Parser::parse_static_value() - { - lex< static_value >(); - Token str(lexed); - // static values always have trailing white- - // space and end delimiter (\s*[;]$) included - --pstate.offset.column; - --after_token.column; - --str.end; - --position; - - return color_or_string(str.time_wspace());; - } - - String_Obj Parser::parse_string() - { - return parse_interpolated_chunk(Token(lexed)); - } - - String_Obj Parser::parse_ie_property() - { - lex< ie_property >(); - Token str(lexed); - const char* i = str.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); - if (!p) { - return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); - } - - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < str.end) { - p = find_first_in_interval< exactly, block_comment >(i, str.end); - if (p) { - if (i < p) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty - } - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside IE function " + str.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < str.end) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); - } - break; - } - } - return schema; - } - - String_Obj Parser::parse_ie_keyword_arg() - { - String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); - if (lex< variable >()) { - kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); - } else { - lex< alternatives< identifier_schema, identifier > >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - lex< exactly<'='> >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (peek< variable >()) kwd_arg->append(parse_list()); - else if (lex< number >()) { - std::string parsed(lexed); - Util::normalize_decimals(parsed); - kwd_arg->append(lexed_number(parsed)); - } - else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } - return kwd_arg; - } - - String_Schema_Obj Parser::parse_value_schema(const char* stop) - { - // initialize the string schema object to add tokens - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - - if (peek>()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - - const char* e; - const char* ee = end; - end = stop; - size_t num_items = 0; - bool need_space = false; - while (position < stop) { - // parse space between tokens - if (lex< spaces >() && num_items) { - need_space = true; - } - if (need_space) { - need_space = false; - // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - } - if ((e = peek< re_functional >()) && e < stop) { - schema->append(parse_function_call()); - } - // lex an interpolant /#{...}/ - else if (lex< exactly < hash_lbrace > >()) { - // Try to lex static expression first - if (peek< exactly< rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - Expression_Obj ex; - if (lex< re_static_expression >()) { - ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } else { - ex = parse_list(true); - } - ex->is_interpolant(true); - schema->append(ex); - if (!lex < exactly < rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - } - // lex some string constants or other valid token - // Note: [-+] chars are left over from i.e. `#{3}+3` - else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - // lex a quoted string - else if (lex< quoted_string >()) { - // need_space = true; - // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - // else need_space = true; - schema->append(parse_string()); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - if (peek < exactly < '-' > >()) break; - } - else if (lex< identifier >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - } - // lex (normalized) variable - else if (lex< variable >()) { - std::string name(Util::normalize_underscores(lexed)); - schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); - } - // lex percentage value - else if (lex< percentage >()) { - schema->append(lexed_percentage(lexed)); - } - // lex dimension value - else if (lex< dimension >()) { - schema->append(lexed_dimension(lexed)); - } - // lex number value - else if (lex< number >()) { - schema->append(lexed_number(lexed)); - } - // lex hex color value - else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { - schema->append(lexed_hex_color(lexed)); - } - else if (lex< sequence < exactly <'#'>, identifier > >()) { - schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); - } - // lex a value in parentheses - else if (peek< parenthese_scope >()) { - schema->append(parse_factor()); - } - else { - break; - } - ++num_items; - } - if (position != stop) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); - position = stop; - } - end = ee; - return schema; - } - - // this parses interpolation outside other strings - // means the result must not be quoted again later - String_Obj Parser::parse_identifier_schema() - { - Token id(lexed); - const char* i = id.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); - if (!p) { - return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < id.end) { - p = find_first_in_interval< exactly, block_comment >(i, id.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - const char* o = position; position = i; - schema->append(parse_value_schema(p)); - position = o; - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); - interp_node->is_interpolant(true); - schema->append(interp_node); - // schema->has_interpolants(true); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside interpolated identifier " + id.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < end) { - const char* o = position; position = i; - schema->append(parse_value_schema(id.end)); - position = o; - } - break; - } - } - return schema ? schema.detach() : 0; - } - - // calc functions should preserve arguments - Function_Call_Obj Parser::parse_calc_function() - { - lex< identifier >(); - std::string name(lexed); - ParserState call_pos = pstate; - lex< exactly<'('> >(); - ParserState arg_pos = pstate; - const char* arg_beg = position; - parse_list(); - const char* arg_end = position; - lex< skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > >(); - - Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); - args->append(arg); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - String_Obj Parser::parse_url_function_string() - { - std::string prefix(""); - if (lex< uri_prefix >()) { - prefix = std::string(lexed); - } - - lex < optional_spaces >(); - String_Obj url_string = parse_url_function_argument(); - - std::string suffix(""); - if (lex< real_uri_suffix >()) { - suffix = std::string(lexed); - } - - std::string uri(""); - if (url_string) { - uri = url_string->to_string({ NESTED, 5 }); - } - - if (String_Schema_Ptr schema = Cast(url_string)) { - String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); - res->append(schema); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); - return res; - } else { - std::string res = prefix + uri + suffix; - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - } - - String_Obj Parser::parse_url_function_argument() - { - const char* p = position; - - std::string uri(""); - if (lex< real_uri_value >(false)) { - uri = lexed.to_string(); - } - - if (peek< exactly< hash_lbrace > >()) { - const char* pp = position; - // TODO: error checking for unclosed interpolants - while (pp && peek< exactly< hash_lbrace > >(pp)) { - pp = sequence< interpolant, real_uri_value >(pp); - } - position = pp; - return parse_interpolated_chunk(Token(p, position)); - } - else if (uri != "") { - std::string res = Util::rtrim(uri); - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - - return 0; - } - - Function_Call_Obj Parser::parse_function_call() - { - lex< identifier >(); - std::string name(lexed); - - if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) - { error("Cannot call content-exists() except within a mixin."); } - - ParserState call_pos = pstate; - Arguments_Obj args = parse_arguments(); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - Function_Call_Schema_Obj Parser::parse_function_call_schema() - { - String_Obj name = parse_identifier_schema(); - ParserState source_position_of_call = pstate; - Arguments_Obj args = parse_arguments(); - - return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); - } - - Content_Obj Parser::parse_content_directive() - { - return SASS_MEMORY_NEW(Content, pstate); - } - - If_Obj Parser::parse_if_directive(bool else_if) - { - stack.push_back(Scope::Control); - ParserState if_source_position = pstate; - bool root = block_stack.back()->is_root(); - Expression_Obj predicate = parse_list(); - Block_Obj block = parse_block(root); - Block_Obj alternative = NULL; - - // only throw away comment if we parse a case - // we want all other comments to be parsed - if (lex_css< elseif_directive >()) { - alternative = SASS_MEMORY_NEW(Block, pstate); - alternative->append(parse_if_directive(true)); - } - else if (lex_css< kwd_else_directive >()) { - alternative = parse_block(root); - } - stack.pop_back(); - return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); - } - - For_Obj Parser::parse_for_directive() - { - stack.push_back(Scope::Control); - ParserState for_source_position = pstate; - bool root = block_stack.back()->is_root(); - lex_variable(); - std::string var(Util::normalize_underscores(lexed)); - if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); - Expression_Obj lower_bound = parse_expression(); - bool inclusive = false; - if (lex< kwd_through >()) inclusive = true; - else if (lex< kwd_to >()) inclusive = false; - else error("expected 'through' or 'to' keyword in @for directive"); - Expression_Obj upper_bound = parse_expression(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); - } - - // helper to parse a var token - Token Parser::lex_variable() - { - // peek for dollar sign first - if (!peek< exactly <'$'> >()) { - css_error("Invalid CSS", " after ", ": expected \"$\", was "); - } - // we expect a simple identifier as the call name - if (!lex< sequence < exactly <'$'>, identifier > >()) { - lex< exactly <'$'> >(); // move pstate and position up - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return token; - } - // helper to parse identifier - Token Parser::lex_identifier() - { - // we expect a simple identifier as the call name - if (!lex< identifier >()) { // ToDo: pstate wrong? - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return token; - } - - Each_Obj Parser::parse_each_directive() - { - stack.push_back(Scope::Control); - ParserState each_source_position = pstate; - bool root = block_stack.back()->is_root(); - std::vector vars; - lex_variable(); - vars.push_back(Util::normalize_underscores(lexed)); - while (lex< exactly<','> >()) { - if (!lex< variable >()) error("@each directive requires an iteration variable"); - vars.push_back(Util::normalize_underscores(lexed)); - } - if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); - Expression_Obj list = parse_list(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); - } - - // called after parsing `kwd_while_directive` - While_Obj Parser::parse_while_directive() - { - stack.push_back(Scope::Control); - bool root = block_stack.back()->is_root(); - // create the initial while call object - While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); - // parse mandatory predicate - Expression_Obj predicate = parse_list(); - List_Obj l = Cast(predicate); - if (!predicate || (l && !l->length())) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); - } - call->predicate(predicate); - // parse mandatory block - call->block(parse_block(root)); - // return ast node - stack.pop_back(); - // return ast node - return call.detach(); - } - - // EO parse_while_directive - Media_Block_Obj Parser::parse_media_block() - { - stack.push_back(Scope::Media); - Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); - - media_block->media_queries(parse_media_queries()); - - Media_Block_Obj prev_media_block = last_media_block; - last_media_block = media_block; - media_block->block(parse_css_block()); - last_media_block = prev_media_block; - stack.pop_back(); - return media_block.detach(); - } - - List_Obj Parser::parse_media_queries() - { - advanceToNextToken(); - List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); - if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); - while (lex_css < exactly <','> >()) queries->append(parse_media_query()); - queries->update_pstate(pstate); - return queries.detach(); - } - - // Expression_Ptr Parser::parse_media_query() - Media_Query_Obj Parser::parse_media_query() - { - advanceToNextToken(); - Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); - if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } - else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } - - if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); - else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); - else media_query->append(parse_media_expression()); - - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - if (lex < identifier_schema >()) { - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - schema->append(media_query->media_type()); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - schema->append(parse_identifier_schema()); - media_query->media_type(schema); - } - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - - media_query->update_pstate(pstate); - - return media_query; - } - - Media_Query_Expression_Obj Parser::parse_media_expression() - { - if (lex < identifier_schema >()) { - String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); - } - if (!lex_css< exactly<'('> >()) { - error("media query expression must begin with '('"); - } - Expression_Obj feature; - if (peek_css< exactly<')'> >()) { - error("media feature required in media query expression"); - } - feature = parse_expression(); - Expression_Obj expression = 0; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!lex_css< exactly<')'> >()) { - error("unclosed parenthesis in media query expression"); - } - return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); - } - - // lexed after `kwd_supports_directive` - // these are very similar to media blocks - Supports_Block_Obj Parser::parse_supports_directive() - { - Supports_Condition_Obj cond = parse_supports_condition(); - if (!cond) { - css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); - } - // create the ast node object for the support queries - Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); - // additional block is mandatory - // parse inner block - query->block(parse_block()); - // return ast node - return query; - } - - // parse one query operation - // may encounter nested queries - Supports_Condition_Obj Parser::parse_supports_condition() - { - lex < css_whitespace >(); - Supports_Condition_Obj cond; - if ((cond = parse_supports_negation())) return cond; - if ((cond = parse_supports_operator())) return cond; - if ((cond = parse_supports_interpolation())) return cond; - return cond; - } - - Supports_Condition_Obj Parser::parse_supports_negation() - { - if (!lex < kwd_not >()) return 0; - Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); - } - - Supports_Condition_Obj Parser::parse_supports_operator() - { - Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - if (cond.isNull()) return 0; - - while (true) { - Supports_Operator::Operand op = Supports_Operator::OR; - if (lex < kwd_and >()) { op = Supports_Operator::AND; } - else if(!lex < kwd_or >()) { break; } - - lex < css_whitespace >(); - Supports_Condition_Obj right = parse_supports_condition_in_parens(); - - // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); - cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); - } - return cond; - } - - Supports_Condition_Obj Parser::parse_supports_interpolation() - { - if (!lex < interpolant >()) return 0; - - String_Obj interp = parse_interpolated_chunk(lexed); - if (!interp) return 0; - - return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); - } - - // TODO: This needs some major work. Although feature conditions - // look like declarations their semantics differ significantly - Supports_Condition_Obj Parser::parse_supports_declaration() - { - Supports_Condition_Ptr cond; - // parse something declaration like - Expression_Obj feature = parse_expression(); - Expression_Obj expression = 0; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!feature || !expression) error("@supports condition expected declaration"); - cond = SASS_MEMORY_NEW(Supports_Declaration, - feature->pstate(), - feature, - expression); - // ToDo: maybe we need an additional error condition? - return cond; - } - - Supports_Condition_Obj Parser::parse_supports_condition_in_parens() - { - Supports_Condition_Obj interp = parse_supports_interpolation(); - if (interp != 0) return interp; - - if (!lex < exactly <'('> >()) return 0; - lex < css_whitespace >(); - - Supports_Condition_Obj cond = parse_supports_condition(); - if (cond != 0) { - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - } else { - cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - } - lex < css_whitespace >(); - return cond; - } - - At_Root_Block_Obj Parser::parse_at_root_block() - { - stack.push_back(Scope::AtRoot); - ParserState at_source_position = pstate; - Block_Obj body = 0; - At_Root_Query_Obj expr; - Lookahead lookahead_result; - if (lex_css< exactly<'('> >()) { - expr = parse_at_root_query(); - } - if (peek_css < exactly<'{'> >()) { - lex (); - body = parse_block(true); - } - else if ((lookahead_result = lookahead_for_selector(position)).found) { - Ruleset_Obj r = parse_ruleset(lookahead_result); - body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); - body->append(r); - } - At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); - if (!expr.isNull()) at_root->expression(expr); - stack.pop_back(); - return at_root; - } - - At_Root_Query_Obj Parser::parse_at_root_query() - { - if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); - - if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { - css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); - } - - Expression_Obj feature = parse_list(); - if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); - Expression_Obj expression = parse_list(); - List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); - - if (expression->concrete_type() == Expression::LIST) { - value = Cast(expression); - } - else value->append(expression); - - At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, - value->pstate(), - feature, - value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); - return cond; - } - - Directive_Obj Parser::parse_special_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - // this whole branch is never hit via spec tests - - Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - // this whole branch is never hit via spec tests - Directive_Obj Parser::parse_prefixed_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - - Directive_Obj Parser::parse_directive() - { - Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); - String_Schema_Obj val = parse_almost_any_value(); - // strip left and right if they are of type string - directive->value(val); - if (peek< exactly<'{'> >()) { - directive->block(parse_block()); - } - return directive; - } - - Expression_Obj Parser::lex_interpolation() - { - if (lex < interpolant >(true) != NULL) { - return parse_interpolated_chunk(lexed, true); - } - return 0; - } - - Expression_Obj Parser::lex_interp_uri() - { - // create a string schema by lexing optional interpolations - return lex_interp< re_string_uri_open, re_string_uri_close >(); - } - - Expression_Obj Parser::lex_interp_string() - { - Expression_Obj rv; - if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; - if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; - return rv; - } - - Expression_Obj Parser::lex_almost_any_value_chars() - { - const char* match = - lex < - one_plus < - alternatives < - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - sequence < - exactly < url_kwd >, - exactly <'('> - > - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - > - >(false); - if (match) { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - return NULL; - } - - Expression_Obj Parser::lex_almost_any_value_token() - { - Expression_Obj rv; - if (*position == 0) return 0; - if ((rv = lex_almost_any_value_chars())) return rv; - // if ((rv = lex_block_comment())) return rv; - // if ((rv = lex_single_line_comment())) return rv; - if ((rv = lex_interp_string())) return rv; - if ((rv = lex_interp_uri())) return rv; - if ((rv = lex_interpolation())) return rv; - if (lex< alternatives< hex, hex0 > >()) - { return lexed_hex_color(lexed); } - return rv; - } - - String_Schema_Obj Parser::parse_almost_any_value() - { - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (*position == 0) return 0; - lex < spaces >(false); - Expression_Obj token = lex_almost_any_value_token(); - if (!token) return 0; - schema->append(token); - if (*position == 0) { - schema->rtrim(); - return schema.detach(); - } - - while ((token = lex_almost_any_value_token())) { - schema->append(token); - } - - lex < css_whitespace >(); - - schema->rtrim(); - - return schema.detach(); - } - - Warning_Obj Parser::parse_warning() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); - } - - Error_Obj Parser::parse_error() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); - } - - Debug_Obj Parser::parse_debug() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); - } - - Return_Obj Parser::parse_return_directive() - { - // check that we do not have an empty list (ToDo: check if we got all cases) - if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) - { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - return SASS_MEMORY_NEW(Return, pstate, parse_list()); - } - - Lookahead Parser::lookahead_for_selector(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - rv.error = p; - if (const char* q = - peek < - re_selector_list - >(p) - ) { - bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; - bool could_be_escaped = false; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - // A property that's ambiguous with a nested selector is interpreted as a - // custom property. - if (*p == ':' && !could_be_escaped) { - rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); - } - could_be_escaped = *p == '\\'; - ++ p; - } - // store anyway } - - - // ToDo: remove - rv.error = q; - rv.position = q; - // check expected opening bracket - // only after successfull matching - if (peek < exactly<'{'> >(q)) rv.found = q; - // else if (peek < end_of_file >(q)) rv.found = q; - else if (peek < exactly<'('> >(q)) rv.found = q; - // else if (peek < exactly<';'> >(q)) rv.found = q; - // else if (peek < exactly<'}'> >(q)) rv.found = q; - if (rv.found || *p == 0) rv.error = 0; - } - - rv.parsable = ! rv.has_interpolants; - - // return result - return rv; - - } - // EO lookahead_for_selector - - // used in parse_block_nodes and parse_special_directive - // ToDo: actual usage is still not really clear to me? - Lookahead Parser::lookahead_for_include(const char* start) - { - // we actually just lookahead for a selector - Lookahead rv = lookahead_for_selector(start); - // but the "found" rules are different - if (const char* p = rv.position) { - // check for additional abort condition - if (peek < exactly<';'> >(p)) rv.found = p; - else if (peek < exactly<'}'> >(p)) rv.found = p; - } - // return result - return rv; - } - // EO lookahead_for_include - - // look ahead for a token with interpolation in it - // we mostly use the result if there is an interpolation - // everything that passes here gets parsed as one schema - // meaning it will not be parsed as a space separated list - Lookahead Parser::lookahead_for_value(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - if (const char* q = - peek < - non_greedy < - alternatives < - // consume whitespace - block_comment, // spaces, - // main tokens - sequence < - interpolant, - optional < - quoted_string - > - >, - identifier, - variable, - // issue #442 - sequence < - parenthese_scope, - interpolant, - optional < - quoted_string - > - > - >, - sequence < - // optional_spaces, - alternatives < - // end_of_file, - exactly<'{'>, - exactly<'}'>, - exactly<';'> - > - > - > - >(p) - ) { - if (p == q) return rv; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - ++ p; - } - // store anyway - // ToDo: remove - rv.position = q; - // check expected opening bracket - // only after successful matching - if (peek < exactly<'{'> >(q)) rv.found = q; - else if (peek < exactly<';'> >(q)) rv.found = q; - else if (peek < exactly<'}'> >(q)) rv.found = q; - } - - // return result - return rv; - } - // EO lookahead_for_value - - void Parser::read_bom() - { - size_t skip = 0; - std::string encoding; - bool utf_8 = false; - switch ((unsigned char) source[0]) { - case 0xEF: - skip = check_bom_chars(source, end, utf_8_bom, 3); - encoding = "UTF-8"; - utf_8 = true; - break; - case 0xFE: - skip = check_bom_chars(source, end, utf_16_bom_be, 2); - encoding = "UTF-16 (big endian)"; - break; - case 0xFF: - skip = check_bom_chars(source, end, utf_16_bom_le, 2); - skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); - encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); - break; - case 0x00: - skip = check_bom_chars(source, end, utf_32_bom_be, 4); - encoding = "UTF-32 (big endian)"; - break; - case 0x2B: - skip = check_bom_chars(source, end, utf_7_bom_1, 4) - | check_bom_chars(source, end, utf_7_bom_2, 4) - | check_bom_chars(source, end, utf_7_bom_3, 4) - | check_bom_chars(source, end, utf_7_bom_4, 4) - | check_bom_chars(source, end, utf_7_bom_5, 5); - encoding = "UTF-7"; - break; - case 0xF7: - skip = check_bom_chars(source, end, utf_1_bom, 3); - encoding = "UTF-1"; - break; - case 0xDD: - skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); - encoding = "UTF-EBCDIC"; - break; - case 0x0E: - skip = check_bom_chars(source, end, scsu_bom, 3); - encoding = "SCSU"; - break; - case 0xFB: - skip = check_bom_chars(source, end, bocu_1_bom, 3); - encoding = "BOCU-1"; - break; - case 0x84: - skip = check_bom_chars(source, end, gb_18030_bom, 4); - encoding = "GB-18030"; - break; - default: break; - } - if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); - position += skip; - } - - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) - { - size_t skip = 0; - if (src + len > end) return 0; - for (size_t i = 0; i < len; ++i, ++skip) { - if ((unsigned char) src[i] != bom[i]) return 0; - } - return skip; - } - - - Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) - { - for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); - } - return base; - } - - Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) - { - if (String_Schema_Ptr schema = Cast(base)) { - // return schema; - if (schema->has_interpolants()) { - if (i + 1 < operands.size() && ( - (ops[0].operand == Sass_OP::EQ) - || (ops[0].operand == Sass_OP::ADD) - || (ops[0].operand == Sass_OP::DIV) - || (ops[0].operand == Sass_OP::MUL) - || (ops[0].operand == Sass_OP::NEQ) - || (ops[0].operand == Sass_OP::LT) - || (ops[0].operand == Sass_OP::GT) - || (ops[0].operand == Sass_OP::LTE) - || (ops[0].operand == Sass_OP::GTE) - )) { - Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); - return rhs; - } - // return schema; - } - } - - for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema_Ptr schema = Cast(operands[i])) { - if (schema->has_interpolants()) { - if (i + 1 < S) { - // this whole branch is never hit via spec tests - Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); - return base; - } - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - return base; - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - Binary_Expression_Ptr b = Cast(base.ptr()); - if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { - base->is_delayed(true); - } - } - // nested binary expression are never to be delayed - if (Binary_Expression_Ptr b = Cast(base)) { - if (Cast(b->left())) base->set_delayed(false); - if (Cast(b->right())) base->set_delayed(false); - } - return base; - } - - void Parser::error(std::string msg, Position pos) - { - Position p(pos.line ? pos : before_token); - ParserState pstate(path, source, p, Offset(0, 0)); - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, msg); - } - - void Parser::error(std::string msg) - { - error(msg, pstate); - } - - // print a css parsing error with actual context information from parsed source - void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) - { - int max_len = 18; - const char* end = this->end; - while (*end != 0) ++ end; - const char* pos = peek < optional_spaces >(); - if (!pos) pos = position; - - const char* last_pos(pos); - if (last_pos > source) { - utf8::prior(last_pos, source); - } - // backup position to last significant char - while (trim && last_pos > source && last_pos < end) { - if (!Prelexer::is_space(*last_pos)) break; - utf8::prior(last_pos, source); - } - - bool ellipsis_left = false; - const char* pos_left(last_pos); - const char* end_left(last_pos); - - if (*pos_left) utf8::next(pos_left, end); - if (*end_left) utf8::next(end_left, end); - while (pos_left > source) { - if (utf8::distance(pos_left, end_left) >= max_len) { - utf8::prior(pos_left, source); - ellipsis_left = *(pos_left) != '\n' && - *(pos_left) != '\r'; - utf8::next(pos_left, end); - break; - } - - const char* prev = pos_left; - utf8::prior(prev, source); - if (*prev == '\r') break; - if (*prev == '\n') break; - pos_left = prev; - } - if (pos_left < source) { - pos_left = source; - } - - bool ellipsis_right = false; - const char* end_right(pos); - const char* pos_right(pos); - while (end_right < end) { - if (utf8::distance(pos_right, end_right) > max_len) { - ellipsis_left = *(pos_right) != '\n' && - *(pos_right) != '\r'; - break; - } - if (*end_right == '\r') break; - if (*end_right == '\n') break; - utf8::next(end_right, end); - } - // if (*end_right == 0) end_right ++; - - std::string left(pos_left, end_left); - std::string right(pos_right, end_right); - size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; - size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; - if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); - if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; - // Hotfix when source is null, probably due to interpolation parsing!? - if (source == NULL || *source == 0) source = pstate.src; - // now pass new message to the more generic error function - error(msg + prefix + quote(left) + middle + quote(right)); - } - -} diff --git a/src/libsass/parser.hpp b/src/libsass/parser.hpp deleted file mode 100644 index d2a6ddc1a..000000000 --- a/src/libsass/parser.hpp +++ /dev/null @@ -1,400 +0,0 @@ -#ifndef SASS_PARSER_H -#define SASS_PARSER_H - -#include -#include - -#include "ast.hpp" -#include "position.hpp" -#include "context.hpp" -#include "position.hpp" -#include "prelexer.hpp" - -#ifndef MAX_NESTING -// Note that this limit is not an exact science -// it depends on various factors, which some are -// not under our control (compile time or even OS -// dependent settings on the available stack size) -// It should fix most common segfault cases though. -#define MAX_NESTING 512 -#endif - -struct Lookahead { - const char* found; - const char* error; - const char* position; - bool parsable; - bool has_interpolants; - bool is_custom_property; -}; - -namespace Sass { - - class Parser : public ParserState { - public: - - enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; - - Context& ctx; - std::vector block_stack; - std::vector stack; - Media_Block_Ptr last_media_block; - const char* source; - const char* position; - const char* end; - Position before_token; - Position after_token; - ParserState pstate; - Backtraces traces; - size_t indentation; - size_t nestings; - - Token lexed; - - Parser(Context& ctx, const ParserState& pstate, Backtraces traces) - : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), - pstate(pstate), traces(traces), indentation(0), nestings(0) - { - stack.push_back(Scope::Root); - } - - // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); - static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); - // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); - -#ifdef __clang__ - - // lex and peak uses the template parameter to branch on the action, which - // triggers clangs tautological comparison on the single-comparison - // branches. This is not a bug, just a merging of behaviour into - // one function - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - -#endif - - - // skip current token and next whitespace - // moves ParserState right before next token - void advanceToNextToken(); - - bool peek_newline(const char* start = 0); - - // skip over spaces, tabs and line comments - template - const char* sneak(const char* start = 0) - { - using namespace Prelexer; - - // maybe use optional start position from arguments? - const char* it_position = start ? start : position; - - // skip white-space? - if (mx == spaces || - mx == no_spaces || - mx == css_comments || - mx == css_whitespace || - mx == optional_spaces || - mx == optional_css_comments || - mx == optional_css_whitespace - ) { - return it_position; - } - - // skip over spaces, tabs and sass line comments - const char* pos = optional_css_whitespace(it_position); - // always return a valid position - return pos ? pos : it_position; - - } - - // match will not skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* match(const char* start = 0) - { - // match the given prelexer - return mx(position); - } - - // peek will only skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* peek(const char* start = 0) - { - - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - const char* it_before_token = sneak < mx >(start); - - // match the given prelexer - const char* match = mx(it_before_token); - - // check if match is in valid range - return match <= end ? match : 0; - - } - - // white-space handling is built into the lexer - // this way you do not need to parse it yourself - // some matchers don't accept certain white-space - // we do not support start arg, since we manipulate - // sourcemap offset and we modify the position pointer! - // lex will only skip over space, tabs and line comment - template - const char* lex(bool lazy = true, bool force = false) - { - - if (*position == 0) return 0; - - // position considered before lexed token - // we can skip whitespace or comments for - // lazy developers (but we need control) - const char* it_before_token = position; - - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - if (lazy) it_before_token = sneak < mx >(position); - - // now call matcher to get position after token - const char* it_after_token = mx(it_before_token); - - // check if match is in valid range - if (it_after_token > end) return 0; - - // maybe we want to update the parser state anyway? - if (force == false) { - // assertion that we got a valid match - if (it_after_token == 0) return 0; - // assertion that we actually lexed something - if (it_after_token == it_before_token) return 0; - } - - // create new lexed token object (holds the parse results) - lexed = Token(position, it_before_token, it_after_token); - - // advance position (add whitespace before current token) - before_token = after_token.add(position, it_before_token); - - // update after_token position for current token - after_token.add(it_before_token, it_after_token); - - // ToDo: could probably do this incremetal on original object (API wants offset?) - pstate = ParserState(path, source, lexed, before_token, after_token - before_token); - - // advance internal char iterator - return position = it_after_token; - - } - - // lex_css skips over space, tabs, line and block comment - // all block comments will be consumed and thrown away - // source-map position will point to token after the comment - template - const char* lex_css() - { - // copy old token - Token prev = lexed; - // store previous pointer - const char* oldpos = position; - Position bt = before_token; - Position at = after_token; - ParserState op = pstate; - // throw away comments - // update srcmap position - lex < Prelexer::css_comments >(); - // now lex a new token - const char* pos = lex< mx >(); - // maybe restore prev state - if (pos == 0) { - pstate = op; - lexed = prev; - position = oldpos; - after_token = at; - before_token = bt; - } - // return match - return pos; - } - - // all block comments will be skipped and thrown away - template - const char* peek_css(const char* start = 0) - { - // now peek a token (skip comments first) - return peek< mx >(peek < Prelexer::css_comments >(start)); - } - -#ifdef __clang__ - -#pragma clang diagnostic pop - -#endif - - void error(std::string msg); - void error(std::string msg, Position pos); - // generate message with given and expected sample - // text before and in the middle are configurable - void css_error(const std::string& msg, - const std::string& prefix = " after ", - const std::string& middle = ", was: ", - const bool trim = true); - void read_bom(); - - Block_Obj parse(); - Import_Obj parse_import(); - Definition_Obj parse_definition(Definition::Type which_type); - Parameters_Obj parse_parameters(); - Parameter_Obj parse_parameter(); - Mixin_Call_Obj parse_include_directive(); - Arguments_Obj parse_arguments(); - Argument_Obj parse_argument(); - Assignment_Obj parse_assignment(); - Ruleset_Obj parse_ruleset(Lookahead lookahead); - Selector_List_Obj parse_selector_list(bool chroot); - Complex_Selector_Obj parse_complex_selector(bool chroot); - Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); - Compound_Selector_Obj parse_compound_selector(); - Simple_Selector_Obj parse_simple_selector(); - Wrapped_Selector_Obj parse_negated_selector(); - Simple_Selector_Obj parse_pseudo_selector(); - Attribute_Selector_Obj parse_attribute_selector(); - Block_Obj parse_block(bool is_root = false); - Block_Obj parse_css_block(bool is_root = false); - bool parse_block_nodes(bool is_root = false); - bool parse_block_node(bool is_root = false); - - bool parse_number_prefix(); - Declaration_Obj parse_declaration(); - Expression_Obj parse_map(); - Expression_Obj parse_bracket_list(); - Expression_Obj parse_list(bool delayed = false); - Expression_Obj parse_comma_list(bool delayed = false); - Expression_Obj parse_space_list(); - Expression_Obj parse_disjunction(); - Expression_Obj parse_conjunction(); - Expression_Obj parse_relation(); - Expression_Obj parse_expression(); - Expression_Obj parse_operators(); - Expression_Obj parse_factor(); - Expression_Obj parse_value(); - Function_Call_Obj parse_calc_function(); - Function_Call_Obj parse_function_call(); - Function_Call_Schema_Obj parse_function_call_schema(); - String_Obj parse_url_function_string(); - String_Obj parse_url_function_argument(); - String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); - String_Obj parse_string(); - Value_Obj parse_static_value(); - String_Schema_Obj parse_css_variable_value(bool top_level = true); - String_Schema_Obj parse_css_variable_value_token(bool top_level = true); - String_Obj parse_ie_property(); - String_Obj parse_ie_keyword_arg(); - String_Schema_Obj parse_value_schema(const char* stop); - String_Obj parse_identifier_schema(); - If_Obj parse_if_directive(bool else_if = false); - For_Obj parse_for_directive(); - Each_Obj parse_each_directive(); - While_Obj parse_while_directive(); - Return_Obj parse_return_directive(); - Content_Obj parse_content_directive(); - void parse_charset_directive(); - Media_Block_Obj parse_media_block(); - List_Obj parse_media_queries(); - Media_Query_Obj parse_media_query(); - Media_Query_Expression_Obj parse_media_expression(); - Supports_Block_Obj parse_supports_directive(); - Supports_Condition_Obj parse_supports_condition(); - Supports_Condition_Obj parse_supports_negation(); - Supports_Condition_Obj parse_supports_operator(); - Supports_Condition_Obj parse_supports_interpolation(); - Supports_Condition_Obj parse_supports_declaration(); - Supports_Condition_Obj parse_supports_condition_in_parens(); - At_Root_Block_Obj parse_at_root_block(); - At_Root_Query_Obj parse_at_root_query(); - String_Schema_Obj parse_almost_any_value(); - Directive_Obj parse_special_directive(); - Directive_Obj parse_prefixed_directive(); - Directive_Obj parse_directive(); - Warning_Obj parse_warning(); - Error_Obj parse_error(); - Debug_Obj parse_debug(); - - Value_Ptr color_or_string(const std::string& lexed) const; - - // be more like ruby sass - Expression_Obj lex_almost_any_value_token(); - Expression_Obj lex_almost_any_value_chars(); - Expression_Obj lex_interp_string(); - Expression_Obj lex_interp_uri(); - Expression_Obj lex_interpolation(); - - // these will throw errors - Token lex_variable(); - Token lex_identifier(); - - void parse_block_comments(); - - Lookahead lookahead_for_value(const char* start = 0); - Lookahead lookahead_for_selector(const char* start = 0); - Lookahead lookahead_for_include(const char* start = 0); - - Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); - Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); - - void throw_syntax_error(std::string message, size_t ln = 0); - void throw_read_error(std::string message, size_t ln = 0); - - - template - Expression_Obj lex_interp() - { - if (lex < open >(false)) { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - Expression_Obj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - while (lex < close >(false)) { - // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - Expression_Obj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - } else { - return schema; - } - } - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - return 0; - } - - public: - static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); - static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); - static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); - static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); - private: - Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; - Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; - Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; - Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; - - static const char* re_attr_sensitive_close(const char* src); - static const char* re_attr_insensitive_close(const char* src); - - }; - - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); -} - -#endif diff --git a/src/libsass/plugins.md b/src/libsass/plugins.md deleted file mode 100644 index a9711e3e1..000000000 --- a/src/libsass/plugins.md +++ /dev/null @@ -1,47 +0,0 @@ -Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). - -## plugin.cpp - -```C++ -#include -#include -#include -#include "sass_values.h" - -union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) -{ - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); -} - -extern "C" const char* ADDCALL libsass_get_version() { - return libsass_version(); -} - -extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() -{ - // allocate a custom function caller - Sass_C_Function_Callback fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - // create list of all custom functions - Sass_C_Function_List fn_list = sass_make_function_list(1); - // put the only function in this plugin to the list - sass_function_set_list_entry(fn_list, 0, fn_foo); - // return the list - return fn_list; -} -``` - -To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). - -## Compile with gcc on linux - -```bash -g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass -``` - -## Compile with mingw on windows - -```bash -g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass -``` diff --git a/src/libsass/prelexer.cpp b/src/libsass/prelexer.cpp deleted file mode 100644 index a43b1ee3c..000000000 --- a/src/libsass/prelexer.cpp +++ /dev/null @@ -1,1774 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include "util.hpp" -#include "position.hpp" -#include "prelexer.hpp" -#include "constants.hpp" - - -namespace Sass { - // using namespace Lexer; - using namespace Constants; - - namespace Prelexer { - - - /* - - def string_re(open, close) - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - end - end - - # A hash of regular expressions that are used for tokenizing strings. - # - # The key is a `[Symbol, Boolean]` pair. - # The symbol represents which style of quotation to use, - # while the boolean represents whether or not the string - # is following an interpolated segment. - STRING_REGULAR_EXPRESSIONS = { - :double => { - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - false => string_re('"', '"'), - true => string_re('', '"') - }, - :single => { - false => string_re("'", "'"), - true => string_re('', "'") - }, - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a - # non-standard version of http://www.w3.org/TR/css3-conditional/ - :url_prefix => { - false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - :domain => { - false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - } - } - */ - - /* - /#{open} - ( - \\. - | - \# (?!\{) - | - [^#{close}\\#] - )* - (#{close}|#\{) - /m - false => string_re('"', '"'), - true => string_re('', '"') - */ - extern const char string_double_negates[] = "\"\\#"; - const char* re_string_double_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_double_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'"'>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - extern const char string_single_negates[] = "'\\#"; - const char* re_string_single_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_single_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'\''>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - /* - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - */ - const char* re_string_uri_close(const char* src) - { - return sequence < - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < optional < W >, exactly <')'> >, - lookahead < exactly< hash_lbrace > > - > - >, - optional < - sequence < optional < W >, exactly <')'> > - > - >(src); - } - - const char* re_string_uri_open(const char* src) - { - return sequence < - exactly <'u'>, - exactly <'r'>, - exactly <'l'>, - exactly <'('>, - W, - alternatives< - quoted_string, - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < W, exactly <')'> >, - exactly< hash_lbrace > - > - > - > - >(src); - } - - // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. - const char* line_comment(const char* src) - { - return sequence< - exactly < - slash_slash - >, - non_greedy< - any_char, - end_of_line - > - >(src); - } - - // Match a block comment. - const char* block_comment(const char* src) - { - return sequence< - delimited_by< - slash_star, - star_slash, - false - > - >(src); - } - /* not use anymore - remove? - const char* block_comment_prefix(const char* src) { - return exactly(src); - } - // Match either comment. - const char* comment(const char* src) { - return line_comment(src); - } - */ - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_whitespace(const char* src) { - return one_plus< alternatives >(src); - } - // Match optional_css_whitepace plus block_comments - const char* optional_css_comments(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_comments(const char* src) { - return one_plus< alternatives >(src); - } - - // Match one backslash escaped char /\\./ - const char* escape_seq(const char* src) - { - return sequence< - exactly<'\\'>, - alternatives < - minmax_range< - 1, 3, xdigit - >, - any_char - >, - optional < - exactly <' '> - > - >(src); - } - - // Match identifier start - const char* identifier_alpha(const char* src) - { - return alternatives< - unicode_seq, - alpha, - unicode, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match identifier after start - const char* identifier_alnum(const char* src) - { - return alternatives< - unicode_seq, - alnum, - unicode, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match CSS identifiers. - const char* strict_identifier(const char* src) - { - return sequence< - one_plus < strict_identifier_alpha >, - zero_plus < strict_identifier_alnum > - // word_boundary not needed - >(src); - } - - // Match CSS identifiers. - const char* identifier(const char* src) - { - return sequence< - zero_plus< exactly<'-'> >, - one_plus < identifier_alpha >, - zero_plus < identifier_alnum > - // word_boundary not needed - >(src); - } - - const char* strict_identifier_alpha(const char* src) - { - return alternatives < - alpha, - unicode, - escape_seq, - exactly<'_'> - >(src); - } - - const char* strict_identifier_alnum(const char* src) - { - return alternatives < - alnum, - unicode, - escape_seq, - exactly<'_'> - >(src); - } - - // Match a single CSS unit - const char* one_unit(const char* src) - { - return sequence < - optional < exactly <'-'> >, - strict_identifier_alpha, - zero_plus < alternatives< - strict_identifier_alnum, - sequence < - one_plus < exactly<'-'> >, - strict_identifier_alpha - > - > > - >(src); - } - - // Match numerator/denominator CSS units - const char* multiple_units(const char* src) - { - return - sequence < - one_unit, - zero_plus < - sequence < - exactly <'*'>, - one_unit - > - > - >(src); - } - - // Match complex CSS unit identifiers - const char* unit_identifier(const char* src) - { - return sequence < - multiple_units, - optional < - sequence < - exactly <'/'>, - negate < sequence < - exactly < calc_fn_kwd >, - exactly < '(' > - > >, - multiple_units - > > - >(src); - } - - const char* identifier_alnums(const char* src) - { - return one_plus< identifier_alnum >(src); - } - - // Match number prefix ([\+\-]+) - const char* number_prefix(const char* src) { - return alternatives < - exactly < '+' >, - sequence < - exactly < '-' >, - optional_css_whitespace, - exactly< '-' > - > - >(src); - } - - // Match interpolant schemas - const char* identifier_schema(const char* src) { - - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - sequence < - optional < - exactly <'$'> - >, - identifier - >, - exactly <'-'> - > - >, - interpolant, - zero_plus < - alternatives < - digits, - sequence < - optional < - exactly <'$'> - >, - identifier - >, - quoted_string, - exactly<'-'> - > - > - > - >, - negate < - exactly<'%'> - > - > (src); - } - - // interpolants can be recursive/nested - const char* interpolant(const char* src) { - return recursive_scopes< exactly, exactly >(src); - } - - // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ - const char* single_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'\''>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '\'' > - > - >, - exactly <'\''> - >(src); - } - - // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ - const char* double_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'"'>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '"' > - > - >, - exactly <'"'> - >(src); - } - - // $re_quoted = /(?:$re_squote|$re_dquote)/ - const char* quoted_string(const char* src) { - // match a quoted string, while skipping interpolants - return alternatives< - single_quoted_string, - double_quoted_string - >(src); - } - - const char* sass_value(const char* src) { - return alternatives < - quoted_string, - identifier, - percentage, - hex, - dimension, - number - >(src); - } - - // this is basically `one_plus < sass_value >` - // takes care to not parse invalid combinations - const char* value_combinations(const char* src) { - // `2px-2px` is invalid combo - bool was_number = false; - const char* pos; - while (src) { - if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { - was_number = false; - src = pos; - } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { - was_number = true; - src = pos; - } else { - break; - } - } - return src; - } - - // must be at least one interpolant - // can be surrounded by sass values - // make sure to never parse (dim)(dim) - // since this wrongly consumes `2px-1px` - // `2px1px` is valid number (unit `px1px`) - const char* value_schema(const char* src) - { - return sequence < - one_plus < - sequence < - optional < value_combinations >, - interpolant, - optional < value_combinations > - > - > - >(src); - } - - // Match CSS '@' keywords. - const char* at_keyword(const char* src) { - return sequence, identifier>(src); - } - - /* - tok(%r{ - ( - \\. - | - (?!url\() - [^"'/\#!;\{\}] # " - | - /(?![\*\/]) - | - \#(?!\{) - | - !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. - )+ - }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || - interpolation(:warn_for_color) - */ - const char* re_almost_any_value_token(const char* src) { - - return alternatives < - one_plus < - alternatives < - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - uri_prefix - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - >, - block_comment, - line_comment, - interpolant, - space, - sequence < - exactly<'u'>, - exactly<'r'>, - exactly<'l'>, - exactly<'('>, - zero_plus < - alternatives < - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - > - >, - // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - exactly<')'> - > - >(src); - } - - /* - DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, - :each, :while, :if, :else, :extend, :import, :media, :charset, :content, - :_moz_document, :at_root, :error] - */ - const char* re_special_directive(const char* src) { - return alternatives < - word < mixin_kwd >, - word < include_kwd >, - word < function_kwd >, - word < return_kwd >, - word < debug_kwd >, - word < warn_kwd >, - word < for_kwd >, - word < each_kwd >, - word < while_kwd >, - word < if_kwd >, - word < else_kwd >, - word < extend_kwd >, - word < import_kwd >, - word < media_kwd >, - word < charset_kwd >, - word < content_kwd >, - // exactly < moz_document_kwd >, - word < at_root_kwd >, - word < error_kwd > - >(src); - } - - const char* re_prefixed_directive(const char* src) { - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < alnum >, - exactly <'-'> - > - >, - exactly < supports_kwd > - >(src); - } - - const char* re_reference_combinator(const char* src) { - return sequence < - optional < - sequence < - zero_plus < - exactly <'-'> - >, - identifier, - exactly <'|'> - > - >, - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* static_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - re_reference_combinator, - exactly <'/'> - >(src); - } - - const char* schema_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - optional < - sequence < - css_ip_identifier, - exactly <'|'> - > - >, - css_ip_identifier, - exactly <'/'> - > (src); - } - - const char* kwd_import(const char* src) { - return word(src); - } - - const char* kwd_at_root(const char* src) { - return word(src); - } - - const char* kwd_with_directive(const char* src) { - return word(src); - } - - const char* kwd_without_directive(const char* src) { - return word(src); - } - - const char* kwd_media(const char* src) { - return word(src); - } - - const char* kwd_supports_directive(const char* src) { - return word(src); - } - - const char* kwd_mixin(const char* src) { - return word(src); - } - - const char* kwd_function(const char* src) { - return word(src); - } - - const char* kwd_return_directive(const char* src) { - return word(src); - } - - const char* kwd_include_directive(const char* src) { - return word(src); - } - - const char* kwd_content_directive(const char* src) { - return word(src); - } - - const char* kwd_charset_directive(const char* src) { - return word(src); - } - - const char* kwd_extend(const char* src) { - return word(src); - } - - - const char* kwd_if_directive(const char* src) { - return word(src); - } - - const char* kwd_else_directive(const char* src) { - return word(src); - } - const char* elseif_directive(const char* src) { - return sequence< exactly< else_kwd >, - optional_css_comments, - word< if_after_else_kwd > >(src); - } - - const char* kwd_for_directive(const char* src) { - return word(src); - } - - const char* kwd_from(const char* src) { - return word(src); - } - - const char* kwd_to(const char* src) { - return word(src); - } - - const char* kwd_through(const char* src) { - return word(src); - } - - const char* kwd_each_directive(const char* src) { - return word(src); - } - - const char* kwd_in(const char* src) { - return word(src); - } - - const char* kwd_while_directive(const char* src) { - return word(src); - } - - const char* name(const char* src) { - return one_plus< alternatives< alnum, - exactly<'-'>, - exactly<'_'>, - escape_seq > >(src); - } - - const char* kwd_warn(const char* src) { - return word(src); - } - - const char* kwd_err(const char* src) { - return word(src); - } - - const char* kwd_dbg(const char* src) { - return word(src); - } - - /* not used anymore - remove? - const char* directive(const char* src) { - return sequence< exactly<'@'>, identifier >(src); - } */ - - const char* kwd_null(const char* src) { - return word(src); - } - - const char* css_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* css_ip_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - alternatives < - identifier, - interpolant - > - >(src); - } - - // Match CSS type selectors - const char* namespace_prefix(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_identifier - > - >, - exactly <'|'>, - negate < - exactly <'='> - > - >(src); - } - - // Match CSS type selectors - const char* namespace_schema(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_ip_identifier - > - >, - exactly<'|'>, - negate < - exactly <'='> - > - >(src); - } - - const char* hyphens_and_identifier(const char* src) { - return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); - } - const char* hyphens_and_name(const char* src) { - return sequence< zero_plus< exactly< '-' > >, name >(src); - } - const char* universal(const char* src) { - return sequence< optional, exactly<'*'> >(src); - } - // Match CSS id names. - const char* id_name(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS class names. - const char* class_name(const char* src) { - return sequence, identifier >(src); - } - // Attribute name in an attribute selector. - const char* attribute_name(const char* src) { - return alternatives< sequence< optional, identifier>, - identifier >(src); - } - // match placeholder selectors - const char* placeholder(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS numeric constants. - - const char* op(const char* src) { - return class_char(src); - } - const char* sign(const char* src) { - return class_char(src); - } - const char* unsigned_number(const char* src) { - return alternatives, - exactly<'.'>, - one_plus >, - digits>(src); - } - const char* number(const char* src) { - return sequence< - optional, - unsigned_number, - optional< - sequence< - exactly<'e'>, - optional, - unsigned_number - > - > - >(src); - } - const char* coefficient(const char* src) { - return alternatives< sequence< optional, digits >, - sign >(src); - } - const char* binomial(const char* src) { - return sequence < - optional < sign >, - optional < digits >, - exactly <'n'>, - zero_plus < sequence < - optional_css_whitespace, sign, - optional_css_whitespace, digits - > > - >(src); - } - const char* percentage(const char* src) { - return sequence< number, exactly<'%'> >(src); - } - const char* ampersand(const char* src) { - return exactly<'&'>(src); - } - - /* not used anymore - remove? - const char* em(const char* src) { - return sequence< number, exactly >(src); - } */ - const char* dimension(const char* src) { - return sequence(src); - } - const char* hex(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 4 && len != 7) ? 0 : p; - } - const char* hexa(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 9) ? 0 : p; - } - const char* hex0(const char* src) { - const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 8) ? 0 : p; - } - - /* no longer used - remove? - const char* rgb_prefix(const char* src) { - return word(src); - }*/ - // Match CSS uri specifiers. - - const char* uri_prefix(const char* src) { - return sequence < - exactly < - url_kwd - >, - zero_plus < - sequence < - exactly <'-'>, - one_plus < - alpha - > - > - >, - exactly <'('> - >(src); - } - - // TODO: rename the following two functions - /* no longer used - remove? - const char* uri(const char* src) { - return sequence< exactly, - optional, - quoted_string, - optional, - exactly<')'> >(src); - }*/ - /* no longer used - remove? - const char* url_value(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename - optional< exactly<'/'> > >(src); - }*/ - /* no longer used - remove? - const char* url_schema(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - filename_schema >(src); // optional trailing slash - }*/ - // Match CSS "!important" keyword. - const char* kwd_important(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!default" keyword. - const char* default_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!global" keyword. - const char* global_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS pseudo-class/element prefixes. - const char* pseudo_prefix(const char* src) { - return sequence< exactly<':'>, optional< exactly<':'> > >(src); - } - // Match CSS function call openers. - const char* functional_schema(const char* src) { - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - identifier, - exactly <'-'> - > - >, - one_plus < - sequence < - interpolant, - alternatives < - digits, - identifier, - exactly<'+'>, - exactly<'-'> - > - > - > - > - >, - negate < - exactly <'%'> - >, - lookahead < - exactly <'('> - > - > (src); - } - - const char* re_nothing(const char* src) { - return src; - } - - const char* re_functional(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - const char* re_pseudo_selector(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - // Match the CSS negation pseudo-class. - const char* pseudo_not(const char* src) { - return word< pseudo_not_fn_kwd >(src); - } - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src) { - return word(src); - } - const char* odd(const char* src) { - return word(src); - } - // Match CSS attribute-matching operators. - const char* exact_match(const char* src) { return exactly<'='>(src); } - const char* class_match(const char* src) { return exactly(src); } - const char* dash_match(const char* src) { return exactly(src); } - const char* prefix_match(const char* src) { return exactly(src); } - const char* suffix_match(const char* src) { return exactly(src); } - const char* substring_match(const char* src) { return exactly(src); } - // Match CSS combinators. - /* not used anymore - remove? - const char* adjacent_to(const char* src) { - return sequence< optional_spaces, exactly<'+'> >(src); - } - const char* precedes(const char* src) { - return sequence< optional_spaces, exactly<'~'> >(src); - } - const char* parent_of(const char* src) { - return sequence< optional_spaces, exactly<'>'> >(src); - } - const char* ancestor_of(const char* src) { - return sequence< spaces, negate< exactly<'{'> > >(src); - }*/ - - // Match SCSS variable names. - const char* variable(const char* src) { - return sequence, identifier>(src); - } - - // parse `calc`, `-a-calc` and `--b-c-calc` - // but do not parse `foocalc` or `foo-calc` - const char* calc_fn_call(const char* src) { - return sequence < - optional < sequence < - hyphens, - one_plus < sequence < - strict_identifier, - hyphens - > > - > >, - exactly < calc_fn_kwd >, - word_boundary - >(src); - } - - // Match Sass boolean keywords. - const char* kwd_true(const char* src) { - return word(src); - } - const char* kwd_false(const char* src) { - return word(src); - } - const char* kwd_only(const char* src) { - return keyword < only_kwd >(src); - } - const char* kwd_and(const char* src) { - return keyword < and_kwd >(src); - } - const char* kwd_or(const char* src) { - return keyword < or_kwd >(src); - } - const char* kwd_not(const char* src) { - return keyword < not_kwd >(src); - } - const char* kwd_eq(const char* src) { - return exactly(src); - } - const char* kwd_neq(const char* src) { - return exactly(src); - } - const char* kwd_gt(const char* src) { - return exactly(src); - } - const char* kwd_gte(const char* src) { - return exactly(src); - } - const char* kwd_lt(const char* src) { - return exactly(src); - } - const char* kwd_lte(const char* src) { - return exactly(src); - } - - // match specific IE syntax - const char* ie_progid(const char* src) { - return sequence < - word, - exactly<':'>, - alternatives< identifier_schema, identifier >, - zero_plus< sequence< - exactly<'.'>, - alternatives< identifier_schema, identifier > - > >, - zero_plus < sequence< - exactly<'('>, - optional_css_whitespace, - optional < sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, - zero_plus< sequence< - optional_css_whitespace, - exactly<','>, - optional_css_whitespace, - sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > - > - > > - > >, - optional_css_whitespace, - exactly<')'> - > > - >(src); - } - const char* ie_expression(const char* src) { - return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); - } - const char* ie_property(const char* src) { - return alternatives < ie_expression, ie_progid >(src); - } - - // const char* ie_args(const char* src) { - // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, - // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); - // } - - const char* ie_keyword_arg_property(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier - >(src); - } - const char* ie_keyword_arg_value(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier, - quoted_string, - number, - hex, - hexa, - sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - > - >(src); - } - - const char* ie_keyword_arg(const char* src) { - return sequence < - ie_keyword_arg_property, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - ie_keyword_arg_value - >(src); - } - - // Path matching functions. - /* not used anymore - remove? - const char* folder(const char* src) { - return sequence< zero_plus< any_char_except<'/'> >, - exactly<'/'> >(src); - } - const char* folders(const char* src) { - return zero_plus< folder >(src); - }*/ - /* not used anymore - remove? - const char* chunk(const char* src) { - char inside_str = 0; - const char* p = src; - size_t depth = 0; - while (true) { - if (!*p) { - return 0; - } - else if (!inside_str && (*p == '"' || *p == '\'')) { - inside_str = *p; - } - else if (*p == inside_str && *(p-1) != '\\') { - inside_str = 0; - } - else if (*p == '(' && !inside_str) { - ++depth; - } - else if (*p == ')' && !inside_str) { - if (depth == 0) return p; - else --depth; - } - ++p; - } - // unreachable - return 0; - } - */ - - // follow the CSS spec more closely and see if this helps us scan URLs correctly - /* not used anymore - remove? - const char* NL(const char* src) { - return alternatives< exactly<'\n'>, - sequence< exactly<'\r'>, exactly<'\n'> >, - exactly<'\r'>, - exactly<'\f'> >(src); - }*/ - - const char* H(const char* src) { - return std::isxdigit(*src) ? src+1 : 0; - } - - const char* W(const char* src) { - return zero_plus< alternatives< - space, - exactly< '\t' >, - exactly< '\r' >, - exactly< '\n' >, - exactly< '\f' > - > >(src); - } - - const char* UUNICODE(const char* src) { - return sequence< exactly<'\\'>, - between, - optional< W > - >(src); - } - - const char* NONASCII(const char* src) { - return nonascii(src); - } - - const char* ESCAPE(const char* src) { - return alternatives< - UUNICODE, - sequence< - exactly<'\\'>, - alternatives< - NONASCII, - escapable_character - > - > - >(src); - } - - const char* list_terminator(const char* src) { - return alternatives < - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<']'>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - >(src); - }; - - const char* space_list_terminator(const char* src) { - return alternatives < - exactly<','>, - list_terminator - >(src); - }; - - - // const char* real_uri_prefix(const char* src) { - // return alternatives< - // exactly< url_kwd >, - // exactly< url_prefix_kwd > - // >(src); - // } - - const char* real_uri(const char* src) { - return sequence< - exactly< url_kwd >, - exactly< '(' >, - W, - real_uri_value, - exactly< ')' > - >(src); - } - - const char* real_uri_suffix(const char* src) { - return sequence< W, exactly< ')' > >(src); - } - - const char* real_uri_value(const char* src) { - return - sequence< - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - real_uri_suffix, - exactly< hash_lbrace > - > - > - > - (src); - } - - const char* static_string(const char* src) { - const char* pos = src; - const char * s = quoted_string(pos); - Token t(pos, s); - const unsigned int p = count_interval< interpolant >(t.begin, t.end); - return (p == 0) ? t.end : 0; - } - - const char* unicode_seq(const char* src) { - return sequence < - alternatives < - exactly< 'U' >, - exactly< 'u' > - >, - exactly< '+' >, - padded_token < - 6, xdigit, - exactly < '?' > - > - >(src); - } - - const char* static_component(const char* src) { - return alternatives< identifier, - static_string, - percentage, - hex, - hexa, - exactly<'|'>, - // exactly<'+'>, - sequence < number, unit_identifier >, - number, - sequence< exactly<'!'>, word > - >(src); - } - - const char* static_property(const char* src) { - return - sequence < - zero_plus< - sequence < - optional_css_comments, - alternatives < - exactly<','>, - exactly<'('>, - exactly<')'>, - kwd_optional, - quoted_string, - interpolant, - identifier, - percentage, - dimension, - variable, - alnum, - sequence < - exactly <'\\'>, - any_char - > - > - > - >, - lookahead < - sequence < - optional_css_comments, - alternatives < - exactly <';'>, - exactly <'}'>, - end_of_file - > - > - > - >(src); - } - - const char* static_value(const char* src) { - return sequence< sequence< - static_component, - zero_plus< identifier > - >, - zero_plus < sequence< - alternatives< - sequence< optional_spaces, alternatives< - exactly < '/' >, - exactly < ',' >, - exactly < ' ' > - >, optional_spaces >, - spaces - >, - static_component - > >, - zero_plus < spaces >, - alternatives< exactly<';'>, exactly<'}'> > - >(src); - } - - extern const char css_variable_url_negates[] = "()[]{}\"'#/"; - const char* css_variable_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; - const char* css_variable_top_level_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_top_level_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - const char* parenthese_scope(const char* src) { - return sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - >(src); - } - - const char* re_selector_list(const char* src) { - return alternatives < - // partial bem selector - sequence < - ampersand, - one_plus < - exactly < '-' > - >, - word_boundary, - optional_spaces - >, - // main selector matching - one_plus < - alternatives < - // consume whitespace and comments - spaces, block_comment, line_comment, - // match `/deep/` selector (pass-trough) - // there is no functionality for it yet - schema_reference_combinator, - // match selector ops /[*&%,\[\]]/ - class_char < selector_lookahead_ops >, - // match selector combinators /[>+~]/ - class_char < selector_combinator_ops >, - // match pseudo selectors - sequence < - exactly <'('>, - optional_spaces, - optional , - optional_spaces, - exactly <')'> - >, - // match attribute compare operators - alternatives < - exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match - >, - // main selector match - sequence < - // allow namespace prefix - optional < namespace_schema >, - // modifiers prefixes - alternatives < - sequence < - exactly <'#'>, - // not for interpolation - negate < exactly <'{'> > - >, - // class match - exactly <'.'>, - // single or double colon - sequence < - optional < pseudo_prefix >, - // fix libsass issue 2376 - negate < uri_prefix > - > - >, - // accept hypens in token - one_plus < sequence < - // can start with hyphens - zero_plus < - sequence < - exactly <'-'>, - optional_spaces - > - >, - // now the main token - alternatives < - kwd_optional, - exactly <'*'>, - quoted_string, - interpolant, - identifier, - variable, - percentage, - binomial, - dimension, - alnum - > - > >, - // can also end with hyphens - zero_plus < exactly<'-'> > - > - > - > - >(src); - } - - const char* type_selector(const char* src) { - return sequence< optional, identifier>(src); - } - const char* re_type_selector(const char* src) { - return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); - } - const char* re_static_expression(const char* src) { - return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); - } - - // lexer special_fn: these functions cannot be overloaded - // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) - const char* re_special_fun(const char* src) { - - // match this first as we test prefix hyphens - if (const char* calc = calc_fn_call(src)) { - return calc; - } - - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < - alternatives < - alpha, - exactly <'+'>, - exactly <'-'> - > - > - > - >, - alternatives < - word < expression_kwd >, - sequence < - sequence < - exactly < progid_kwd >, - exactly <':'> - >, - zero_plus < - alternatives < - char_range <'a', 'z'>, - exactly <'.'> - > - > - > - > - >(src); - } - - } -} diff --git a/src/libsass/sass.cpp b/src/libsass/sass.cpp deleted file mode 100644 index 72edd7ced..000000000 --- a/src/libsass/sass.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include - -#include "sass.h" -#include "file.hpp" -#include "util.hpp" -#include "sass_context.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - // helper to convert string list to vector - std::vector list2vec(struct string_list* cur) - { - std::vector list; - while (cur) { - list.push_back(cur->string); - cur = cur->next; - } - return list; - } - -} - -extern "C" { - using namespace Sass; - - // Allocate libsass heap memory - // Don't forget string termination! - void* ADDCALL sass_alloc_memory(size_t size) - { - void* ptr = malloc(size); - if (ptr == NULL) { - std::cerr << "Out of memory.\n"; - exit(EXIT_FAILURE); - } - return ptr; - } - - char* ADDCALL sass_copy_c_string(const char* str) - { - size_t len = strlen(str) + 1; - char* cpy = (char*) sass_alloc_memory(len); - std::memcpy(cpy, str, len); - return cpy; - } - - // Deallocate libsass heap memory - void ADDCALL sass_free_memory(void* ptr) - { - if (ptr) free (ptr); - } - - // caller must free the returned memory - char* ADDCALL sass_string_quote (const char *str, const char quote_mark) - { - std::string quoted = quote(str, quote_mark); - return sass_copy_c_string(quoted.c_str()); - } - - // caller must free the returned memory - char* ADDCALL sass_string_unquote (const char *str) - { - std::string unquoted = unquote(str); - return sass_copy_c_string(unquoted.c_str()); - } - - char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const std::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - std::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - std::string resolved(File::find_include(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const std::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - std::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - std::string resolved(File::find_file(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - // this has the original resolve logic for sass include - char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) - { - std::vector vec(list2vec(opt->include_paths)); - std::string resolved(File::find_include(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) - { - std::vector vec(list2vec(opt->include_paths)); - std::string resolved(File::find_file(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Get compiled libsass version - const char* ADDCALL libsass_version(void) - { - return LIBSASS_VERSION; - } - - // Get compiled libsass version - const char* ADDCALL libsass_language_version(void) - { - return LIBSASS_LANGUAGE_VERSION; - } - -} - -namespace Sass { - - // helper to aid dreaded MSVC debug mode - char* sass_copy_string(std::string str) - { - // In MSVC the following can lead to segfault: - // sass_copy_c_string(stream.str().c_str()); - // Reason is that the string returned by str() is disposed before - // sass_copy_c_string is invoked. The string is actually a stack - // object, so indeed nobody is holding on to it. So it seems - // perfectly fair to release it right away. So the const char* - // by c_str will point to invalid memory. I'm not sure if this is - // the behavior for all compiler, but I'm pretty sure we would - // have gotten more issues reported if that would be the case. - // Wrapping it in a functions seems the cleanest approach as the - // function must hold on to the stack variable until it's done. - return sass_copy_c_string(str.c_str()); - } - -} \ No newline at end of file diff --git a/src/libsass/sass2scss.cpp b/src/libsass/sass2scss.cpp deleted file mode 100644 index 8645d0c37..000000000 --- a/src/libsass/sass2scss.cpp +++ /dev/null @@ -1,895 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#define _CRT_NONSTDC_NO_DEPRECATE -#endif - -// include library -#include -#include -#include -#include -#include -#include -#include - -///* -// -// src comments: comments in sass syntax (staring with //) -// css comments: multiline comments in css syntax (starting with /*) -// -// KEEP_COMMENT: keep src comments in the resulting css code -// STRIP_COMMENT: strip out all comments (either src or css) -// CONVERT_COMMENT: convert all src comments to css comments -// -//*/ - -// our own header -#include "sass2scss.h" - -// add namespace for c++ -namespace Sass -{ - - // return the actual prettify value from options - #define PRETTIFY(converter) (converter.options - (converter.options & 248)) - // query the options integer to check if the option is enables - #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) - #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) - #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) - - // some makros to access the indentation stack - #define INDENT(converter) (converter.indents.top()) - - // some makros to query comment parser status - #define IS_PARSING(converter) (converter.comment == "") - #define IS_COMMENT(converter) (converter.comment != "") - #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) - #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) - - // pretty printer helper function - static std::string closer (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " }" : - PRETTIFY(converter) <= 1 ? " }" : - "\n" + INDENT(converter) + "}"; - } - - // pretty printer helper function - static std::string opener (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " { " : - PRETTIFY(converter) <= 2 ? " {" : - "\n" + INDENT(converter) + "{"; - } - - // check if the given string is a pseudo selector - // needed to differentiate from sass property syntax - static bool isPseudoSelector (std::string& sel) - { - - size_t len = sel.length(); - if (len < 1) return false; - size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); - if (pos != std::string::npos) sel.erase(pos, std::string::npos); - size_t i = sel.length(); - while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } - - // CSS Level 1 - Recommendation - if (sel == ":link") return true; - if (sel == ":visited") return true; - if (sel == ":active") return true; - - // CSS Level 2 (Revision 1) - Recommendation - if (sel == ":lang") return true; - if (sel == ":first-child") return true; - if (sel == ":hover") return true; - if (sel == ":focus") return true; - // disabled - also valid properties - // if (sel == ":left") return true; - // if (sel == ":right") return true; - if (sel == ":first") return true; - - // Selectors Level 3 - Recommendation - if (sel == ":target") return true; - if (sel == ":root") return true; - if (sel == ":nth-child") return true; - if (sel == ":nth-last-of-child") return true; - if (sel == ":nth-of-type") return true; - if (sel == ":nth-last-of-type") return true; - if (sel == ":last-child") return true; - if (sel == ":first-of-type") return true; - if (sel == ":last-of-type") return true; - if (sel == ":only-child") return true; - if (sel == ":only-of-type") return true; - if (sel == ":empty") return true; - if (sel == ":not") return true; - - // CSS Basic User Interface Module Level 3 - Working Draft - if (sel == ":default") return true; - if (sel == ":valid") return true; - if (sel == ":invalid") return true; - if (sel == ":in-range") return true; - if (sel == ":out-of-range") return true; - if (sel == ":required") return true; - if (sel == ":optional") return true; - if (sel == ":read-only") return true; - if (sel == ":read-write") return true; - if (sel == ":dir") return true; - if (sel == ":enabled") return true; - if (sel == ":disabled") return true; - if (sel == ":checked") return true; - if (sel == ":indeterminate") return true; - if (sel == ":nth-last-child") return true; - - // Selectors Level 4 - Working Draft - if (sel == ":any-link") return true; - if (sel == ":local-link") return true; - if (sel == ":scope") return true; - if (sel == ":active-drop-target") return true; - if (sel == ":valid-drop-target") return true; - if (sel == ":invalid-drop-target") return true; - if (sel == ":current") return true; - if (sel == ":past") return true; - if (sel == ":future") return true; - if (sel == ":placeholder-shown") return true; - if (sel == ":user-error") return true; - if (sel == ":blank") return true; - if (sel == ":nth-match") return true; - if (sel == ":nth-last-match") return true; - if (sel == ":nth-column") return true; - if (sel == ":nth-last-column") return true; - if (sel == ":matches") return true; - - // Fullscreen API - Living Standard - if (sel == ":fullscreen") return true; - - // not a pseudo selector - return false; - - } - - static size_t findFirstCharacter (std::string& sass, size_t pos) - { - return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static size_t findLastCharacter (std::string& sass, size_t pos) - { - return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static bool isUrl (std::string& sass, size_t pos) - { - return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; - } - - // check if there is some char data - // will ignore everything in comments - static bool hasCharData (std::string& sass) - { - - size_t col_pos = 0; - - while (true) - { - - // try to find some meaningfull char - col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); - - // there was no meaningfull char found - if (col_pos == std::string::npos) return false; - - // found a multiline comment opener - if (sass.substr(col_pos, 2) == "/*") - { - // find the multiline comment closer - col_pos = sass.find("*/", col_pos); - // maybe we did not find the closer here - if (col_pos == std::string::npos) return false; - // skip closer - col_pos += 2; - } - else - { - return true; - } - - } - - } - // EO hasCharData - - // find src comment opener - // correctly skips quoted strings - static size_t findCommentOpener (std::string& sass) - { - - size_t col_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - size_t brackets = 0; - - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*()", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - if (character == '(') - { - if (!quoted && !apoed) brackets ++; - } - else if (character == ')') - { - if (!quoted && !apoed) brackets --; - } - else if (character == '\"') - { - // invert quote bool - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - // invert quote bool - if (!quoted && !comment) apoed = !apoed; - } - else if (col_pos > 0 && character == '/') - { - if (sass.at(col_pos - 1) == '*') - { - comment = false; - } - // next needs to be a slash too - else if (sass.at(col_pos - 1) == '/') - { - // only found if not in single or double quote, bracket or comment - if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; - } - } - else if (character == '\\') - { - // skip next char if in quote - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (col_pos > 0 && character == '*') - { - // opening a multiline comment - if (sass.at(col_pos - 1) == '/') - { - // we are now in a comment - if (!quoted && !apoed) comment = true; - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - return col_pos; - - } - // EO findCommentOpener - - // remove multiline comments from sass string - // correctly skips quoted strings - static std::string removeMultilineComment (std::string &sass) - { - - std::string clean = ""; - size_t col_pos = 0; - size_t open_pos = 0; - size_t close_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - - // process sass til string end - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - // found quoted string delimiter - if (character == '\"') - { - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - if (!quoted && !comment) apoed = !apoed; - } - // found possible comment closer - else if (character == '/') - { - // look back to see if it is actually a closer - if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') - { - close_pos = col_pos + 1; comment = false; - } - } - else if (character == '\\') - { - // skip escaped char - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (character == '*') - { - // look back to see if it is actually an opener - if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') - { - comment = true; open_pos = col_pos - 1; - clean += sass.substr(close_pos, open_pos - close_pos); - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - // add final parts (add half open comment text) - if (comment) clean += sass.substr(open_pos); - else clean += sass.substr(close_pos); - - // return string - return clean; - - } - // EO removeMultilineComment - - // right trim a given string - std::string rtrim(const std::string &sass) - { - std::string trimmed = sass; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) - { trimmed.erase(pos_ws + 1); } - else { trimmed.clear(); } - return trimmed; - } - // EO rtrim - - // flush whitespace and print additional text, but - // only print additional chars and buffer whitespace - std::string flush (std::string& sass, converter& converter) - { - - // return flushed - std::string scss = ""; - - // print whitespace buffer - scss += PRETTIFY(converter) > 0 ? - converter.whitespace : ""; - // reset whitespace buffer - converter.whitespace = ""; - - // remove possible newlines from string - size_t pos_right = sass.find_last_not_of("\n\r"); - if (pos_right == std::string::npos) return scss; - - // get the linefeeds from the string - std::string lfs = sass.substr(pos_right + 1); - sass = sass.substr(0, pos_right + 1); - - // find some source comment opener - size_t comment_pos = findCommentOpener(sass); - // check if there was a source comment - if (comment_pos != std::string::npos) - { - // convert comment (but only outside other coments) - if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) - { - // convert to multiline comment - sass.at(comment_pos + 1) = '*'; - // add comment node to the whitespace - sass += " */"; - } - // not at line start - if (comment_pos > 0) - { - // also include whitespace before the actual comment opener - size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); - comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; - } - if (!STRIP_COMMENT(converter)) - { - // add comment node to the whitespace - converter.whitespace += sass.substr(comment_pos); - } - else - { - // sass = removeMultilineComments(sass); - } - // update the actual sass code - sass = sass.substr(0, comment_pos); - } - - // add newline as getline discharged it - converter.whitespace += lfs + "\n"; - - // maybe remove any leading whitespace - if (PRETTIFY(converter) == 0) - { - // remove leading whitespace and update string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - if (pos_left != std::string::npos) sass = sass.substr(pos_left); - } - - // add flushed data - scss += sass; - - // return string - return scss; - - } - // EO flush - - // process a line of the sass text - std::string process (std::string& sass, converter& converter) - { - - // resulting string - std::string scss = ""; - - // strip multi line comments - if (STRIP_COMMENT(converter)) - { - sass = removeMultilineComment(sass); - } - - // right trim input - sass = rtrim(sass); - - // get postion of first meaningfull character in string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - - // special case for final run - if (converter.end_of_file) pos_left = 0; - - // maybe has only whitespace - if (pos_left == std::string::npos) - { - // just add complete whitespace - converter.whitespace += sass + "\n"; - } - // have meaningfull first char - else - { - - // extract and store indentation string - std::string indent = sass.substr(0, pos_left); - - // check if current line starts a comment - std::string open = sass.substr(pos_left, 2); - - // line has less or same indentation - // finalize previous open parser context - if (indent.length() <= INDENT(converter).length()) - { - - // close multilinie comment - if (IS_CSS_COMMENT(converter)) - { - // check if comments will be stripped anyway - if (!STRIP_COMMENT(converter)) scss += " */"; - } - // close src comment comment - else if (IS_SRC_COMMENT(converter)) - { - // add a newline to avoid closer on same line - // this would put the bracket in the comment node - // no longer needed since we parse them correctly - // if (KEEP_COMMENT(converter)) scss += "\n"; - } - // close css properties - else if (converter.property) - { - // add closer unless in concat mode - if (!converter.comma) - { - // if there was no colon we have a selector - // looks like there were no inner properties - if (converter.selector) scss += " {}"; - // add final semicolon - else if (!converter.semicolon) scss += ";"; - } - } - - // reset comment state - converter.comment = ""; - - } - - // make sure we close every "higher" block - while (indent.length() < INDENT(converter).length()) - { - // pop stacked context - converter.indents.pop(); - // print close bracket - if (IS_PARSING(converter)) - { scss += closer(converter); } - else { scss += " */"; } - // reset comment state - converter.comment = ""; - } - - // reset converter state - converter.selector = false; - - // looks like some undocumented behavior ... - // https://github.com/mgreter/sass2scss/issues/29 - if (sass.substr(pos_left, 1) == "\\") { - converter.selector = true; - sass[pos_left] = ' '; - } - - // check if we have sass property syntax - if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") - { - - // default to a selector - // change back if property found - converter.selector = true; - // get postion of first whitespace char - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - // assertion check for valid result - if (pos_wspace != std::string::npos) - { - // get the possible pseudo selector - std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); - // get position of the first real property value char - // pseudo selectors get this far, but have no actual value - size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); - // assertion check for valid result - if (pos_value != std::string::npos) - { - // only process if not (fallowed by a semicolon or is a pseudo selector) - if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) - { - // create new string by interchanging the colon sign for property and value - sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); - // try to find a colon in the current line, but only ... - size_t pos_colon = sass.find_first_not_of(":", pos_left); - // assertion for valid result - if (pos_colon != std::string::npos) - { - // ... after the first word (skip begining colons) - pos_colon = sass.find_first_of(":", pos_colon); - // it is a selector if there was no colon found - converter.selector = pos_colon == std::string::npos; - } - } - } - } - - // check if we have a BEM property (one colon and no selector) - if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; - } - - } - - // terminate some statements immediately - else if ( - sass.substr(pos_left, 5) == "@warn" || - sass.substr(pos_left, 6) == "@debug" || - sass.substr(pos_left, 6) == "@error" || - sass.substr(pos_left, 6) == "@value" || - sass.substr(pos_left, 8) == "@charset" || - sass.substr(pos_left, 10) == "@namespace" - ) { sass = indent + sass.substr(pos_left); } - // replace some specific sass shorthand directives (if not fallowed by a white space character) - else if (sass.substr(pos_left, 1) == "=") - { sass = indent + "@mixin " + sass.substr(pos_left + 1); } - else if (sass.substr(pos_left, 1) == "+") - { - // must be followed by a mixin call (no whitespace afterwards or at ending directly) - if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { - sass = indent + "@include " + sass.substr(pos_left + 1); - } - } - - // add quotes for import if needed - else if (sass.substr(pos_left, 7) == "@import") - { - // get positions for the actual import url - size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - size_t start = pos; - bool in_dqstr = false; - bool in_sqstr = false; - bool is_escaped = false; - do { - if (is_escaped) { - is_escaped = false; - } - else if (sass[pos] == '\\') { - is_escaped = true; - } - else if (sass[pos] == '"') { - if (!in_sqstr) in_dqstr = ! in_dqstr; - } - else if (sass[pos] == '\'') { - if (!in_dqstr) in_sqstr = ! in_sqstr; - } - else if (in_dqstr || in_sqstr) { - // skip over quoted stuff - } - else if (sass[pos] == ',' || sass[pos] == 0) { - if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { - size_t end = findLastCharacter(sass, pos - 1) + 1; - sass = sass.replace(end, 0, "\""); - sass = sass.replace(start, 0, "\""); - pos += 2; - } - start = findFirstCharacter(sass, pos + 1); - } - } - while (sass[pos++] != 0); - - } - else if ( - sass.substr(pos_left, 7) != "@return" && - sass.substr(pos_left, 7) != "@extend" && - sass.substr(pos_left, 8) != "@include" && - sass.substr(pos_left, 8) != "@content" - ) { - - // probably a selector anyway - converter.selector = true; - // try to find first colon in the current line - size_t pos_colon = sass.find_first_of(":", pos_left); - // assertion that we have a colon - if (pos_colon != std::string::npos) - { - // it is not a selector if we have a space after a colon - if (sass[pos_colon+1] == ' ') converter.selector = false; - if (sass[pos_colon+1] == ' ') converter.selector = false; - } - - } - - // current line has more indentation - if (indent.length() >= INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // has meaningfull chars - if (hasCharData(sass)) - { - // is probably a property - // also true for selectors - converter.property = true; - } - } - } - // current line has more indentation - if (indent.length() > INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // had meaningfull chars - if (converter.property) - { - // print block opener - scss += opener(converter); - // push new stack context - converter.indents.push(""); - // store block indentation - INDENT(converter) = indent; - } - } - // is and will be a src comment - else if (!IS_CSS_COMMENT(converter)) - { - // scss does not allow multiline src comments - // therefore add forward slashes to all lines - sass.at(INDENT(converter).length()+0) = '/'; - // there is an edge case here if indentation - // is minimal (will overwrite the fist char) - sass.at(INDENT(converter).length()+1) = '/'; - // could code around that, but I dont' think - // this will ever be the cause for any trouble - } - } - - // line is opening a new comment - if (open == "/*" || open == "//") - { - // reset the property state - converter.property = false; - // close previous comment - if (IS_CSS_COMMENT(converter) && open != "") - { - if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; - } - // force single line comments - // into a correct css comment - if (CONVERT_COMMENT(converter)) - { - if (IS_PARSING(converter)) - { sass.at(pos_left + 1) = '*'; } - } - // set comment flag - converter.comment = open; - - } - - // flush data only under certain conditions - if (!( - // strip css and src comments if option is set - (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || - // strip src comment even if strip option is not set - // but only if the keep src comment option is not set - (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) - )) - { - // flush data and buffer whitespace - scss += flush(sass, converter); - } - - // get postion of last meaningfull char - size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - - // check for invalid result - if (pos_right != std::string::npos) - { - - // get the last meaningfull char - std::string close = sass.substr(pos_right, 1); - - // check if next line should be concatenated (list mode) - converter.comma = IS_PARSING(converter) && close == ","; - converter.semicolon = IS_PARSING(converter) && close == ";"; - - // check if we have more than - // one meaningfull char - if (pos_right > 0) - { - - // get the last two chars from string - std::string close = sass.substr(pos_right - 1, 2); - // update parser status for expicitly closed comment - if (close == "*/") converter.comment = ""; - - } - - } - // EO have meaningfull chars from end - - } - // EO have meaningfull chars from start - - // return scss - return scss; - - } - // EO process - - // read line with either CR, LF or CR LF format - // http://stackoverflow.com/a/6089413/1550314 - static std::istream& safeGetline(std::istream& is, std::string& t) - { - t.clear(); - - // The characters in the stream are read one-by-one using a std::streambuf. - // That is faster than reading them one-by-one using the std::istream. - // Code that uses streambuf this way must be guarded by a sentry object. - // The sentry object performs various tasks, - // such as thread synchronization and updating the stream state. - - std::istream::sentry se(is, true); - std::streambuf* sb = is.rdbuf(); - - for(;;) { - int c = sb->sbumpc(); - switch (c) { - case '\n': - return is; - case '\r': - if(sb->sgetc() == '\n') - sb->sbumpc(); - return is; - case EOF: - // Also handle the case when the last line has no line ending - if(t.empty()) - is.setstate(std::ios::eofbit); - return is; - default: - t += (char)c; - } - } - } - - // the main converter function for c++ - char* sass2scss (const std::string& sass, const int options) - { - - // local variables - std::string line; - std::string scss = ""; - std::stringstream stream(sass); - - // create converter variable - converter converter; - // initialise all options - converter.comma = false; - converter.property = false; - converter.selector = false; - converter.semicolon = false; - converter.end_of_file = false; - converter.comment = ""; - converter.whitespace = ""; - converter.indents.push(""); - converter.options = options; - - // read line by line and process them - while(safeGetline(stream, line) && !stream.eof()) - { scss += process(line, converter); } - - // create mutable string - std::string closer = ""; - // set the end of file flag - converter.end_of_file = true; - // process to close all open blocks - scss += process(closer, converter); - - // allocate new memory on the heap - // caller has to free it after use - char * cstr = (char*) malloc (scss.length() + 1); - // create a copy of the string - strcpy (cstr, scss.c_str()); - // return pointer - return &cstr[0]; - - } - // EO sass2scss - -} -// EO namespace - -// implement for c -extern "C" -{ - - char* ADDCALL sass2scss (const char* sass, const int options) - { - return Sass::sass2scss(sass, options); - } - - // Get compiled sass2scss version - const char* ADDCALL sass2scss_version(void) { - return SASS2SCSS_VERSION; - } - -} diff --git a/src/libsass/sass_context.cpp b/src/libsass/sass_context.cpp deleted file mode 100644 index 7a0a49ce1..000000000 --- a/src/libsass/sass_context.cpp +++ /dev/null @@ -1,799 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include - -#include "sass.h" -#include "ast.hpp" -#include "file.hpp" -#include "json.hpp" -#include "util.hpp" -#include "context.hpp" -#include "sass_context.hpp" -#include "sass_functions.hpp" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" - -#define LFEED "\n" - -// C++ helper -namespace Sass { - // see sass_copy_c_string(std::string str) - static inline JsonNode* json_mkstream(const std::stringstream& stream) - { - // hold on to string on stack! - std::string str(stream.str()); - return json_mkstring(str.c_str()); - } - - static int handle_error(Sass_Context* c_ctx) { - try { - throw; - } - catch (Exception::Base& e) { - std::stringstream msg_stream; - std::string cwd(Sass::File::get_cwd()); - std::string msg_prefix(e.errtype()); - bool got_newline = false; - msg_stream << msg_prefix << ": "; - const char* msg = e.what(); - while (msg && *msg) { - if (*msg == '\r') { - got_newline = true; - } - else if (*msg == '\n') { - got_newline = true; - } - else if (got_newline) { - msg_stream << std::string(msg_prefix.size() + 2, ' '); - got_newline = false; - } - msg_stream << *msg; - ++msg; - } - if (!got_newline) msg_stream << "\n"; - - if (e.traces.empty()) { - // we normally should have some traces, still here as a fallback - std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); - msg_stream << std::string(msg_prefix.size() + 2, ' '); - msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; - } - else { - std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); - msg_stream << traces_to_string(e.traces, " "); - } - - // now create the code trace (ToDo: maybe have util functions?) - if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { - size_t lines = e.pstate.line; - const char* line_beg = e.pstate.src; - // scan through src until target line - // move line_beg pointer to line start - while (line_beg && *line_beg && lines != 0) { - if (*line_beg == '\n') --lines; - utf8::unchecked::next(line_beg); - } - const char* line_end = line_beg; - // move line_end before next newline character - while (line_end && *line_end && *line_end != '\n') { - if (*line_end == '\n') break; - if (*line_end == '\r') break; - utf8::unchecked::next(line_end); - } - if (line_end && *line_end != 0) ++ line_end; - size_t line_len = line_end - line_beg; - size_t move_in = 0; size_t shorten = 0; - size_t left_chars = 42; size_t max_chars = 76; - // reported excerpt should not exceed `max_chars` chars - if (e.pstate.column > line_len) left_chars = e.pstate.column; - if (e.pstate.column > left_chars) move_in = e.pstate.column - left_chars; - if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; - utf8::advance(line_beg, move_in, line_end); - utf8::retreat(line_end, shorten, line_beg); - std::string sanitized; std::string marker(e.pstate.column - move_in, '-'); - utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); - msg_stream << ">> " << sanitized << "\n"; - msg_stream << " " << marker << "^\n"; - } - - JsonNode* json_err = json_mkobject(); - json_append_member(json_err, "status", json_mknumber(1)); - json_append_member(json_err, "file", json_mkstring(e.pstate.path)); - json_append_member(json_err, "line", json_mknumber((double)(e.pstate.line + 1))); - json_append_member(json_err, "column", json_mknumber((double)(e.pstate.column + 1))); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 1; - c_ctx->error_file = sass_copy_c_string(e.pstate.path); - c_ctx->error_line = e.pstate.line + 1; - c_ctx->error_column = e.pstate.column + 1; - c_ctx->error_src = e.pstate.src; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::bad_alloc& ba) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(2)); - json_append_member(json_err, "message", json_mkstring(ba.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(ba.what()); - c_ctx->error_status = 2; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::exception& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(3)); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 3; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::string& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e.c_str())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.c_str()); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (const char* e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e)); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (...) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unknown error occurred" << std::endl; - json_append_member(json_err, "status", json_mknumber(5)); - json_append_member(json_err, "message", json_mkstring("unknown")); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string("unknown"); - c_ctx->error_status = 5; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - return c_ctx->error_status; - } - - // allow one error handler to throw another error - // this can happen with invalid utf8 and json lib - static int handle_errors(Sass_Context* c_ctx) { - try { return handle_error(c_ctx); } - catch (...) { return handle_error(c_ctx); } - } - - static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() - { - - // assert valid pointer - if (compiler == 0) return 0; - // The cpp context must be set by now - Context* cpp_ctx = compiler->cpp_ctx; - Sass_Context* c_ctx = compiler->c_ctx; - // We will take care to wire up the rest - compiler->cpp_ctx->c_compiler = compiler; - compiler->state = SASS_COMPILER_PARSED; - - try { - - // get input/output path from options - std::string input_path = safe_str(c_ctx->input_path); - std::string output_path = safe_str(c_ctx->output_path); - - // maybe skip some entries of included files - // we do not include stdin for data contexts - bool skip = c_ctx->type == SASS_CONTEXT_DATA; - - // dispatch parse call - Block_Obj root(cpp_ctx->parse()); - // abort on errors - if (!root) return 0; - - // skip all prefixed files? (ToDo: check srcmap) - // IMO source-maps should point to headers already - // therefore don't skip it for now. re-enable or - // remove completely once this is tested - size_t headers = cpp_ctx->head_imports; - - // copy the included files on to the context (dont forget to free later) - if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) - throw(std::bad_alloc()); - - // return parsed block - return root; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return 0; - - } - -} - -extern "C" { - using namespace Sass; - - static void sass_clear_options (struct Sass_Options* options); - static void sass_reset_options (struct Sass_Options* options); - static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { - // do not overwrite ourself - if (to == from) return; - // free assigned memory - sass_clear_options(to); - // move memory - *to = *from; - // Reset pointers on source - sass_reset_options(from); - } - - #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } - #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } - #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ - { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } - #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) - - #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ - type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } - #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ - type sass_context_take_##option (struct Sass_Context* ctx) \ - { type foo = ctx->option; ctx->option = 0; return foo; } - - - // generic compilation function (not exported, use file/data compile instead) - static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() - { - try { - // register our custom functions - if (c_ctx->c_functions) { - auto this_func_data = c_ctx->c_functions; - while (this_func_data && *this_func_data) { - cpp_ctx->add_c_function(*this_func_data); - ++this_func_data; - } - } - - // register our custom headers - if (c_ctx->c_headers) { - auto this_head_data = c_ctx->c_headers; - while (this_head_data && *this_head_data) { - cpp_ctx->add_c_header(*this_head_data); - ++this_head_data; - } - } - - // register our custom importers - if (c_ctx->c_importers) { - auto this_imp_data = c_ctx->c_importers; - while (this_imp_data && *this_imp_data) { - cpp_ctx->add_c_importer(*this_imp_data); - ++this_imp_data; - } - } - - // reset error status - c_ctx->error_json = 0; - c_ctx->error_text = 0; - c_ctx->error_message = 0; - c_ctx->error_status = 0; - // reset error position - c_ctx->error_src = 0; - c_ctx->error_file = 0; - c_ctx->error_line = std::string::npos; - c_ctx->error_column = std::string::npos; - - // allocate a new compiler instance - void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); - if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } - Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; - compiler->state = SASS_COMPILER_CREATED; - - // store in sass compiler - compiler->c_ctx = c_ctx; - compiler->cpp_ctx = cpp_ctx; - cpp_ctx->c_compiler = compiler; - - // use to parse block - return compiler; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return 0; - - } - - // generic compilation function (not exported, use file/data compile instead) - static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) - { - - // prepare sass compiler with context and options - Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); - - try { - // call each compiler step - sass_compiler_parse(compiler); - sass_compiler_execute(compiler); - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - sass_delete_compiler(compiler); - - return c_ctx->error_status; - } - - inline void init_options (struct Sass_Options* options) - { - options->precision = 5; - options->indent = " "; - options->linefeed = LFEED; - } - - Sass_Options* ADDCALL sass_make_options (void) - { - struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); - if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } - init_options(options); - return options; - } - - Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) - { - SharedObj::setTaint(true); // needed for static colors - struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_FILE; - init_options(ctx); - try { - if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } - if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } - sass_option_set_input_path(ctx, input_path); - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) - { - struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_DATA; - init_options(ctx); - try { - if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } - if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } - ctx->source_string = source_string; - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 0; - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_prepare_context(data_ctx, cpp_ctx); - } - - struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 0; - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_prepare_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 1; - if (data_ctx->error_status) - return data_ctx->error_status; - try { - if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } - // empty source string is a valid case, even if not really usefull (different than with file context) - // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } - } - catch (...) { return handle_errors(data_ctx) | 1; } - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_compile_context(data_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 1; - if (file_ctx->error_status) - return file_ctx->error_status; - try { - if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } - if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } - } - catch (...) { return handle_errors(file_ctx) | 1; } - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_compile_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_PARSED) return 0; - if (compiler->state != SASS_COMPILER_CREATED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - // parse the context we have set up (file or data) - compiler->root = sass_parse_block(compiler); - // success - return 0; - } - - int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_EXECUTED) return 0; - if (compiler->state != SASS_COMPILER_PARSED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->root.isNull()) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - compiler->state = SASS_COMPILER_EXECUTED; - Context* cpp_ctx = compiler->cpp_ctx; - Block_Obj root = compiler->root; - // compile the parsed root block - try { compiler->c_ctx->output_string = cpp_ctx->render(root); } - // pass catched errors to generic error handler - catch (...) { return handle_errors(compiler->c_ctx) | 1; } - // generate source map json and store on context - compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); - // success - return 0; - } - - // helper function, not exported, only accessible locally - static void sass_reset_options (struct Sass_Options* options) - { - // free pointer before - // or copy/move them - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - options->extensions = 0; - } - - // helper function, not exported, only accessible locally - static void sass_clear_options (struct Sass_Options* options) - { - if (options == 0) return; - // Deallocate custom functions, headers and importes - sass_delete_function_list(options->c_functions); - sass_delete_importer_list(options->c_importers); - sass_delete_importer_list(options->c_headers); - // Deallocate inc paths - if (options->plugin_paths) { - struct string_list* cur; - struct string_list* next; - cur = options->plugin_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Deallocate inc paths - if (options->include_paths) { - struct string_list* cur; - struct string_list* next; - cur = options->include_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Deallocate extension - if (options->extensions) { - struct string_list* cur; - struct string_list* next; - cur = options->extensions; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Free options strings - free(options->input_path); - free(options->output_path); - free(options->plugin_path); - free(options->include_path); - free(options->source_map_file); - free(options->source_map_root); - // Reset our pointers - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - options->extensions = 0; - } - - // helper function, not exported, only accessible locally - // sass_free_context is also defined in old sass_interface - static void sass_clear_context (struct Sass_Context* ctx) - { - if (ctx == 0) return; - // release the allocated memory (mostly via sass_copy_c_string) - if (ctx->output_string) free(ctx->output_string); - if (ctx->source_map_string) free(ctx->source_map_string); - if (ctx->error_message) free(ctx->error_message); - if (ctx->error_text) free(ctx->error_text); - if (ctx->error_json) free(ctx->error_json); - if (ctx->error_file) free(ctx->error_file); - free_string_array(ctx->included_files); - // play safe and reset properties - ctx->output_string = 0; - ctx->source_map_string = 0; - ctx->error_message = 0; - ctx->error_text = 0; - ctx->error_json = 0; - ctx->error_file = 0; - ctx->included_files = 0; - // debug leaked memory - #ifdef DEBUG_SHARED_PTR - SharedObj::dumpMemLeaks(); - #endif - // now clear the options - sass_clear_options(ctx); - } - - void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) - { - if (compiler == 0) { - return; - } - Context* cpp_ctx = compiler->cpp_ctx; - if (cpp_ctx) delete(cpp_ctx); - compiler->cpp_ctx = NULL; - compiler->c_ctx = NULL; - compiler->root = NULL; - free(compiler); - } - - void ADDCALL sass_delete_options (struct Sass_Options* options) - { - sass_clear_options(options); free(options); - } - - // Deallocate all associated memory with file context - void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) - { - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - // Deallocate all associated memory with data context - void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) - { - // clean the source string if it was not passed - // we reset this member once we start parsing - if (ctx->source_string) free(ctx->source_string); - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - - // Getters for sass context from specific implementations - struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } - - // Getters for context options from Sass_Context - struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } - void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - - // Getters for Sass_Compiler options (get conected sass context) - enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } - struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - // Getters for Sass_Compiler options (query import stack) - size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } - Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } - Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } - // Getters for Sass_Compiler options (query function stack) - size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } - - // Calculate the size of the stored null terminated array - size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) - { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } - - // Create getter and setters for options - IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); - IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); - - // Create getter and setters for context - IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); - - // Take ownership of memory (value on context is set to 0) - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); - - // Push function for import extenions - void ADDCALL sass_option_push_import_extension(struct Sass_Options* options, const char* ext) - { - struct string_list* extension = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (extension == 0) return; - extension->string = ext ? sass_copy_c_string(ext) : 0; - struct string_list* last = options->extensions; - if (!options->extensions) { - options->extensions = extension; - } else { - while (last->next) - last = last->next; - last->next = extension; - } - } - - // Push function for include paths (no manipulation support for now) - void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) - { - - struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (include_path == 0) return; - include_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->include_paths; - if (!options->include_paths) { - options->include_paths = include_path; - } else { - while (last->next) - last = last->next; - last->next = include_path; - } - - } - - // Push function for include paths (no manipulation support for now) - size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) - { - size_t len = 0; - struct string_list* cur = options->include_paths; - while (cur) { len ++; cur = cur->next; } - return len; - } - - // Push function for include paths (no manipulation support for now) - const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) - { - struct string_list* cur = options->include_paths; - while (i) { i--; cur = cur->next; } - return cur->string; - } - - // Push function for plugin paths (no manipulation support for now) - void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) - { - - struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (plugin_path == 0) return; - plugin_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->plugin_paths; - if (!options->plugin_paths) { - options->plugin_paths = plugin_path; - } else { - while (last->next) - last = last->next; - last->next = plugin_path; - } - - } - -} diff --git a/src/libsass/sass_context.hpp b/src/libsass/sass_context.hpp deleted file mode 100644 index 9d192a301..000000000 --- a/src/libsass/sass_context.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef SASS_SASS_CONTEXT_H -#define SASS_SASS_CONTEXT_H - -#include "sass/base.h" -#include "sass/context.h" -#include "ast_fwd_decl.hpp" - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* extension; - char* include_path; - char* plugin_path; - - // Extensions (linked string list) - struct string_list* extensions; - // Include paths (linked string list) - struct string_list* include_paths; - // Plugin paths (linked string list) - struct string_list* plugin_paths; - - // Path to source map file - // Enables source map generation - // Used to create sourceMappingUrl - char* source_map_file; - - // Directly inserted in source maps - char* source_map_root; - - // Custom functions that can be called from sccs code - Sass_Function_List c_functions; - - // List of custom importers - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - const char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; - -#endif diff --git a/src/libsass/setup-environment.md b/src/libsass/setup-environment.md deleted file mode 100644 index 805613656..000000000 --- a/src/libsass/setup-environment.md +++ /dev/null @@ -1,68 +0,0 @@ -## Requirements -In order to install and setup your local development environment, there are some prerequisites: - -* git -* gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) -* ruby w/ bundler - -OS X: -First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: - -```` -xcode-select --install -```` - -## Cloning the Projects - -First, clone the project and then add a line to your `~/.bash_profile` that will let other programs know where the LibSass dev files are. - -```` -git clone git@github.com:sass/libsass.git -cd libsass -echo "export SASS_LIBSASS_PATH=$(pwd)" >> ~/.bash_profile - -```` - -Then, if you run the "bootstrap" script, it should clone all the other required projects. - -```` -./script/bootstrap -```` - -You should now have a `sass-spec` and `sassc` folder within the libsass folder. Both of these are clones of their respective git projects. If you want to do a pull request, remember to work in those folders. For instance, if you want to add a test (see other documentation for how to do that), make sure to commit it to your *fork* of the sass-spec github project. Also, whenever you are running tests, make sure to `pull` from the origin! We want to make sure we are testing against the newest libsass, sassc, and sass-spec! - -Now, try and see if you can build the project. We do that with the `make` command. - -```` -make -```` - -At this point, if you get an error, something is most likely wrong with your compiler installation. Yikes. It's hard to cover how to fix this in an article. Feel free to open an issue and we'll try and help! But, remember, before you do that, googling the error message is your friend! Many problems are solved quickly that way. - -## Running The Spec Against LibSass - -Then, to run the spec against LibSass, just run: - -```` -./script/spec -```` - -If you get an error about `SASS_LIBSASS_PATH`, you may still need to set a variable pointing to the libsass folder, like this: - -```` -export SASS_LIBSASS_PATH=/Users/you/path/libsass -```` - -...where the latter part is to the `libsass` directory you've cloned. You can get this path by typing `pwd` in the Terminal - -## Running the Spec Against Ruby Sass - -Go into the sass-spec folder that should have been cloned earlier with the "bootstrap" command. Run the following. - -```` -bundle install -./sass-spec.rb -```` - -Voila! Now you are testing against Sass too! - diff --git a/src/libsass/source-map-internals.md b/src/libsass/source-map-internals.md deleted file mode 100644 index 50f83b54f..000000000 --- a/src/libsass/source-map-internals.md +++ /dev/null @@ -1,51 +0,0 @@ -This document is mainly intended for developers! - -# Documenting some of the source map internals - -Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. - -The main storage for SourceMap mappings is the `mappings` vector: - -``` -# in source_map.hpp -vector mappings -# in mappings.hpp -struct Mapping ... - Position original_position; - Position generated_position; -``` - -## Every parsed token has its source associated - -LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: - -``` -if (lex< custom_property_name >()) { - Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); - return new (ctx.mem) Declaration(path, prop->position(), prop, ...); -} -``` - -## How is the `source_position` calculated - -This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! - -``` -lex< exactly < empty_str > >(); -end = new (ctx.mem) String_Constant(path, source_position, lexed); -``` - -## How are mappings for the output created - -So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: - -``` -# in source_map.hpp -void add_mapping(AST_Node* node); -``` - -This method is called in two places: -- `Inspect::append_to_buffer` -- `Output_[Nested|Compressed]::append_to_buffer` - -Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/src/libsass/tap-runner b/src/libsass/tap-runner deleted file mode 100755 index 56c13bfb4..000000000 --- a/src/libsass/tap-runner +++ /dev/null @@ -1 +0,0 @@ -$@ $TEST_FLAGS --tap --silent | tapout tap diff --git a/src/libsass/trace.md b/src/libsass/trace.md deleted file mode 100644 index 4a57c901f..000000000 --- a/src/libsass/trace.md +++ /dev/null @@ -1,26 +0,0 @@ -## This is proposed interface in https://github.com/sass/libsass/pull/1288 - -Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. - -Both macros simulate a string stream, so they can be used like this: - - TRACE() << "Reached."; - -produces: - - [LibSass] parse_value parser.cpp:1384 Reached. - -`TRACE()` - logs function name, source filename, source file name to the standard error and the attached - stream to the standard error. - -`TRACEINST(obj)` - logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: - - TRACEINST(this) << "String_Constant created " << this; - -produces: - - [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") - -The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/src/libsass/triage.md b/src/libsass/triage.md deleted file mode 100644 index 0fc11784c..000000000 --- a/src/libsass/triage.md +++ /dev/null @@ -1,17 +0,0 @@ -This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: - -1. Issue is reported by a user. -2. If the issue seems like a bug, then the "bug" tag is added. -3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. -4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. -5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. -6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! -7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. -8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". -9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". -10. A C++ developer will then work on the issue and issue a pull request to fix the issue. -11. A core member verifies that the fix does actually fix the spec tests. -12. The fix is merged into the project. -13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues -14. The issue is closed -15. Have a soda pop or enjoyable beverage of your choice diff --git a/src/libsass/unicode.md b/src/libsass/unicode.md deleted file mode 100644 index a1eb5b1cf..000000000 --- a/src/libsass/unicode.md +++ /dev/null @@ -1,45 +0,0 @@ -LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your -input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected -behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! - -### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) - -This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. - -Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). - -### Current status on LibSass unicode support - -LibSass should/is fully UTF (and therefore plain ASCII) compatible. - -~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ - -LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. - -### Current encoding auto detection - -LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! - -### What is currently not supported - -- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) -- Using non ASCII characters in different encodings in different includes - -### What is missing to support the above cases - -- A way to convert between encodings (like libiconv/ICU) -- Sniffing the charset inside the file (source is available) -- Handling the conversion on import (and export) -- Optional: Make output encoding configurable -- Optional: Add optional/mandatory BOM (configurable) - -### Low priority feature - -I guess the current implementation should handle more than 99% of all real world use cases. -A) Unicode characters are still seldomly seen (as they can be written escaped) -~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. -Although I'm not sure how this applies to asian and other "exotic" codepages!~~ - -I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). - -I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/src/libsass/util.hpp b/src/libsass/util.hpp deleted file mode 100644 index f23475fe0..000000000 --- a/src/libsass/util.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef SASS_UTIL_H -#define SASS_UTIL_H - -#include -#include -#include -#include "sass.hpp" -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#define SASS_ASSERT(cond, msg) assert(cond && msg) - -namespace Sass { - - double round(double val, size_t precision = 0); - double sass_strtod(const char* str); - const char* safe_str(const char *, const char* = ""); - void free_string_array(char **); - char **copy_strings(const std::vector&, char ***, int = 0); - std::string read_css_string(const std::string& str, bool css = true); - std::string evacuate_escapes(const std::string& str); - std::string string_to_output(const std::string& str); - std::string comment_to_string(const std::string& text); - std::string read_hex_escapes(const std::string& str); - std::string escape_string(const std::string& str); - void newline_to_space(std::string& str); - - std::string quote(const std::string&, char q = 0); - std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); - char detect_best_quotemark(const char* s, char qm = '"'); - - bool is_hex_doublet(double n); - bool is_color_doublet(double r, double g, double b); - - bool peek_linefeed(const char* start); - - namespace Util { - - std::string rtrim(const std::string& str); - - std::string normalize_underscores(const std::string& str); - std::string normalize_decimals(const std::string& str); - - bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); - bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); - bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); - bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); - bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); - bool isAscii(const char chr); - - } -} -#endif From 7ec14dff2087266357146598cd267f4133f3fdf6 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Apr 2018 01:56:30 +1000 Subject: [PATCH 129/286] Bump LibSass@3.5.3 --- package.json | 4 +- src/libsass/GNUmakefile.am | 56 ++++++++++----------------- src/libsass/configure.ac | 20 ++++------ src/libsass/docs/implementations.md | 25 ++++++++---- src/libsass/docs/unicode.md | 26 ++++++++----- src/libsass/include/sass/context.h | 3 ++ src/libsass/script/tap-runner | 2 +- src/libsass/src/context.cpp | 50 +++++++++++++++++++++++- src/libsass/src/context.hpp | 3 ++ src/libsass/src/cssize.cpp | 2 +- src/libsass/src/debugger.hpp | 1 + src/libsass/src/eval.cpp | 15 -------- src/libsass/src/expand.cpp | 3 +- src/libsass/src/file.cpp | 4 +- src/libsass/src/file.hpp | 10 ++++- src/libsass/src/functions.cpp | 2 +- src/libsass/src/inspect.cpp | 6 +-- src/libsass/src/operators.cpp | 8 ++-- src/libsass/src/parser.cpp | 34 ++++++++++++----- src/libsass/src/parser.hpp | 8 ++-- src/libsass/src/prelexer.cpp | 2 +- src/libsass/src/sass.cpp | 6 ++- src/libsass/src/sass2scss.cpp | 59 ++++++++++++++++++++++------- src/libsass/src/sass_context.cpp | 34 ++++++++++++++++- src/libsass/src/sass_context.hpp | 5 ++- src/libsass/src/util.hpp | 5 --- 26 files changed, 257 insertions(+), 136 deletions(-) diff --git a/package.json b/package.json index 466e3ab63..5c5972278 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.9.0", - "libsass": "3.5.2", + "libsass": "3.5.3", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "^3.5.1", + "sass-spec": "3.5.3", "unique-temp-dir": "^1.0.0" } } diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am index 9e658a415..d197261e7 100644 --- a/src/libsass/GNUmakefile.am +++ b/src/libsass/GNUmakefile.am @@ -25,63 +25,49 @@ else AM_CXXFLAGS += -std=c++0x endif +TEST_EXTENSIONS = .rb + if ENABLE_TESTS -noinst_PROGRAMS = tester +SASS_SASSC_PATH ?= $(top_srcdir)/sassc +SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec +noinst_PROGRAMS = tester tester_LDADD = src/libsass.la -tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c -tester_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` -tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" -tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" tester_LDFLAGS = $(AM_LDFLAGS) +nodist_tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c +SASS_SASSC_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` +tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" +tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" if ENABLE_COVERAGE nodist_EXTRA_tester_SOURCES = non-existent-file-to-force-CXX-linking.cxx endif -SASS_SASSC_PATH ?= $(top_srcdir)/sassc -SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec - -TESTS = \ - $(SASS_SPEC_PATH)/spec/basic \ - $(SASS_SPEC_PATH)/spec/css \ - $(SASS_SPEC_PATH)/spec/extend-tests \ - $(SASS_SPEC_PATH)/spec/extends \ - $(SASS_SPEC_PATH)/spec/libsass \ - $(SASS_SPEC_PATH)/spec/libsass-closed-issues \ - $(SASS_SPEC_PATH)/spec/maps \ - $(SASS_SPEC_PATH)/spec/misc \ - $(SASS_SPEC_PATH)/spec/regressions \ - $(SASS_SPEC_PATH)/spec/scss \ - $(SASS_SPEC_PATH)/spec/scss-tests \ - $(SASS_SPEC_PATH)/spec/types +TESTS = $(SASS_SPEC_PATH)/sass-spec.rb +RB_LOG_COMPILER = ./script/tap-runner +AM_RB_LOG_FLAGS = $(RUBY) SASS_TEST_FLAGS = -V 3.5 --impl libsass -LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) ./script/tap-driver -AM_LOG_FLAGS = -c ./tester $(LOG_FLAGS) -if USE_TAP - AM_LOG_FLAGS += -t - SASS_TEST_FLAGS += -t | tapout - LOG_COMPILER = ./script/tap-runner $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -else - LOG_COMPILER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -endif +SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH) +SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) +AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -SASS_TESTER += -c $(top_srcdir)/tester$(EXEEXT) test: - $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) $(SASS_TEST_FLAGS) test_build: - $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) $(SASS_TEST_FLAGS) test_full: - $(SASS_TESTER) --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) test_probe: - $(SASS_TESTER) --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) + +.PHONY: test test_build test_full test_probe endif diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac index bf05dbfaa..b5a943217 100644 --- a/src/libsass/configure.ac +++ b/src/libsass/configure.ac @@ -9,6 +9,7 @@ AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_FILES([include/sass/version.h]) AC_CONFIG_AUX_DIR([script]) + # These are flags passed to automake # Though they look like gcc flags! AM_INIT_AUTOMAKE([foreign parallel-tests -Wall]) @@ -93,21 +94,16 @@ the --with-sass-spec-dir= argument. ;; esac AC_SUBST(SASS_SPEC_PATH) - - # TODO: Remove this when automake requirements are 1.12+ - AC_MSG_CHECKING([whether we can use TAP mode]) - tmp=`$AWK '/TEST_LOG_DRIVER/' $srcdir/GNUmakefile.in` - if test "x$tmp" != "x"; then - use_tap=yes - else - use_tap=no - fi - AC_MSG_RESULT([$use_tap]) - +else + # we do not really need these paths for non test build + # but automake may error if we do not define them here + SASS_SPEC_PATH=sass-spec + SASS_SASSC_PATH=sassc + AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SASSC_PATH) fi AM_CONDITIONAL(ENABLE_TESTS, test "x$enable_tests" = "xyes") -AM_CONDITIONAL(USE_TAP, test "x$use_tap" = "xyes") AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage], diff --git a/src/libsass/docs/implementations.md b/src/libsass/docs/implementations.md index 4814cdd8e..5239adcde 100644 --- a/src/libsass/docs/implementations.md +++ b/src/libsass/docs/implementations.md @@ -14,6 +14,17 @@ There are several implementations of `libsass` for a variety of languages. Here * [go_sass](https://github.com/suapapa/go_sass) * [go-sass](https://github.com/SamWhited/go-sass) +### Haskell +* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) +* [hSass](https://github.com/jakubfijalkowski/hsass) + +### Java +* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) +* [jsass](https://github.com/bit3/jsass) + +### JavaScript +* [sass.js](https://github.com/medialize/sass.js) + ### Lua * [lua-sass](https://github.com/craigbarnes/lua-sass) @@ -21,16 +32,14 @@ There are several implementations of `libsass` for a variety of languages. Here * [libsass-net](https://github.com/darrenkopp/libsass-net) * [NSass](https://github.com/TBAPI-0KA/NSass) * [Sass.Net](https://github.com/andyalm/Sass.Net) +* [SharpScss](https://github.com/xoofx/SharpScss) +* [LibSassHost](https://github.com/Taritsyn/LibSassHost) -### node.js -* [node-sass](https://github.com/andrew/node-sass) - -### Java -* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) -* [jsass](https://github.com/bit3/jsass) +### Nim +* [nim-sass](https://github.com/zacharycarter/nim-sass) -### JavaScript -* [sass.js](https://github.com/medialize/sass.js) +### node.js +* [node-sass](https://github.com/sass/node-sass) ### Perl * [CSS::Sass](https://github.com/caldwell/CSS-Sass) diff --git a/src/libsass/docs/unicode.md b/src/libsass/docs/unicode.md index 3897dcd6c..a1eb5b1cf 100644 --- a/src/libsass/docs/unicode.md +++ b/src/libsass/docs/unicode.md @@ -1,27 +1,33 @@ -LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. +LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your +input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected +behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! ### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) -This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I solved that by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. +This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. -Since my tool is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). +Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). ### Current status on LibSass unicode support -Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv). +LibSass should/is fully UTF (and therefore plain ASCII) compatible. + +~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ + +LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. ### Current encoding auto detection -LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). +LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! ### What is currently not supported -- Using non ASCII compatible encodings (like UTF-16) +- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) - Using non ASCII characters in different encodings in different includes ### What is missing to support the above cases -- A way to convert between encodings (like libiconv) +- A way to convert between encodings (like libiconv/ICU) - Sniffing the charset inside the file (source is available) - Handling the conversion on import (and export) - Optional: Make output encoding configurable @@ -31,9 +37,9 @@ LibSass currently reads all kind of BOMs and will error out if it finds somethin I guess the current implementation should handle more than 99% of all real world use cases. A) Unicode characters are still seldomly seen (as they can be written escaped) -B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. -Although I'm not sure how this applies to asian and other "exotic" codepages! +~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. +Although I'm not sure how this applies to asian and other "exotic" codepages!~~ -I guess the biggest Problem is to have libiconv (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). +I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/src/libsass/include/sass/context.h b/src/libsass/include/sass/context.h index 2f88d6888..29754b75f 100644 --- a/src/libsass/include/sass/context.h +++ b/src/libsass/include/sass/context.h @@ -149,6 +149,9 @@ ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); +// Push function for import extenions +ADDAPI void ADDCALL sass_option_push_import_extension (struct Sass_Options* options, const char* ext); + // Push function for paths (no manipulation support for now) ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); diff --git a/src/libsass/script/tap-runner b/src/libsass/script/tap-runner index 4adecafb9..56c13bfb4 100755 --- a/src/libsass/script/tap-runner +++ b/src/libsass/script/tap-runner @@ -1 +1 @@ -$@ | tapout tap \ No newline at end of file +$@ $TEST_FLAGS --tap --silent | tapout tap diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp index dae2cbd75..b199412cd 100644 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -96,6 +96,8 @@ namespace Sass { // include_paths.push_back(CWD); // collect more paths from different options + collect_extensions(c_options.extension); + collect_extensions(c_options.extensions); collect_include_paths(c_options.include_path); collect_include_paths(c_options.include_paths); collect_plugin_paths(c_options.plugin_path); @@ -166,6 +168,37 @@ namespace Sass { { } + void Context::collect_extensions(const char* exts_str) + { + if (exts_str) { + const char* beg = exts_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string ext(beg, end - beg); + if (!ext.empty()) { + extensions.push_back(ext); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string ext(beg); + if (!ext.empty()) { + extensions.push_back(ext); + } + } + } + + void Context::collect_extensions(string_list* paths_array) + { + while (paths_array) + { + collect_extensions(paths_array->string); + paths_array = paths_array->next; + } + } + void Context::collect_include_paths(const char* paths_str) { if (paths_str) { @@ -236,15 +269,20 @@ namespace Sass { // looks for alternatives and returns a list from one directory std::vector Context::find_includes(const Importer& import) { + // include configured extensions + std::vector exts(File::defaultExtensions); + if (extensions.size() > 0) { + exts.insert(exts.end(), extensions.begin(), extensions.end()); + } // make sure we resolve against an absolute path std::string base_path(rel2abs(import.base_path)); // first try to resolve the load path relative to the base path - std::vector vec(resolve_includes(base_path, import.imp_path)); + std::vector vec(resolve_includes(base_path, import.imp_path, exts)); // then search in every include path (but only if nothing found yet) for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) { // call resolve_includes and individual base path and append all results - std::vector resolved(resolve_includes(include_paths[i], import.imp_path)); + std::vector resolved(resolve_includes(include_paths[i], import.imp_path, exts)); if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); } // return vector @@ -365,6 +403,14 @@ namespace Sass { // process the resolved entry else if (resolved.size() == 1) { bool use_cache = c_importers.size() == 0; + if (resolved[0].deprecated) { + // emit deprecation warning when import resolves to a .css file + deprecated( + "Including .css files with @import is non-standard behaviour which will be removed in future versions of LibSass.", + "Use a custom importer to maintain this behaviour. Check your implementations documentation on how to create a custom importer.", + true, pstate + ); + } // use cache for the resource loading if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; // try to read the content of the resolved file entry diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp index d3caba13e..f14e69f6d 100644 --- a/src/libsass/src/context.hpp +++ b/src/libsass/src/context.hpp @@ -67,6 +67,7 @@ namespace Sass { std::vector plugin_paths; // relative paths to load plugins std::vector include_paths; // lookup paths for includes + std::vector extensions; // lookup extensions for imports` @@ -109,6 +110,8 @@ namespace Sass { void collect_plugin_paths(string_list* paths_array); void collect_include_paths(const char* paths_str); void collect_include_paths(string_list* paths_array); + void collect_extensions(const char* extensions_str); + void collect_extensions(string_list* extensions_array); std::string format_embedded_source_map(); std::string format_source_mapping_url(const std::string& out_path); diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp index 4c062a628..6a12fdf7b 100644 --- a/src/libsass/src/cssize.cpp +++ b/src/libsass/src/cssize.cpp @@ -559,7 +559,7 @@ namespace Sass { std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; - std::string m2 = std::string(mq2->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp index ee0d6eba7..f1ceabd9a 100644 --- a/src/libsass/src/debugger.hpp +++ b/src/libsass/src/debugger.hpp @@ -627,6 +627,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) Number_Ptr expression = Cast(node); std::cerr << ind << "Number " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << expression->unit() << "]" << " [hash: " << expression->hash() << "] " << diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp index 2ddfa93ea..841f7277b 100644 --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -599,10 +599,6 @@ namespace Sass { switch (op_type) { case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; - case Sass_OP::LT: return *l_n < *r_c ? bool_true : bool_false; - case Sass_OP::GTE: return *l_n < *r_c ? bool_false : bool_true; - case Sass_OP::LTE: return *l_n < *r_c || *l_n == *r_c ? bool_true : bool_false; - case Sass_OP::GT: return *l_n < *r_c || *l_n == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); default: break; @@ -643,10 +639,6 @@ namespace Sass { switch (op_type) { case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; - case Sass_OP::LT: return *l_c < *r_n ? bool_true : bool_false; - case Sass_OP::GTE: return *l_c < *r_n ? bool_false : bool_true; - case Sass_OP::LTE: return *l_c < *r_n || *l_c == *r_n ? bool_true : bool_false; - case Sass_OP::GT: return *l_c < *r_n || *l_c == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); default: break; @@ -1265,13 +1257,6 @@ namespace Sass { Expression_Ptr Eval::operator()(String_Constant_Ptr s) { - if (!s->is_delayed() && name_to_color(s->value())) { - Color_Ptr c = SASS_MEMORY_COPY(name_to_color(s->value())); // copy - c->pstate(s->pstate()); - c->disp(s->value()); - c->is_delayed(true); - return c; - } return s; } diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp index ccd2822df..d8dc03f14 100644 --- a/src/libsass/src/expand.cpp +++ b/src/libsass/src/expand.cpp @@ -246,7 +246,8 @@ namespace Sass { std::string str(prop->to_string(ctx.c_options)); new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); } - Expression_Obj value = d->value()->perform(&eval); + Expression_Obj value = d->value(); + if (value) value = value->perform(&eval); Block_Obj bb = ab ? operator()(ab) : NULL; if (!bb) { if (!value || (value->is_invisible() && !d->is_important())) return 0; diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp index 32d4a7c63..ab2065194 100644 --- a/src/libsass/src/file.cpp +++ b/src/libsass/src/file.cpp @@ -342,13 +342,13 @@ namespace Sass { for(auto ext : exts) { rel_path = join_paths(base, "_" + name + ext); abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); } // next test plain name with exts for(auto ext : exts) { rel_path = join_paths(base, name + ext); abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); } // nothing found return includes; diff --git a/src/libsass/src/file.hpp b/src/libsass/src/file.hpp index 279b9e9f6..a043bea7a 100644 --- a/src/libsass/src/file.hpp +++ b/src/libsass/src/file.hpp @@ -89,9 +89,14 @@ namespace Sass { public: // resolved absolute path std::string abs_path; + // is a deprecated file type + bool deprecated; public: + Include(const Importer& imp, std::string abs_path, bool deprecated) + : Importer(imp), abs_path(abs_path), deprecated(deprecated) + { } Include(const Importer& imp, std::string abs_path) - : Importer(imp), abs_path(abs_path) + : Importer(imp), abs_path(abs_path), deprecated(false) { } }; @@ -121,11 +126,12 @@ namespace Sass { namespace File { - static std::vector defaultExtensions = { ".scss", ".sass", ".css" }; + static std::vector defaultExtensions = { ".scss", ".sass" }; std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts = defaultExtensions); + } } diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp index 9b118d53a..c9999fc3a 100644 --- a/src/libsass/src/functions.cpp +++ b/src/libsass/src/functions.cpp @@ -467,7 +467,7 @@ namespace Sass { double s; double l = (max + min) / 2.0; - if (max == min) { + if (NEAR_EQUAL(max, min)) { h = s = 0; // achromatic } else { diff --git a/src/libsass/src/inspect.cpp b/src/libsass/src/inspect.cpp index 273ff8fb1..b4a66fab8 100644 --- a/src/libsass/src/inspect.cpp +++ b/src/libsass/src/inspect.cpp @@ -658,6 +658,9 @@ namespace Sass { } std::stringstream hexlet; + // dart sass compressed all colors in regular css always + // ruby sass and libsass does it only when not delayed + // since color math is going to be removed, this can go too bool compressed = opt.output_style == COMPRESSED; hexlet << '#' << std::setw(1) << std::setfill('0'); // create a short color hexlet if there is any need for it @@ -681,9 +684,6 @@ namespace Sass { if (name != "") { ss << name; } - else if (r == 0 && g == 0 && b == 0 && a == 0) { - ss << "transparent"; - } else if (a >= 1) { if (res_name != "") { if (compressed && hexlet.str().size() < res_name.size()) { diff --git a/src/libsass/src/operators.cpp b/src/libsass/src/operators.cpp index 65885bf19..02e303738 100644 --- a/src/libsass/src/operators.cpp +++ b/src/libsass/src/operators.cpp @@ -127,15 +127,15 @@ namespace Sass { double lval = lhs.value(); double rval = rhs.value(); + if (op == Sass_OP::MOD && rval == 0) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); + } + if (op == Sass_OP::DIV && rval == 0) { std::string result(lval ? "Infinity" : "NaN"); return SASS_MEMORY_NEW(String_Quoted, pstate, result); } - if (op == Sass_OP::MOD && rval == 0) { - throw Exception::ZeroDivisionError(lhs, rhs); - } - size_t l_n_units = lhs.numerators.size(); size_t l_d_units = lhs.numerators.size(); size_t r_n_units = rhs.denominators.size(); diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp index 525f199d1..ee51d56b6 100644 --- a/src/libsass/src/parser.cpp +++ b/src/libsass/src/parser.cpp @@ -1578,7 +1578,7 @@ namespace Sass { return nr; } - Expression_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) { Color_Ptr color = NULL; if (parsed[0] != '#') { @@ -1628,6 +1628,19 @@ namespace Sass { return color; } + Value_Ptr Parser::color_or_string(const std::string& lexed) const + { + if (auto color = name_to_color(lexed)) { + auto c = SASS_MEMORY_NEW(Color, color); + c->is_delayed(true); + c->pstate(pstate); + c->disp(lexed); + return c; + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + // parse one value for a list Expression_Obj Parser::parse_value() { @@ -1670,7 +1683,7 @@ namespace Sass { { return SASS_MEMORY_NEW(Null, pstate); } if (lex< identifier >()) { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + return color_or_string(lexed); } if (lex< percentage >()) @@ -1841,7 +1854,7 @@ namespace Sass { return schema->length() > 0 ? schema.detach() : NULL; } - String_Constant_Obj Parser::parse_static_value() + Value_Obj Parser::parse_static_value() { lex< static_value >(); Token str(lexed); @@ -1852,8 +1865,7 @@ namespace Sass { --str.end; --position; - String_Constant_Ptr str_node = SASS_MEMORY_NEW(String_Constant, pstate, str.time_wspace()); - return str_node; + return color_or_string(str.time_wspace());; } String_Obj Parser::parse_string() @@ -1962,7 +1974,7 @@ namespace Sass { if (lex< re_static_expression >()) { ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); } else { - ex = parse_list(); + ex = parse_list(true); } ex->is_interpolant(true); schema->append(ex); @@ -1986,7 +1998,7 @@ namespace Sass { } if (peek < exactly < '-' > >()) break; } - else if (lex< sequence < identifier > >()) { + else if (lex< identifier >()) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { // need_space = true; @@ -2691,7 +2703,9 @@ namespace Sass { if ((rv = lex_interp_string())) return rv; if ((rv = lex_interp_uri())) return rv; if ((rv = lex_interpolation())) return rv; - return rv; + if (lex< alternatives< hex, hex0 > >()) + { return lexed_hex_color(lexed); } + return rv; } String_Schema_Obj Parser::parse_almost_any_value() @@ -2777,6 +2791,7 @@ namespace Sass { >(p) ) { bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; + bool could_be_escaped = false; while (p < q) { // did we have interpolations? if (*p == '#' && *(p+1) == '{') { @@ -2785,9 +2800,10 @@ namespace Sass { } // A property that's ambiguous with a nested selector is interpreted as a // custom property. - if (*p == ':') { + if (*p == ':' && !could_be_escaped) { rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); } + could_be_escaped = *p == '\\'; ++ p; } // store anyway } diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp index 83c7f34ba..d2a6ddc1a 100644 --- a/src/libsass/src/parser.hpp +++ b/src/libsass/src/parser.hpp @@ -290,7 +290,7 @@ namespace Sass { String_Obj parse_url_function_argument(); String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); String_Obj parse_string(); - String_Constant_Obj parse_static_value(); + Value_Obj parse_static_value(); String_Schema_Obj parse_css_variable_value(bool top_level = true); String_Schema_Obj parse_css_variable_value_token(bool top_level = true); String_Obj parse_ie_property(); @@ -325,6 +325,8 @@ namespace Sass { Error_Obj parse_error(); Debug_Obj parse_debug(); + Value_Ptr color_or_string(const std::string& lexed) const; + // be more like ruby sass Expression_Obj lex_almost_any_value_token(); Expression_Obj lex_almost_any_value_chars(); @@ -380,12 +382,12 @@ namespace Sass { static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); - static Expression_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); + static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); private: Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; - Expression_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; + Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; static const char* re_attr_sensitive_close(const char* src); static const char* re_attr_insensitive_close(const char* src); diff --git a/src/libsass/src/prelexer.cpp b/src/libsass/src/prelexer.cpp index d0edbc9cc..a43b1ee3c 100644 --- a/src/libsass/src/prelexer.cpp +++ b/src/libsass/src/prelexer.cpp @@ -1606,7 +1606,7 @@ namespace Sass { >(src); } - extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;!"; + extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; const char* css_variable_top_level_value(const char* src) { return sequence< alternatives< diff --git a/src/libsass/src/sass.cpp b/src/libsass/src/sass.cpp index 98e349f48..72edd7ced 100644 --- a/src/libsass/src/sass.cpp +++ b/src/libsass/src/sass.cpp @@ -33,8 +33,10 @@ extern "C" { void* ADDCALL sass_alloc_memory(size_t size) { void* ptr = malloc(size); - if (ptr == NULL) - out_of_memory(); + if (ptr == NULL) { + std::cerr << "Out of memory.\n"; + exit(EXIT_FAILURE); + } return ptr; } diff --git a/src/libsass/src/sass2scss.cpp b/src/libsass/src/sass2scss.cpp index 56333b38e..8645d0c37 100644 --- a/src/libsass/src/sass2scss.cpp +++ b/src/libsass/src/sass2scss.cpp @@ -154,6 +154,21 @@ namespace Sass } + static size_t findFirstCharacter (std::string& sass, size_t pos) + { + return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); + } + + static size_t findLastCharacter (std::string& sass, size_t pos) + { + return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); + } + + static bool isUrl (std::string& sass, size_t pos) + { + return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; + } + // check if there is some char data // will ignore everything in comments static bool hasCharData (std::string& sass) @@ -587,6 +602,7 @@ namespace Sass sass.substr(pos_left, 5) == "@warn" || sass.substr(pos_left, 6) == "@debug" || sass.substr(pos_left, 6) == "@error" || + sass.substr(pos_left, 6) == "@value" || sass.substr(pos_left, 8) == "@charset" || sass.substr(pos_left, 10) == "@namespace" ) { sass = indent + sass.substr(pos_left); } @@ -606,23 +622,38 @@ namespace Sass { // get positions for the actual import url size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - // leave proper urls untouched - if (sass.substr(pos_quote, 4) != "url(") - { - // check if the url appears to be already quoted - if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'") - { - // get position of the last char on the line - size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - // assertion check for valid result - if (pos_end != std::string::npos) - { - // add quotes around the full line after the import statement - sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\""; + size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); + size_t start = pos; + bool in_dqstr = false; + bool in_sqstr = false; + bool is_escaped = false; + do { + if (is_escaped) { + is_escaped = false; + } + else if (sass[pos] == '\\') { + is_escaped = true; + } + else if (sass[pos] == '"') { + if (!in_sqstr) in_dqstr = ! in_dqstr; + } + else if (sass[pos] == '\'') { + if (!in_dqstr) in_sqstr = ! in_sqstr; + } + else if (in_dqstr || in_sqstr) { + // skip over quoted stuff + } + else if (sass[pos] == ',' || sass[pos] == 0) { + if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { + size_t end = findLastCharacter(sass, pos - 1) + 1; + sass = sass.replace(end, 0, "\""); + sass = sass.replace(start, 0, "\""); + pos += 2; } + start = findFirstCharacter(sass, pos + 1); } } + while (sass[pos++] != 0); } else if ( diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp index afadc66e1..7a0a49ce1 100644 --- a/src/libsass/src/sass_context.cpp +++ b/src/libsass/src/sass_context.cpp @@ -74,14 +74,14 @@ namespace Sass { // move line_beg pointer to line start while (line_beg && *line_beg && lines != 0) { if (*line_beg == '\n') --lines; - utf8::unchecked::next(line_beg); + utf8::unchecked::next(line_beg); } const char* line_end = line_beg; // move line_end before next newline character while (line_end && *line_end && *line_end != '\n') { if (*line_end == '\n') break; if (*line_end == '\r') break; - utf8::unchecked::next(line_end); + utf8::unchecked::next(line_end); } if (line_end && *line_end != 0) ++ line_end; size_t line_len = line_end - line_beg; @@ -524,6 +524,7 @@ extern "C" { options->c_headers = 0; options->plugin_paths = 0; options->include_paths = 0; + options->extensions = 0; } // helper function, not exported, only accessible locally @@ -558,6 +559,18 @@ extern "C" { cur = next; } } + // Deallocate extension + if (options->extensions) { + struct string_list* cur; + struct string_list* next; + cur = options->extensions; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } // Free options strings free(options->input_path); free(options->output_path); @@ -577,6 +590,7 @@ extern "C" { options->c_headers = 0; options->plugin_paths = 0; options->include_paths = 0; + options->extensions = 0; } // helper function, not exported, only accessible locally @@ -713,6 +727,22 @@ extern "C" { IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); + // Push function for import extenions + void ADDCALL sass_option_push_import_extension(struct Sass_Options* options, const char* ext) + { + struct string_list* extension = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (extension == 0) return; + extension->string = ext ? sass_copy_c_string(ext) : 0; + struct string_list* last = options->extensions; + if (!options->extensions) { + options->extensions = extension; + } else { + while (last->next) + last = last->next; + last->next = extension; + } + } + // Push function for include paths (no manipulation support for now) void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) { diff --git a/src/libsass/src/sass_context.hpp b/src/libsass/src/sass_context.hpp index 8ae1fb12c..9d192a301 100644 --- a/src/libsass/src/sass_context.hpp +++ b/src/libsass/src/sass_context.hpp @@ -40,9 +40,12 @@ struct Sass_Options : Sass_Output_Options { // Colon-separated list of paths // Semicolon-separated on Windows // Maybe use array interface instead? + char* extension; char* include_path; char* plugin_path; + // Extensions (linked string list) + struct string_list* extensions; // Include paths (linked string list) struct string_list* include_paths; // Plugin paths (linked string list) @@ -126,4 +129,4 @@ struct Sass_Compiler { Sass::Block_Obj root; }; -#endif \ No newline at end of file +#endif diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp index 4313d502d..f23475fe0 100644 --- a/src/libsass/src/util.hpp +++ b/src/libsass/src/util.hpp @@ -12,11 +12,6 @@ namespace Sass { - #define out_of_memory() do { \ - std::cerr << "Out of memory.\n"; \ - exit(EXIT_FAILURE); \ - } while (0) - double round(double val, size_t precision = 0); double sass_strtod(const char* str); const char* safe_str(const char *, const char* = ""); From cc7301e9a9e61129f83c52b10b833e1d54695829 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Apr 2018 02:09:39 +1000 Subject: [PATCH 130/286] Re-implement raw CSS imports for the deprecation warning Raw CSS imports have been removed from LibSass. We need to opt into the feature with `sass_option_push_import_extension`. We're must do this to maintain BC. Any imported files with a `.css` extension will produce a deprecation warning. This commit should be reverted in v5. This would remove raw CSS imports once and for all. --- src/binding.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/binding.cpp b/src/binding.cpp index abf298fcf..cb9ed132c 100644 --- a/src/binding.cpp +++ b/src/binding.cpp @@ -113,6 +113,7 @@ int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapp sass_option_set_precision(sass_options, Nan::To(Nan::Get(options, Nan::New("precision").ToLocalChecked()).ToLocalChecked()).FromJust()); sass_option_set_indent(sass_options, ctx_w->indent); sass_option_set_linefeed(sass_options, ctx_w->linefeed); + sass_option_push_import_extension(sass_options, ".css"); v8::Local importer_callback = Nan::Get(options, Nan::New("importer").ToLocalChecked()).ToLocalChecked(); From a124e9d42e1f5bdb0a96fca78435c889e6e2b41f Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Apr 2018 16:06:39 +1000 Subject: [PATCH 131/286] Add Node 10 to CI --- .travis.yml | 6 ++++++ appveyor.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8893c565e..f7d17297d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,12 @@ jobs: - stage: platform-test node_js: "node" os: osx + - stage: platform-test + node_js: "9" + os: linux + - stage: platform-test + node_js: "9" + os: osx - stage: platform-test node_js: "7" os: linux diff --git a/appveyor.yml b/appveyor.yml index bcf5b4d47..fad8c01bb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -67,6 +67,9 @@ - nodejs_version: 9 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 10 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform @@ -148,6 +151,9 @@ - nodejs_version: 9 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 10 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform From 739d76898d3e776efa29e9dde3333dc27ab79460 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Apr 2018 16:37:20 +1000 Subject: [PATCH 132/286] Bump gcc@4.9 for Node 10 --- .travis.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7d17297d..018399b3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,14 +55,24 @@ addons: packages: - gcc-4.7 - g++-4.7 + - gcc-4.9 + - g++-4.9 before_install: + - echo $TRAVIS_NODE_VERSION - npm config set python `which python` - if [ $TRAVIS_OS_NAME == "linux" ]; then - export CC="gcc-4.7"; - export CXX="g++-4.7"; - export LINK="gcc-4.7"; - export LINKXX="g++-4.7"; + if [[ $(node -v) =~ "10" ]]; then + export CC="gcc-4.9"; + export CXX="g++-4.9"; + export LINK="gcc-4.9"; + export LINKXX="g++-4.9"; + else + export CC="gcc-4.7"; + export CXX="g++-4.7"; + export LINK="gcc-4.7"; + export LINKXX="g++-4.7"; + fi fi - nvm --version - node --version From 9d6faf6f0a005dc73870b1f87268014a82f170e3 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 25 Apr 2018 15:58:19 +1000 Subject: [PATCH 133/286] Bump LibSass@3.5.4 See https://github.com/sass/libsass/releases/tag/3.5.4 Revert to sass-spec@3.5.4-1 because of specs that depend on features reverted in 3.5.4. --- package.json | 4 +-- src/libsass/src/sass2scss.cpp | 59 +++++++++-------------------------- 2 files changed, 16 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 5c5972278..ab61c02c0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.9.0", - "libsass": "3.5.3", + "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.3", + "sass-spec": "3.5.4-1", "unique-temp-dir": "^1.0.0" } } diff --git a/src/libsass/src/sass2scss.cpp b/src/libsass/src/sass2scss.cpp index 8645d0c37..56333b38e 100644 --- a/src/libsass/src/sass2scss.cpp +++ b/src/libsass/src/sass2scss.cpp @@ -154,21 +154,6 @@ namespace Sass } - static size_t findFirstCharacter (std::string& sass, size_t pos) - { - return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static size_t findLastCharacter (std::string& sass, size_t pos) - { - return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static bool isUrl (std::string& sass, size_t pos) - { - return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; - } - // check if there is some char data // will ignore everything in comments static bool hasCharData (std::string& sass) @@ -602,7 +587,6 @@ namespace Sass sass.substr(pos_left, 5) == "@warn" || sass.substr(pos_left, 6) == "@debug" || sass.substr(pos_left, 6) == "@error" || - sass.substr(pos_left, 6) == "@value" || sass.substr(pos_left, 8) == "@charset" || sass.substr(pos_left, 10) == "@namespace" ) { sass = indent + sass.substr(pos_left); } @@ -622,38 +606,23 @@ namespace Sass { // get positions for the actual import url size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - size_t start = pos; - bool in_dqstr = false; - bool in_sqstr = false; - bool is_escaped = false; - do { - if (is_escaped) { - is_escaped = false; - } - else if (sass[pos] == '\\') { - is_escaped = true; - } - else if (sass[pos] == '"') { - if (!in_sqstr) in_dqstr = ! in_dqstr; - } - else if (sass[pos] == '\'') { - if (!in_dqstr) in_sqstr = ! in_sqstr; - } - else if (in_dqstr || in_sqstr) { - // skip over quoted stuff - } - else if (sass[pos] == ',' || sass[pos] == 0) { - if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { - size_t end = findLastCharacter(sass, pos - 1) + 1; - sass = sass.replace(end, 0, "\""); - sass = sass.replace(start, 0, "\""); - pos += 2; + size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); + // leave proper urls untouched + if (sass.substr(pos_quote, 4) != "url(") + { + // check if the url appears to be already quoted + if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'") + { + // get position of the last char on the line + size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + // assertion check for valid result + if (pos_end != std::string::npos) + { + // add quotes around the full line after the import statement + sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\""; } - start = findFirstCharacter(sass, pos + 1); } } - while (sass[pos++] != 0); } else if ( From 26a20320d14f85ac48fc302d6c00f7fb1e785579 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 27 Apr 2018 17:43:34 -0400 Subject: [PATCH 134/286] Add PR Template for Request bumps --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..0745d189b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ + From 8c4808a64e5484d3ab62b9d8323ac93d272332da Mon Sep 17 00:00:00 2001 From: rupav jain Date: Sun, 6 May 2018 09:22:36 +0530 Subject: [PATCH 135/286] Updated links to absolute path instead of relative (#2371) with relative path to troubleshoot guide, npm was unable to direct to correct page. Now its corrected using absolute path. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 537c8acdc..aa399b894 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,13 @@ Some users have reported issues installing on Ubuntu due to `node` being registe Compiling on Windows machines requires the [node-gyp prerequisites](https://github.com/nodejs/node-gyp#on-windows). -Are you seeing the following error? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md#installing-node-sass-4x-with-node--4).** +Are you seeing the following error? Check out our [Troubleshooting guide](https://github.com/sass/node-sass/blob/master/TROUBLESHOOTING.md#installing-node-sass-4x-with-node--4).** ``` SyntaxError: Use of const in strict mode. ``` -**Having installation troubles? Check out our [Troubleshooting guide](/TROUBLESHOOTING.md).** +**Having installation troubles? Check out our [Troubleshooting guide](https://github.com/sass/node-sass/blob/master/TROUBLESHOOTING.md).** ### Install from mirror in China From e3ab6e18c54a1cf2f1954508942188038a4f19a1 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 8 May 2018 10:12:16 +0100 Subject: [PATCH 136/286] Clarify docs for --source-map. Closes #1026. It is not clear from the `--help` docs that `--source-map` requires a boolean or path. Passing the flag alone results in a TypeError. There's an argument to be made that passing the flag alone should imply `true`, or at the very least it should fail with a helpful error, not a cryptic TypeError. For now, this PR fixes the --help docs so that they match the README. --- bin/node-sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node-sass b/bin/node-sass index 5f1a8d650..54660ed6e 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -47,7 +47,7 @@ var cli = meow({ ' --indent-width Indent width; number of spaces or tabs (maximum value: 10)', ' --linefeed Linefeed style (cr | crlf | lf | lfcr)', ' --source-comments Include debug info in output', - ' --source-map Emit source map', + ' --source-map Emit source map (boolean, or path to output .map file)', ' --source-map-contents Embed include contents in map', ' --source-map-embed Embed sourceMappingUrl as data URI', ' --source-map-root Base path, will be emitted in source-map as is', From 6fef242ebd1509d1168fb15ba5db35e850ff5ffc Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 10 May 2018 09:08:02 +0300 Subject: [PATCH 137/286] Updates README.md with AppVeyor svg badge (#2376) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa399b894..02b3f6879 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@
[![Build Status](https://travis-ci.org/sass/node-sass.svg?branch=master&style=flat)](https://travis-ci.org/sass/node-sass) -[![Build status](https://ci.appveyor.com/api/projects/status/22mjbk59kvd55m9y/branch/master)](https://ci.appveyor.com/project/sass/node-sass/branch/master) +[![Build status](https://ci.appveyor.com/api/projects/status/22mjbk59kvd55m9y/branch/master?svg=true)](https://ci.appveyor.com/project/sass/node-sass/branch/master) [![npm version](https://badge.fury.io/js/node-sass.svg)](http://badge.fury.io/js/node-sass) [![Dependency Status](https://david-dm.org/sass/node-sass.svg?theme=shields.io)](https://david-dm.org/sass/node-sass) [![devDependency Status](https://david-dm.org/sass/node-sass/dev-status.svg?theme=shields.io)](https://david-dm.org/sass/node-sass#info=devDependencies) From 8268296b42a4a2fca789e36398b13dd78b20a34c Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 9 May 2018 23:44:58 -0700 Subject: [PATCH 138/286] doc: New ISSUE Template for Request Security issues --- .github/ISSUE_TEMPLATE.md | 2 -- .github/ISSUE_TEMPLATE/Request_Security.md | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Request_Security.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 2c3f5d06d..b6178a867 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,7 +1,5 @@ diff --git a/.github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md b/.github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md new file mode 100644 index 000000000..dd7dd38f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md @@ -0,0 +1,10 @@ + From 043e2bce02b17fc740bad43f7587768a1000ac8e Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 10 May 2018 00:03:34 -0700 Subject: [PATCH 140/286] docs: Move and update Installation template --- .../Installation_Problem.md} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/Installation_Problem.md} (64%) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/Installation_Problem.md similarity index 64% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/Installation_Problem.md index 785ce6e54..527db0b14 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/Installation_Problem.md @@ -1,11 +1,12 @@ From 8878118c96c6bf00fe39779fb31d89d8647181fe Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 10 May 2018 00:15:51 -0700 Subject: [PATCH 141/286] docs: Add Feature request issue template --- .github/ISSUE_TEMPLATE/Feature_Request.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/Feature_Request.md diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md new file mode 100644 index 000000000..613780620 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Request.md @@ -0,0 +1,11 @@ + From e23531d538276417cfb9d8f5d3300f9b74db7e47 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Sun, 13 May 2018 11:39:59 -0400 Subject: [PATCH 142/286] Update issue templates using builder --- .github/ISSUE_TEMPLATE/Bug_report.md | 25 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/Custom.md | 7 +++++++ .github/ISSUE_TEMPLATE/Feature_request.md | 17 +++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/Custom.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 000000000..8272e9d64 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,25 @@ +--- +name: Bug report +about: If you're having an issue with installing + +--- + + + +- NPM version (`npm -v`): +- Node version (`node -v`): +- Node Process (`node -p process.versions`): +- Node Platform (`node -p process.platform`): +- Node architecture (`node -p process.arch`): +- node-sass version (`node -p "require('node-sass').info"`): +- npm node-sass versions (`npm ls node-sass`): diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md new file mode 100644 index 000000000..41be13971 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -0,0 +1,7 @@ +--- +name: 'Security: Request/Hawk/Hoek is being flagged' +about: We cannot address this till we drop support for Node < 6. Subscribe to 2355 + +--- + +See https://github.com/sass/node-sass/issues/2355 for the reason we can't upgrade the Request version that your security scanner is flagging. diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 000000000..de9fdbea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + + From 91973eda5b79304ec651a371d240d0bbd00cd754 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Sun, 13 May 2018 11:48:57 -0400 Subject: [PATCH 143/286] chore: Add compile issue details to bug template --- .github/ISSUE_TEMPLATE/Bug_report.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 8272e9d64..6085e154e 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,18 +1,19 @@ --- name: Bug report -about: If you're having an issue with installing +about: If you're having an issue with installing node-sass or the compiled results + look incorrect --- @@ -23,3 +24,15 @@ or your issue will be closed without discussion** - Node architecture (`node -p process.arch`): - node-sass version (`node -p "require('node-sass').info"`): - npm node-sass versions (`npm ls node-sass`): + + From 94ce8529486643f350d8a658791a4e6204db0e70 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 16 May 2018 10:31:55 -0400 Subject: [PATCH 144/286] Be even more explicit that Node 10 needs 4.9 --- .github/ISSUE_TEMPLATE/Bug_report.md | 77 ++++++++++++----------- .github/ISSUE_TEMPLATE/Custom.md | 14 ++--- .github/ISSUE_TEMPLATE/Feature_request.md | 34 +++++----- 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 6085e154e..a2b5f9c8a 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,38 +1,39 @@ ---- -name: Bug report -about: If you're having an issue with installing node-sass or the compiled results - look incorrect - ---- - - - -- NPM version (`npm -v`): -- Node version (`node -v`): -- Node Process (`node -p process.versions`): -- Node Platform (`node -p process.platform`): -- Node architecture (`node -p process.arch`): -- node-sass version (`node -p "require('node-sass').info"`): -- npm node-sass versions (`npm ls node-sass`): - - +--- +name: Bug report +about: If you're having an issue with installing node-sass or the compiled results + look incorrect + +--- + + + +- NPM version (`npm -v`): +- Node version (`node -v`): +- Node Process (`node -p process.versions`): +- Node Platform (`node -p process.platform`): +- Node architecture (`node -p process.arch`): +- node-sass version (`node -p "require('node-sass').info"`): +- npm node-sass versions (`npm ls node-sass`): + + diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md index 41be13971..d3489958a 100644 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -1,7 +1,7 @@ ---- -name: 'Security: Request/Hawk/Hoek is being flagged' -about: We cannot address this till we drop support for Node < 6. Subscribe to 2355 - ---- - -See https://github.com/sass/node-sass/issues/2355 for the reason we can't upgrade the Request version that your security scanner is flagging. +--- +name: 'Security: Request/Hawk/Hoek is being flagged' +about: We cannot address this till we drop support for Node < 6. Subscribe to 2355 + +--- + +See https://github.com/sass/node-sass/issues/2355 for the reason we can't upgrade the Request version that your security scanner is flagging. diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index de9fdbea7..0d9189631 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,17 +1,17 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - - +--- +name: Feature request +about: Suggest an idea for this project + +--- + + From e0a92f666ef488d5fd64df8d522219e7e68e2408 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 21 May 2018 01:35:23 -0400 Subject: [PATCH 145/286] docs: Cleanup issue templates --- .../Compile_results_are_incorrect.md | 10 ---------- .../ISSUE_TEMPLATE/Installation_Problem.md | 19 ------------------- .github/ISSUE_TEMPLATE/Request_Security.md | 3 --- 3 files changed, 32 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md delete mode 100644 .github/ISSUE_TEMPLATE/Installation_Problem.md delete mode 100644 .github/ISSUE_TEMPLATE/Request_Security.md diff --git a/.github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md b/.github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md deleted file mode 100644 index dd7dd38f9..000000000 --- a/.github/ISSUE_TEMPLATE/Compile_results_are_incorrect.md +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/.github/ISSUE_TEMPLATE/Installation_Problem.md b/.github/ISSUE_TEMPLATE/Installation_Problem.md deleted file mode 100644 index 527db0b14..000000000 --- a/.github/ISSUE_TEMPLATE/Installation_Problem.md +++ /dev/null @@ -1,19 +0,0 @@ - - -- NPM version (`npm -v`): -- Node version (`node -v`): -- Node Process (`node -p process.versions`): -- Node Platform (`node -p process.platform`): -- Node architecture (`node -p process.arch`): -- node-sass version (`node -p "require('node-sass').info"`): -- npm node-sass versions (`npm ls node-sass`): diff --git a/.github/ISSUE_TEMPLATE/Request_Security.md b/.github/ISSUE_TEMPLATE/Request_Security.md deleted file mode 100644 index 4db73c1e3..000000000 --- a/.github/ISSUE_TEMPLATE/Request_Security.md +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not open an issue - -See https://github.com/sass/node-sass/issues/2355 for the reason we can't upgrade the Request version that your security scanner is flagging. From a3ac021ab8e6e94ff0d1d459933545bdb2682151 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 21 May 2018 22:23:31 -0400 Subject: [PATCH 146/286] Clean out duplicate ISSUE template --- .github/ISSUE_TEMPLATE/Feature_Request.md | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Feature_Request.md diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md deleted file mode 100644 index 613780620..000000000 --- a/.github/ISSUE_TEMPLATE/Feature_Request.md +++ /dev/null @@ -1,11 +0,0 @@ - From 8040cb7a0ab72a7c34120cc05be9e3e518f9ba34 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 21 May 2018 01:33:22 -0400 Subject: [PATCH 147/286] docs: add more 404 binding install info --- TROUBLESHOOTING.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 3433561f9..e3a5e66d6 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -5,6 +5,7 @@ This document covers some common node-sass issues and how to resolve them. You s ## TOC - [Installation problems](#installation-problems) + - [404 downloading binding.node file](#404-downloading-bindingnode-file) - [Assertion failed: (handle->flags & UV_CLOSING), function uv__finish_close](#assertion-failed-handle-flags-&-uv_closing-function-uv__finish_close) - [Cannot find module '/root/<...>/install.js'](#cannot-find-module-rootinstalljs) - [Linux](#linux) @@ -20,6 +21,16 @@ This document covers some common node-sass issues and how to resolve them. You s ## Installation problems +### 404 downloading binding.node file + +If you see a 404 when trying to install node-sas, this indicates that your trying +to install a version of node-sass that doesn't support your verion of NodeJS, or +uses an alternate V8 environment (Meteor, Electron, etc...) that isn't supported +by node-sass. +If you encounter this, please check what version of NodeJs you're running (`node -v`) +and check for a supported version of node-sass for your NodeJs by checking our +[release page](https://github.com/sass/node-sass/releases). + ### Assertion failed: (handle->flags & UV_CLOSING), function uv__finish_close This issue primarily affected early node-sass@3.0.0 alpha and beta releases, although it did occassionally happen in node-sass@2.x. @@ -46,7 +57,9 @@ If this didn't solve your problem please open an issue with the output from [our ### npm 5 -Some users upgrading from previous versions of npm have found conflicts with old lock file formats. This may be show up as a URL instead of the actual version number when downloading the binary. EX: +Some users upgrading from previous versions of npm before 5 have found conflicts with +old lock file formats. This may be show up as a URL instead of the actual version +number when downloading the binary. EX: ```console Downloading binary from https://github.com/sass/node-sass/releases/download/vhttps://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz/win32-x64-57_binding.node From 64fdacfe7a7aea7f19da83e1660370c0655d01ad Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 13 Jun 2018 20:09:24 -0400 Subject: [PATCH 148/286] chore: Add link to 2355 on PR template --- .github/PULL_REQUEST_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0745d189b..cddd6984c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,5 +2,7 @@ **Do not bump the Request package, it breaks old Node compatiblity. It is used for downloading the binaries.** +Follow https://github.com/sass/node-sass/issues/2355 for updates on when that will be resolved. + Thanks for contibuting otherwise! --> From 18d198eb24033a32980b65eff29b0b5f1e6bca46 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 19 Jun 2018 00:42:00 -0400 Subject: [PATCH 149/286] typo: node-sas -> node-sass --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index e3a5e66d6..7ef229474 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -23,7 +23,7 @@ This document covers some common node-sass issues and how to resolve them. You s ### 404 downloading binding.node file -If you see a 404 when trying to install node-sas, this indicates that your trying +If you see a 404 when trying to install node-sass, this indicates that your trying to install a version of node-sass that doesn't support your verion of NodeJS, or uses an alternate V8 environment (Meteor, Electron, etc...) that isn't supported by node-sass. From d3aebe72d2735b79e68019b3801000fe8178f18b Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 25 Jun 2018 21:11:17 -0400 Subject: [PATCH 150/286] Create CODE_OF_CONDUCT.md Copied from main ruby-sass --- CODE_OF_CONDUCT.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..dfc4c84a7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,10 @@ +Sass is more than a technology; Sass is driven by the community of individuals +that power its development and use every day. As a community, we want to embrace +the very differences that have made our collaboration so powerful, and work +together to provide the best environment for learning, growing, and sharing of +ideas. It is imperative that we keep Sass a fun, welcoming, challenging, and +fair place to play. + +[The full community guidelines can be found on the Sass website.][link] + +[link]: http://sass-lang.com/community-guidelines From 62fd84a8416fe41aecd02d7c39dbe5b6e6e22cd3 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 28 Jun 2018 00:19:59 -0400 Subject: [PATCH 151/286] chore: Add info for "Pinned" label --- .github/ISSUE_TEMPLATE/Bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index a2b5f9c8a..cae82da69 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -6,13 +6,13 @@ about: If you're having an issue with installing node-sass or the compiled resul --- diff --git a/package.json b/package.json index ab61c02c0..f446b746f 100644 --- a/package.json +++ b/package.json @@ -68,13 +68,13 @@ "nan": "^2.10.0", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", - "request": "~2.79.0", + "request": "2.87.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, "devDependencies": { - "coveralls": "^2.11.8", + "coveralls": "^3.0.2", "eslint": "^3.4.0", "fs-extra": "^0.30.0", "istanbul": "^0.4.2", From cc6ff42d27580cb5d0413b46f1f795ee56fc96b1 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 5 Jul 2018 00:08:16 +1000 Subject: [PATCH 153/286] Restore old node to CI --- .travis.yml | 6 ++++++ appveyor.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.travis.yml b/.travis.yml index 018399b3d..1f4cb35f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,12 @@ jobs: - stage: platform-test node_js: "lts/argon" os: osx + - stage: platform-test + node_js: "0.12" + os: linux + - stage: platform-test + node_js: "0.10" + os: linux addons: apt: diff --git a/appveyor.yml b/appveyor.yml index fad8c01bb..37d38103d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -136,6 +136,12 @@ environment: SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: + - nodejs_version: 0.10 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - nodejs_version: 0.12 + GYP_MSVS_VERSION: 2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - nodejs_version: 4 GYP_MSVS_VERSION: 2013 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 From 240e8da8f14bf9d7fa10fac9e27889ad949a3143 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 5 Jul 2018 20:03:20 +1000 Subject: [PATCH 154/286] 4.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f446b746f..f4fdaf4b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.9.0", + "version": "4.9.1", "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", From cba089d2ffd51d631750c6b4ee3b91ac542a4843 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 5 Jul 2018 20:29:50 +1000 Subject: [PATCH 155/286] Remove custom issue template Request has been updated so this irrelevant --- .github/ISSUE_TEMPLATE/Custom.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Custom.md diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md deleted file mode 100644 index d3489958a..000000000 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: 'Security: Request/Hawk/Hoek is being flagged' -about: We cannot address this till we drop support for Node < 6. Subscribe to 2355 - ---- - -See https://github.com/sass/node-sass/issues/2355 for the reason we can't upgrade the Request version that your security scanner is flagging. From 57c8b590fef43a6ac815b56ddce91ceb318c9818 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 5 Jul 2018 20:31:39 +1000 Subject: [PATCH 156/286] Stop telling people to run npm rebuild with --force This forces a local compilation (slow) when they just want to download right binary (fast). --- lib/errors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/errors.js b/lib/errors.js index 6bfb77f29..f729db6e6 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -25,7 +25,7 @@ function foundBinariesList() { function missingBinaryFooter() { return [ 'This usually happens because your environment has changed since running `npm install`.', - 'Run `npm rebuild node-sass --force` to build the binding for your current environment.', + 'Run `npm rebuild node-sass` to download the binding for your current environment.', ].join('\n'); } From ecfcab00b33d66e64b69915ae2210312f239f521 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 8 Jul 2018 16:31:50 +1000 Subject: [PATCH 157/286] 4.9.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4fdaf4b3..ed84ad57e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.9.1", + "version": "4.9.2", "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", From 71fe854c23eab4ae67df2450428141edfbf2953d Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 19 Jul 2018 01:25:37 -0400 Subject: [PATCH 158/286] chore: Typo offically -> officially --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 7ef229474..b4f77001a 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -271,4 +271,4 @@ Alternatively, if you prefer using system-installed node.js (supposedly higher v ### Installing node-sass 4.x with Node < 4 -See the discussion in [this comment](https://github.com/sass/node-sass/issues/2100#issuecomment-334651235) for a workaround. As of node-sass@v5 only Node 6 and above will be offically supported. +See the discussion in [this comment](https://github.com/sass/node-sass/issues/2100#issuecomment-334651235) for a workaround. As of node-sass@v5 only Node 6 and above will be officially supported. From e8f6b85ab14106fc52bc19dd6a93a6265d906dd2 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 19 Jul 2018 01:50:34 -0400 Subject: [PATCH 159/286] docs: First pass reformatting --- TROUBLESHOOTING.md | 148 +++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 66 deletions(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index b4f77001a..ae20d3470 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -1,11 +1,12 @@ # Troubleshooting -This document covers some common node-sass issues and how to resolve them. You should always follow these steps before opening a new issue. +This document covers some common node-sass issues and how to resolve them. You +should always follow these steps before opening a new issue. ## TOC - [Installation problems](#installation-problems) - - [404 downloading binding.node file](#404-downloading-bindingnode-file) + - [404 downloading binding.node file](#404s) - [Assertion failed: (handle->flags & UV_CLOSING), function uv__finish_close](#assertion-failed-handle-flags-&-uv_closing-function-uv__finish_close) - [Cannot find module '/root/<...>/install.js'](#cannot-find-module-rootinstalljs) - [Linux](#linux) @@ -19,43 +20,52 @@ This document covers some common node-sass issues and how to resolve them. You s - [Using node-sass with Visual Studio 2015 Task Runner.](#using-node-sass-with-visual-studio-2015-task-runner) - [Installing node-sass 4.x with Node < 4](#installing-node-sass-4x-with-node--4) -## Installation problems +## Installation -### 404 downloading binding.node file +### 404s If you see a 404 when trying to install node-sass, this indicates that your trying -to install a version of node-sass that doesn't support your verion of NodeJS, or +to install a version of node-sass that doesn't support your version of NodeJS, or uses an alternate V8 environment (Meteor, Electron, etc...) that isn't supported by node-sass. -If you encounter this, please check what version of NodeJs you're running (`node -v`) -and check for a supported version of node-sass for your NodeJs by checking our -[release page](https://github.com/sass/node-sass/releases). -### Assertion failed: (handle->flags & UV_CLOSING), function uv__finish_close +```console +> node-sass@4.6.1 install /src/node_modules/node-sass +> node scripts/install.js -This issue primarily affected early node-sass@3.0.0 alpha and beta releases, although it did occassionally happen in node-sass@2.x. +Downloading binary from https://github.com/sass/node-sass/releas… +Cannot download "https://github.com/sass/node-sass/releas…": -The only fix for this issue is to update to node-sass >= 3.0.0. +HTTP error 404 Not Found +``` -If this didn't solve your problem please open an issue with the output from [our debugging script](#debugging-installation-issues). +If you encounter this, please check what version of NodeJs you're running (`node -v`) +and check for a supported version of node-sass for your NodeJs by checking our +[release page](https://github.com/sass/node-sass/releases). +### Proxy issues -### Cannot find module '/root/<...>/install.js' +If you work in behind a corporate proxy try setting the proxy variables. The +following is a [guide for setting this up](https://jjasonclark.com/how-to-setup-node-behind-web-proxy/). -#### Linux +### Running with sudo or as root -This can happen if you are install node-sass as `root`, or globally with `sudo`. This is a security feature of `npm`. You should always avoid running `npm` as `sudo` because install scripts can be unintentionally malicious. +This can happen if you are install node-sass as `root`, or globally with `sudo`. +This is a security feature of `npm`. You should always avoid running `npm` as +`sudo` because install scripts can be unintentionally malicious. Please check [npm documentation on fixing permissions](https://docs.npmjs.com/getting-started/fixing-npm-permissions). -If you must however, you can work around this error by using the `--unsafe-perm` flag with npm install i.e. +If you must however, you can work around this error by using the `--unsafe-perm` +flag with npm install i.e. ```sh -$ sudo npm install --unsafe-perm -g node-sass +sudo npm install --unsafe-perm -g node-sass ``` -If this didn't solve your problem please open an issue with the output from [our debugging script](#debugging-installation-issues). +If this didn't solve your problem please open an issue with the output from +[our debugging script](#debugging-installation-issues). -### npm 5 +### npm Some users upgrading from previous versions of npm before 5 have found conflicts with old lock file formats. This may be show up as a URL instead of the actual version @@ -77,34 +87,25 @@ npm cache clean npm install ``` -## Glossary - - -### Which node runtime am I using? - -There are two primary node runtimes, Node.js and io.js, both of which are supported by node-sass. To determine which you are currently using you first need to determine [which node runtime](#which-node-runtime-am-i-using-glossaryruntime) you are running. - -``` -node -v -``` - -If the version reported begins with a `0`, you are running Node.js, otherwise you are running io.js. +## Helping us, help you +### Find what version of Node you're running -### Which version of node am I using? +To determine which version of Node.js or io.js you are currently using run the +following command in a terminal. -To determine which version of Node.js or io.js you are currenty using run the following command in a terminal. - -``` +```console node -v ``` The resulting value the version you are running. +### Debugging installation issues -### Debugging installation issues. - -Node sass runs some install scripts to make it as easy to use as possible, but some times there can be issues. Before opening a new issue please follow the instructions for [Windows](#windows) or [Linux/OSX](#linuxosx) and provide their output in you [GitHub issue](https://github.com/sass/node-sass/issues). +Node sass runs some install scripts to make it as easy to use as possible, but +some times there can be issues. Before opening a new issue please follow the +instructions for [Windows](#windows) or [Linux/OSX](#linuxosx) and provide +their output in you [GitHub issue](https://github.com/sass/node-sass/issues). **Remember to always search before opening a new issue**. @@ -112,14 +113,14 @@ Node sass runs some install scripts to make it as easy to use as possible, but s Firstly create a clean work space. -```sh +```console mkdir \temp1 cd \temp1 ``` Check your `COMSPEC` environment variable. -```sh +```console node -p process.env.comspec ``` @@ -127,7 +128,7 @@ Please make sure the variable points to `C:\WINDOWS\System32\cmd.exe` Gather some basic diagnostic information. -```sh +```console npm -v node -v node -p process.versions @@ -137,64 +138,68 @@ node -p process.arch Clean npm cache -```sh +```console npm cache clean ``` Install the latest node-sass -```sh -npm install -ddd node-sass > npm.log 2> npm.err +```console +npm install node-sass@latest ``` Note which version was installed by running -```sh +```console npm ls node-sass ``` -```sh + +```console y@1.0.0 /tmp └── node-sass@3.8.0 ``` -If node-sass could not be installed successfully, please publish your `npm.log` +If node-sass couldn't be installed successfully, please publish your `npm.log` and `npm.err` files for analysis. You can [download reference known-good logfiles](https://gist.github.com/saper/62b6e5ea41695c1883e3) to compare your log against. -If node-sass install successfully lets gather some basic installation infomation. +If node-sass install successfully lets gather some basic installation information. -```sh +```console node -p "require('node-sass').info" ``` -```sh + +```console node-sass 3.8.0 (Wrapper) [JavaScript] libsass 3.3.6 (Sass Compiler) [C/C++] ``` If the node-sass installation process produced an error, open the vendor folder. -```sh +```console cd node_modules\node-sass\vendor ``` Then, using the version number we gather at the beginning, go to `https://github.com/sass/node-sass/releases/tag/v`. -There you should see a folder with same name as the one in the `vendor` folder. Download the `binding.node` file from that folder and replace your own with it. +There you should see a folder with same name as the one in the `vendor` folder. +Download the `binding.node` file from that folder and replace your own with it. -Test if that worked by gathering some basic installation infomation. +Test if that worked by gathering some basic installation information. -```sh +```console node -p "require('node-sass').info" ``` -```sh + +```console node-sass 3.8.0 (Wrapper) [JavaScript] libsass 3.3.6 (Sass Compiler) [C/C++] ``` -If this still produces an error please open an issue with the output from these steps. - +If this still produces an error please open an issue with the output from these +steps. #### Linux/OSX @@ -218,7 +223,7 @@ node -p process.arch Install the latest node-sass ```sh -npm install node-sass +npm install node-sass@latest ``` Note which version was installed by running @@ -226,16 +231,18 @@ Note which version was installed by running ```sh npm ls node-sass ``` + ```sh y@1.0.0 /tmp └── node-sass@3.8.0 ``` -If node-sass install successfully lets gather some basic installation infomation. +If node-sass install successfully lets gather some basic installation information. ```sh node -p "require('node-sass').info" ``` + ```sh node-sass 3.8.0 (Wrapper) [JavaScript] libsass 3.3.6 (Sass Compiler) [C/C++] @@ -249,26 +256,35 @@ cd node_modules/node-sass/vendor Then, using the version number we gather at the beginning, go to `https://github.com/sass/node-sass/releases/tag/v`. -There you should see a folder with same name as the one in the `vendor` folder. Download the `binding.node` file from that folder and replace your own with it. +There you should see a folder with same name as the one in the `vendor` folder. +Download the `binding.node` file from that folder and replace your own with it. -Test if that worked by gathering some basic installation infomation. +Test if that worked by gathering some basic installation information. ```sh node -p "require('node-sass').info" ``` + ```sh node-sass 3.8.0 (Wrapper) [JavaScript] libsass 3.3.6 (Sass Compiler) [C/C++] ``` -If this still produces an error please open an issue with the output from these steps. +If this still produces an error please open an issue with the output from these +steps. -### Using node-sass with Visual Studio 2015 Task Runner. +### Using node-sass with Visual Studio 2015 Task Runner -If you are using node-sass with VS2015 Task Runner Explorer, you need to make sure that the version of node.js (or io.js) is same as the one you installed node-sass with. This is because for each node.js runtime modules version (`node -p process.versions.modules`), we have a separate build of native binary. See [#532](https://github.com/sass/node-sass/issues/532). +If you are using node-sass with VS2015 Task Runner Explorer, you need to make +sure that the version of node.js is same as the one you installed node-sass +with. This is because for each node.js runtime modules version (`node -p process.versions.modules`) +, we have a separate build of native binary. See [#532](https://github.com/sass/node-sass/issues/532). -Alternatively, if you prefer using system-installed node.js (supposedly higher version than one bundles with VS2015), you may want to point Visual Studio 2015 to use it for task runner jobs by following the guidelines available at: http://blogs.msdn.com/b/webdev/archive/2015/03/19/customize-external-web-tools-in-visual-studio-2015.aspx. +Alternatively, if you prefer using system-installed node.js (supposedly higher +version than one bundles with VS2015), you may want to point Visual Studio 2015 +to use it for task runner jobs by [following the guidelines](http://blogs.msdn.com/b/webdev/archive/2015/03/19/customize-external-web-tools-in-visual-studio-2015.aspx). ### Installing node-sass 4.x with Node < 4 -See the discussion in [this comment](https://github.com/sass/node-sass/issues/2100#issuecomment-334651235) for a workaround. As of node-sass@v5 only Node 6 and above will be officially supported. +See the discussion in [this comment](https://github.com/sass/node-sass/issues/2100#issuecomment-334651235) +for a workaround. As of node-sass@v5 only Node 6 and above will be officially supported. From 60d9ae9354f5af29f4990a4901b46ba015eacd56 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 19 Jul 2018 02:00:04 -0400 Subject: [PATCH 160/286] chore: Remove Travis Gitter hook (#2453) --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f4cb35f8..9e4b0cd1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -97,9 +97,3 @@ cache: - $HOME/.node-gyp - $HOME/.npm - node_modules - -notifications: - webhooks: - urls: - - https://webhooks.gitter.im/e/8dddd234a441d0d07664 - on_success: change From 33e8b36327cfbf25b5f09d5e0fe6e4328409dd0b Mon Sep 17 00:00:00 2001 From: Jorrit Schippers Date: Tue, 24 Jul 2018 15:37:58 +0200 Subject: [PATCH 161/286] Typo: verion -> version --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 7ef229474..981ff7065 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -24,7 +24,7 @@ This document covers some common node-sass issues and how to resolve them. You s ### 404 downloading binding.node file If you see a 404 when trying to install node-sass, this indicates that your trying -to install a version of node-sass that doesn't support your verion of NodeJS, or +to install a version of node-sass that doesn't support your version of NodeJS, or uses an alternate V8 environment (Meteor, Electron, etc...) that isn't supported by node-sass. If you encounter this, please check what version of NodeJs you're running (`node -v`) From ff64b094457d6039240d98526029f027f9a74d5e Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 9 Aug 2018 01:16:32 -0400 Subject: [PATCH 162/286] fix: bump node-gyp for hoek fix Closes #2355 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed84ad57e..de6ac4166 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.10.0", - "node-gyp": "^3.3.1", + "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "2.87.0", "sass-graph": "^2.2.4", From cdf24f212e11e7ee0406ae23798fce62d10da5a3 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 9 Aug 2018 22:55:00 +1000 Subject: [PATCH 163/286] 4.9.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de6ac4166..7b8b293ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.9.2", + "version": "4.9.3", "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", From 746759cc4def477a8594c49ef299a15aa2ed1746 Mon Sep 17 00:00:00 2001 From: Yoann Colin Date: Mon, 17 Sep 2018 09:59:35 +0200 Subject: [PATCH 164/286] Upgrade request package to v.2.88 The package `extend 3.0.1`, which is a dependency of `request 2.87` has a vulnerability : https://hackerone.com/reports/381185 Upgrade `request` to v.2.88 will install `extend` v.3.0.2, the fixed version. Fix #2496 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b8b293ce..796775d3c 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "nan": "^2.10.0", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", - "request": "2.87.0", + "request": "^2.88.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" From 4aa398202d68c6519b8b4625de3529ed937927c5 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Wed, 24 Oct 2018 10:06:40 +1100 Subject: [PATCH 165/286] Add Node 11 to AppVeyor --- appveyor.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 37d38103d..ed7c2f8ff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -70,6 +70,9 @@ - nodejs_version: 10 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 11 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform @@ -160,7 +163,10 @@ - nodejs_version: 10 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - + - nodejs_version: 11 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + install: - ps: Install-Product node $env:nodejs_version $env:platform - node --version From 97849b23e5f00fad493466810ce046e2a27a4f05 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Wed, 24 Oct 2018 10:07:57 +1100 Subject: [PATCH 166/286] Add Node 11 to TravisCI --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9e4b0cd1e..854f4e3d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,9 @@ jobs: - stage: platform-test node_js: "node" os: osx + - stage: platform-test + node_js: "10" + os: osx - stage: platform-test node_js: "9" os: linux From f74e9cd3b6e0d7759c9c002e3c5459f9cc0af646 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Wed, 24 Oct 2018 10:36:47 +1100 Subject: [PATCH 167/286] Update .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 854f4e3d7..9ebdd18f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,9 @@ jobs: - stage: platform-test node_js: "node" os: osx + - stage: platform-test + node_js: "10" + os: linux - stage: platform-test node_js: "10" os: osx From 0c31dc28c4d825e47c4c194673946839d4106c30 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 23 Oct 2018 21:40:52 -0400 Subject: [PATCH 168/286] build: Use GCC 4.9 for Travis Node 10 and 11 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9ebdd18f7..8c7acd20b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,7 +74,7 @@ before_install: - echo $TRAVIS_NODE_VERSION - npm config set python `which python` - if [ $TRAVIS_OS_NAME == "linux" ]; then - if [[ $(node -v) =~ "10" ]]; then + if [[ $(node -v) =~ v1[01] ]]; then export CC="gcc-4.9"; export CXX="g++-4.9"; export LINK="gcc-4.9"; From c73e2fc1cee3056b5d9acced23ee684862abc8e6 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 23 Oct 2018 21:55:29 -0400 Subject: [PATCH 169/286] feat: Add detecton for Node 11 (module 67) --- lib/extensions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/extensions.js b/lib/extensions.js index 948f4604b..47ada7166 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -76,6 +76,7 @@ function getHumanNodeVersion(abi) { case 57: return 'Node.js 8.x'; case 59: return 'Node.js 9.x'; case 64: return 'Node.js 10.x'; + case 67: return 'Node.js 11.x'; default: return false; } } From c65a1bfe2ce221521503d3111f3fe2a34f524f36 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 4 Nov 2018 12:52:29 +1100 Subject: [PATCH 170/286] 4.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 796775d3c..7ccc32e3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.9.3", + "version": "4.10.0", "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", From 9b7015c79a0ed496ac2c384ff3b1b17bd8c98ddf Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Nov 2018 13:19:24 +1100 Subject: [PATCH 171/286] Update changelog Fixes #2538 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7952408ed..1a96e5726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +## v4.10.0 + +https://github.com/sass/node-sass/releases/tag/v4.10.0 + +## v4.9.4 + +https://github.com/sass/node-sass/releases/tag/v4.9.4 + +## v4.9.3 + +https://github.com/sass/node-sass/releases/tag/v4.9.3 + +## v4.9.2 + +https://github.com/sass/node-sass/releases/tag/v4.9.2 + +## v4.9.1 + +https://github.com/sass/node-sass/releases/tag/v4.9.1 + +## v4.9.0 + +https://github.com/sass/node-sass/releases/tag/v4.9.0 + +## v4.8.3 + +https://github.com/sass/node-sass/releases/tag/v4.8.3 + +## v4.8.2 + +https://github.com/sass/node-sass/releases/tag/v4.8.2 + ## v4.8.1 https://github.com/sass/node-sass/releases/tag/v4.8.1 From 688d654e520a287a8389d8daa0b2f2cfb9b60fa9 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Nov 2018 16:15:36 +1100 Subject: [PATCH 172/286] Remove committed src/libsass --- src/libsass/.editorconfig | 15 - src/libsass/.gitattributes | 2 - src/libsass/.github/CONTRIBUTING.md | 65 - src/libsass/.github/ISSUE_TEMPLATE.md | 54 - src/libsass/.gitignore | 85 - src/libsass/.travis.yml | 64 - src/libsass/COPYING | 25 - src/libsass/GNUmakefile.am | 74 - src/libsass/INSTALL | 1 - src/libsass/LICENSE | 25 - src/libsass/Makefile | 351 -- src/libsass/Makefile.conf | 55 - src/libsass/Readme.md | 104 - src/libsass/SECURITY.md | 10 - src/libsass/appveyor.yml | 91 - src/libsass/configure.ac | 134 - src/libsass/contrib/libsass.spec | 66 - src/libsass/contrib/plugin.cpp | 60 - src/libsass/docs/README.md | 20 - src/libsass/docs/api-context-example.md | 45 - src/libsass/docs/api-context-internal.md | 163 - src/libsass/docs/api-context.md | 295 -- src/libsass/docs/api-doc.md | 215 -- src/libsass/docs/api-function-example.md | 67 - src/libsass/docs/api-function-internal.md | 8 - src/libsass/docs/api-function.md | 74 - src/libsass/docs/api-importer-example.md | 112 - src/libsass/docs/api-importer-internal.md | 20 - src/libsass/docs/api-importer.md | 86 - src/libsass/docs/api-value-example.md | 55 - src/libsass/docs/api-value-internal.md | 76 - src/libsass/docs/api-value.md | 154 - src/libsass/docs/build-on-darwin.md | 27 - src/libsass/docs/build-on-gentoo.md | 55 - src/libsass/docs/build-on-windows.md | 139 - src/libsass/docs/build-shared-library.md | 35 - src/libsass/docs/build-with-autotools.md | 78 - src/libsass/docs/build-with-makefiles.md | 68 - src/libsass/docs/build-with-mingw.md | 107 - src/libsass/docs/build-with-visual-studio.md | 90 - src/libsass/docs/build.md | 97 - src/libsass/docs/compatibility-plan.md | 48 - src/libsass/docs/contributing.md | 17 - src/libsass/docs/custom-functions-internal.md | 122 - src/libsass/docs/dev-ast-memory.md | 223 -- src/libsass/docs/implementations.md | 65 - src/libsass/docs/plugins.md | 47 - src/libsass/docs/setup-environment.md | 68 - src/libsass/docs/source-map-internals.md | 51 - src/libsass/docs/trace.md | 26 - src/libsass/docs/triage.md | 17 - src/libsass/docs/unicode.md | 45 - src/libsass/extconf.rb | 6 - src/libsass/include/sass.h | 15 - src/libsass/include/sass/base.h | 89 - src/libsass/include/sass/context.h | 173 - src/libsass/include/sass/functions.h | 139 - src/libsass/include/sass/values.h | 145 - src/libsass/include/sass/version.h | 12 - src/libsass/include/sass/version.h.in | 12 - src/libsass/include/sass2scss.h | 120 - src/libsass/m4/.gitkeep | 0 src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 | 167 - src/libsass/res/resource.rc | 35 - src/libsass/script/bootstrap | 13 - src/libsass/script/branding | 10 - src/libsass/script/ci-build-libsass | 134 - src/libsass/script/ci-build-plugin | 62 - src/libsass/script/ci-install-compiler | 6 - src/libsass/script/ci-install-deps | 20 - src/libsass/script/ci-report-coverage | 42 - src/libsass/script/spec | 5 - src/libsass/script/tap-driver | 652 ---- src/libsass/script/tap-runner | 1 - src/libsass/script/test-leaks.pl | 103 - src/libsass/src/GNUmakefile.am | 54 - src/libsass/src/ast.cpp | 2226 ------------ src/libsass/src/ast.hpp | 3049 ---------------- src/libsass/src/ast_def_macros.hpp | 80 - src/libsass/src/ast_fwd_decl.cpp | 29 - src/libsass/src/ast_fwd_decl.hpp | 463 --- src/libsass/src/b64/cencode.h | 32 - src/libsass/src/b64/encode.h | 79 - src/libsass/src/backtrace.cpp | 46 - src/libsass/src/backtrace.hpp | 29 - src/libsass/src/base64vlq.cpp | 44 - src/libsass/src/base64vlq.hpp | 30 - src/libsass/src/bind.cpp | 311 -- src/libsass/src/bind.hpp | 13 - src/libsass/src/c99func.c | 54 - src/libsass/src/cencode.c | 108 - src/libsass/src/check_nesting.cpp | 398 --- src/libsass/src/check_nesting.hpp | 65 - src/libsass/src/color_maps.cpp | 648 ---- src/libsass/src/color_maps.hpp | 331 -- src/libsass/src/constants.cpp | 179 - src/libsass/src/constants.hpp | 181 - src/libsass/src/context.cpp | 926 ----- src/libsass/src/context.hpp | 155 - src/libsass/src/cssize.cpp | 606 ---- src/libsass/src/cssize.hpp | 77 - src/libsass/src/debug.hpp | 43 - src/libsass/src/debugger.hpp | 801 ----- src/libsass/src/emitter.cpp | 297 -- src/libsass/src/emitter.hpp | 99 - src/libsass/src/environment.cpp | 246 -- src/libsass/src/environment.hpp | 113 - src/libsass/src/error_handling.cpp | 235 -- src/libsass/src/error_handling.hpp | 216 -- src/libsass/src/eval.cpp | 1663 --------- src/libsass/src/eval.hpp | 103 - src/libsass/src/expand.cpp | 817 ----- src/libsass/src/expand.hpp | 82 - src/libsass/src/extend.cpp | 2130 ----------- src/libsass/src/extend.hpp | 86 - src/libsass/src/file.cpp | 485 --- src/libsass/src/file.hpp | 139 - src/libsass/src/functions.cpp | 2234 ------------ src/libsass/src/functions.hpp | 198 -- src/libsass/src/inspect.cpp | 1138 ------ src/libsass/src/inspect.hpp | 103 - src/libsass/src/json.cpp | 1436 -------- src/libsass/src/json.hpp | 117 - src/libsass/src/kwd_arg_macros.hpp | 28 - src/libsass/src/lexer.cpp | 181 - src/libsass/src/lexer.hpp | 315 -- src/libsass/src/listize.cpp | 86 - src/libsass/src/listize.hpp | 34 - src/libsass/src/mapping.hpp | 18 - src/libsass/src/memory/SharedPtr.cpp | 114 - src/libsass/src/memory/SharedPtr.hpp | 206 -- src/libsass/src/node.cpp | 319 -- src/libsass/src/node.hpp | 118 - src/libsass/src/operation.hpp | 173 - src/libsass/src/operators.cpp | 240 -- src/libsass/src/operators.hpp | 30 - src/libsass/src/output.cpp | 336 -- src/libsass/src/output.hpp | 54 - src/libsass/src/parser.cpp | 3137 ----------------- src/libsass/src/parser.hpp | 400 --- src/libsass/src/paths.hpp | 71 - src/libsass/src/plugins.cpp | 184 - src/libsass/src/plugins.hpp | 57 - src/libsass/src/position.cpp | 181 - src/libsass/src/position.hpp | 124 - src/libsass/src/prelexer.cpp | 1774 ---------- src/libsass/src/prelexer.hpp | 484 --- src/libsass/src/remove_placeholders.cpp | 84 - src/libsass/src/remove_placeholders.hpp | 35 - src/libsass/src/sass.cpp | 151 - src/libsass/src/sass.hpp | 139 - src/libsass/src/sass2scss.cpp | 864 ----- src/libsass/src/sass_context.cpp | 799 ----- src/libsass/src/sass_context.hpp | 132 - src/libsass/src/sass_functions.cpp | 207 -- src/libsass/src/sass_functions.hpp | 50 - src/libsass/src/sass_util.cpp | 149 - src/libsass/src/sass_util.hpp | 256 -- src/libsass/src/sass_values.cpp | 357 -- src/libsass/src/sass_values.hpp | 82 - src/libsass/src/source_map.cpp | 195 - src/libsass/src/source_map.hpp | 62 - src/libsass/src/subset_map.cpp | 55 - src/libsass/src/subset_map.hpp | 76 - src/libsass/src/support/libsass.pc.in | 11 - src/libsass/src/to_c.cpp | 74 - src/libsass/src/to_c.hpp | 39 - src/libsass/src/to_value.cpp | 112 - src/libsass/src/to_value.hpp | 50 - src/libsass/src/units.cpp | 501 --- src/libsass/src/units.hpp | 109 - src/libsass/src/utf8.h | 34 - src/libsass/src/utf8/checked.h | 334 -- src/libsass/src/utf8/core.h | 329 -- src/libsass/src/utf8/unchecked.h | 235 -- src/libsass/src/utf8_string.cpp | 102 - src/libsass/src/utf8_string.hpp | 37 - src/libsass/src/util.cpp | 733 ---- src/libsass/src/util.hpp | 56 - src/libsass/src/values.cpp | 131 - src/libsass/src/values.hpp | 12 - src/libsass/test/test_node.cpp | 94 - src/libsass/test/test_paths.cpp | 28 - src/libsass/test/test_selector_difference.cpp | 25 - src/libsass/test/test_specificity.cpp | 25 - src/libsass/test/test_subset_map.cpp | 472 --- src/libsass/test/test_superselector.cpp | 69 - src/libsass/test/test_unification.cpp | 31 - src/libsass/version.sh | 10 - src/libsass/win/libsass.sln | 39 - src/libsass/win/libsass.sln.DotSettings | 9 - src/libsass/win/libsass.targets | 118 - src/libsass/win/libsass.vcxproj | 188 - src/libsass/win/libsass.vcxproj.filters | 357 -- 194 files changed, 45772 deletions(-) delete mode 100644 src/libsass/.editorconfig delete mode 100644 src/libsass/.gitattributes delete mode 100644 src/libsass/.github/CONTRIBUTING.md delete mode 100644 src/libsass/.github/ISSUE_TEMPLATE.md delete mode 100644 src/libsass/.gitignore delete mode 100644 src/libsass/.travis.yml delete mode 100644 src/libsass/COPYING delete mode 100644 src/libsass/GNUmakefile.am delete mode 100644 src/libsass/INSTALL delete mode 100644 src/libsass/LICENSE delete mode 100644 src/libsass/Makefile delete mode 100644 src/libsass/Makefile.conf delete mode 100644 src/libsass/Readme.md delete mode 100644 src/libsass/SECURITY.md delete mode 100644 src/libsass/appveyor.yml delete mode 100644 src/libsass/configure.ac delete mode 100644 src/libsass/contrib/libsass.spec delete mode 100644 src/libsass/contrib/plugin.cpp delete mode 100644 src/libsass/docs/README.md delete mode 100644 src/libsass/docs/api-context-example.md delete mode 100644 src/libsass/docs/api-context-internal.md delete mode 100644 src/libsass/docs/api-context.md delete mode 100644 src/libsass/docs/api-doc.md delete mode 100644 src/libsass/docs/api-function-example.md delete mode 100644 src/libsass/docs/api-function-internal.md delete mode 100644 src/libsass/docs/api-function.md delete mode 100644 src/libsass/docs/api-importer-example.md delete mode 100644 src/libsass/docs/api-importer-internal.md delete mode 100644 src/libsass/docs/api-importer.md delete mode 100644 src/libsass/docs/api-value-example.md delete mode 100644 src/libsass/docs/api-value-internal.md delete mode 100644 src/libsass/docs/api-value.md delete mode 100644 src/libsass/docs/build-on-darwin.md delete mode 100644 src/libsass/docs/build-on-gentoo.md delete mode 100644 src/libsass/docs/build-on-windows.md delete mode 100644 src/libsass/docs/build-shared-library.md delete mode 100644 src/libsass/docs/build-with-autotools.md delete mode 100644 src/libsass/docs/build-with-makefiles.md delete mode 100644 src/libsass/docs/build-with-mingw.md delete mode 100644 src/libsass/docs/build-with-visual-studio.md delete mode 100644 src/libsass/docs/build.md delete mode 100644 src/libsass/docs/compatibility-plan.md delete mode 100644 src/libsass/docs/contributing.md delete mode 100644 src/libsass/docs/custom-functions-internal.md delete mode 100644 src/libsass/docs/dev-ast-memory.md delete mode 100644 src/libsass/docs/implementations.md delete mode 100644 src/libsass/docs/plugins.md delete mode 100644 src/libsass/docs/setup-environment.md delete mode 100644 src/libsass/docs/source-map-internals.md delete mode 100644 src/libsass/docs/trace.md delete mode 100644 src/libsass/docs/triage.md delete mode 100644 src/libsass/docs/unicode.md delete mode 100644 src/libsass/extconf.rb delete mode 100644 src/libsass/include/sass.h delete mode 100644 src/libsass/include/sass/base.h delete mode 100644 src/libsass/include/sass/context.h delete mode 100644 src/libsass/include/sass/functions.h delete mode 100644 src/libsass/include/sass/values.h delete mode 100644 src/libsass/include/sass/version.h delete mode 100644 src/libsass/include/sass/version.h.in delete mode 100644 src/libsass/include/sass2scss.h delete mode 100644 src/libsass/m4/.gitkeep delete mode 100644 src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 delete mode 100644 src/libsass/res/resource.rc delete mode 100755 src/libsass/script/bootstrap delete mode 100755 src/libsass/script/branding delete mode 100755 src/libsass/script/ci-build-libsass delete mode 100755 src/libsass/script/ci-build-plugin delete mode 100755 src/libsass/script/ci-install-compiler delete mode 100755 src/libsass/script/ci-install-deps delete mode 100755 src/libsass/script/ci-report-coverage delete mode 100755 src/libsass/script/spec delete mode 100755 src/libsass/script/tap-driver delete mode 100755 src/libsass/script/tap-runner delete mode 100755 src/libsass/script/test-leaks.pl delete mode 100644 src/libsass/src/GNUmakefile.am delete mode 100644 src/libsass/src/ast.cpp delete mode 100644 src/libsass/src/ast.hpp delete mode 100644 src/libsass/src/ast_def_macros.hpp delete mode 100644 src/libsass/src/ast_fwd_decl.cpp delete mode 100644 src/libsass/src/ast_fwd_decl.hpp delete mode 100644 src/libsass/src/b64/cencode.h delete mode 100644 src/libsass/src/b64/encode.h delete mode 100644 src/libsass/src/backtrace.cpp delete mode 100644 src/libsass/src/backtrace.hpp delete mode 100644 src/libsass/src/base64vlq.cpp delete mode 100644 src/libsass/src/base64vlq.hpp delete mode 100644 src/libsass/src/bind.cpp delete mode 100644 src/libsass/src/bind.hpp delete mode 100644 src/libsass/src/c99func.c delete mode 100644 src/libsass/src/cencode.c delete mode 100644 src/libsass/src/check_nesting.cpp delete mode 100644 src/libsass/src/check_nesting.hpp delete mode 100644 src/libsass/src/color_maps.cpp delete mode 100644 src/libsass/src/color_maps.hpp delete mode 100644 src/libsass/src/constants.cpp delete mode 100644 src/libsass/src/constants.hpp delete mode 100644 src/libsass/src/context.cpp delete mode 100644 src/libsass/src/context.hpp delete mode 100644 src/libsass/src/cssize.cpp delete mode 100644 src/libsass/src/cssize.hpp delete mode 100644 src/libsass/src/debug.hpp delete mode 100644 src/libsass/src/debugger.hpp delete mode 100644 src/libsass/src/emitter.cpp delete mode 100644 src/libsass/src/emitter.hpp delete mode 100644 src/libsass/src/environment.cpp delete mode 100644 src/libsass/src/environment.hpp delete mode 100644 src/libsass/src/error_handling.cpp delete mode 100644 src/libsass/src/error_handling.hpp delete mode 100644 src/libsass/src/eval.cpp delete mode 100644 src/libsass/src/eval.hpp delete mode 100644 src/libsass/src/expand.cpp delete mode 100644 src/libsass/src/expand.hpp delete mode 100644 src/libsass/src/extend.cpp delete mode 100644 src/libsass/src/extend.hpp delete mode 100644 src/libsass/src/file.cpp delete mode 100644 src/libsass/src/file.hpp delete mode 100644 src/libsass/src/functions.cpp delete mode 100644 src/libsass/src/functions.hpp delete mode 100644 src/libsass/src/inspect.cpp delete mode 100644 src/libsass/src/inspect.hpp delete mode 100644 src/libsass/src/json.cpp delete mode 100644 src/libsass/src/json.hpp delete mode 100644 src/libsass/src/kwd_arg_macros.hpp delete mode 100644 src/libsass/src/lexer.cpp delete mode 100644 src/libsass/src/lexer.hpp delete mode 100644 src/libsass/src/listize.cpp delete mode 100644 src/libsass/src/listize.hpp delete mode 100644 src/libsass/src/mapping.hpp delete mode 100644 src/libsass/src/memory/SharedPtr.cpp delete mode 100644 src/libsass/src/memory/SharedPtr.hpp delete mode 100644 src/libsass/src/node.cpp delete mode 100644 src/libsass/src/node.hpp delete mode 100644 src/libsass/src/operation.hpp delete mode 100644 src/libsass/src/operators.cpp delete mode 100644 src/libsass/src/operators.hpp delete mode 100644 src/libsass/src/output.cpp delete mode 100644 src/libsass/src/output.hpp delete mode 100644 src/libsass/src/parser.cpp delete mode 100644 src/libsass/src/parser.hpp delete mode 100644 src/libsass/src/paths.hpp delete mode 100644 src/libsass/src/plugins.cpp delete mode 100644 src/libsass/src/plugins.hpp delete mode 100644 src/libsass/src/position.cpp delete mode 100644 src/libsass/src/position.hpp delete mode 100644 src/libsass/src/prelexer.cpp delete mode 100644 src/libsass/src/prelexer.hpp delete mode 100644 src/libsass/src/remove_placeholders.cpp delete mode 100644 src/libsass/src/remove_placeholders.hpp delete mode 100644 src/libsass/src/sass.cpp delete mode 100644 src/libsass/src/sass.hpp delete mode 100644 src/libsass/src/sass2scss.cpp delete mode 100644 src/libsass/src/sass_context.cpp delete mode 100644 src/libsass/src/sass_context.hpp delete mode 100644 src/libsass/src/sass_functions.cpp delete mode 100644 src/libsass/src/sass_functions.hpp delete mode 100644 src/libsass/src/sass_util.cpp delete mode 100644 src/libsass/src/sass_util.hpp delete mode 100644 src/libsass/src/sass_values.cpp delete mode 100644 src/libsass/src/sass_values.hpp delete mode 100644 src/libsass/src/source_map.cpp delete mode 100644 src/libsass/src/source_map.hpp delete mode 100644 src/libsass/src/subset_map.cpp delete mode 100644 src/libsass/src/subset_map.hpp delete mode 100644 src/libsass/src/support/libsass.pc.in delete mode 100644 src/libsass/src/to_c.cpp delete mode 100644 src/libsass/src/to_c.hpp delete mode 100644 src/libsass/src/to_value.cpp delete mode 100644 src/libsass/src/to_value.hpp delete mode 100644 src/libsass/src/units.cpp delete mode 100644 src/libsass/src/units.hpp delete mode 100644 src/libsass/src/utf8.h delete mode 100644 src/libsass/src/utf8/checked.h delete mode 100644 src/libsass/src/utf8/core.h delete mode 100644 src/libsass/src/utf8/unchecked.h delete mode 100644 src/libsass/src/utf8_string.cpp delete mode 100644 src/libsass/src/utf8_string.hpp delete mode 100644 src/libsass/src/util.cpp delete mode 100644 src/libsass/src/util.hpp delete mode 100644 src/libsass/src/values.cpp delete mode 100644 src/libsass/src/values.hpp delete mode 100644 src/libsass/test/test_node.cpp delete mode 100644 src/libsass/test/test_paths.cpp delete mode 100644 src/libsass/test/test_selector_difference.cpp delete mode 100644 src/libsass/test/test_specificity.cpp delete mode 100644 src/libsass/test/test_subset_map.cpp delete mode 100644 src/libsass/test/test_superselector.cpp delete mode 100644 src/libsass/test/test_unification.cpp delete mode 100755 src/libsass/version.sh delete mode 100644 src/libsass/win/libsass.sln delete mode 100644 src/libsass/win/libsass.sln.DotSettings delete mode 100644 src/libsass/win/libsass.targets delete mode 100644 src/libsass/win/libsass.vcxproj delete mode 100644 src/libsass/win/libsass.vcxproj.filters diff --git a/src/libsass/.editorconfig b/src/libsass/.editorconfig deleted file mode 100644 index c3d859e7a..000000000 --- a/src/libsass/.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -# This file is for unifying the coding style for different editors and IDEs -# editorconfig.org - -root = true - -[*] -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -indent_style = space -indent_size = 2 - -[{Makefile, GNUmakefile.am}] -indent_style = tab -indent_size = 4 diff --git a/src/libsass/.gitattributes b/src/libsass/.gitattributes deleted file mode 100644 index dfe077042..000000000 --- a/src/libsass/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/src/libsass/.github/CONTRIBUTING.md b/src/libsass/.github/CONTRIBUTING.md deleted file mode 100644 index 2ff99d8bd..000000000 --- a/src/libsass/.github/CONTRIBUTING.md +++ /dev/null @@ -1,65 +0,0 @@ -# Contributing to LibSass - -:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: - -The following is a set of guidelines for contributing to LibSass, which is hosted in the [Sass Organization](https://github.com/sass) on GitHub. -These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. - -LibSass is a library that implements a [sass language][8] compiler. As such it does not directly interface with end users (frontend developers). -For direct contributions to the LibSass code base you will need to have at least a rough idea of C++, we will not lie about that. -But there are other ways to contribute to the progress of LibSass. All contributions are done via github pull requests. - -You can also contribute to the LibSass [documentation][9] or provide additional [spec tests][10] (and we will gladly point you in the -direction for corners that lack test coverage). Foremost we rely on good and concise bug reports for issues the spec tests do not yet catch. - -## Precheck: My Sass isn't compiling -- [ ] Check if you can reproduce the issue via [SourceMap Inspector][5] (updated regularly). -- [ ] Validate official ruby sass compiler via [SassMeister][6] produces your expected result. -- [ ] Search for similar issue in [LibSass][1] and [node-sass][2] (include closed tickets) -- [ ] Optionally test your code directly with [sass][7] or [sassc][3] ([installer][4]) - -## Precheck: My build/install fails -- [ ] Problems with building or installing libsass should be directed to implementors first! -- [ ] Except for issues directly verified via sassc or LibSass own build (make/autotools9 - -## Craft a meaningfull error report -- [ ] Include the version of libsass and the implementor (i.e. node-sass or sassc) -- [ ] Include information about your operating system and environment (i.e. io.js) -- [ ] Either create a self contained sample that shows your issue ... -- [ ] ... or provide it as a fetchable (github preferred) archive/repo -- [ ] ... and include a step by step list of command to get all dependencies -- [ ] Make it clear if you use indented or/and scss syntax - -## My error is hiding in a big code base -1. we do not have time to support your code base! -2. to fix occuring issues we need precise bug reports -3. the more precise you are, the faster we can help you -4. lazy reports get overlooked even when exposing serious bugs -5. it's not hard to do, it only takes time -- [ ] Make sure you saved the current state (i.e. commit to git) -- [ ] Start by uncommenting blocks in the initial source file -- [ ] Check if the problem is still there after each edit -- [ ] Repeat until the problem goes away -- [ ] Inline imported files as you go along -- [ ] Finished once you cannot remove more -- [ ] The emphasis is on the word "repeat" ... - -## What makes a code test case - -Important is that someone else can get the test case up and running to reproduce it locally. For this -we urge you to verify that your sample yields the expected result by testing it via [SassMeister][6] -or directly via ruby sass or node-sass (or any other libsass implementor) before submitting your bug -report. Once you verified all of the above, you may use the template below to file your bug report. - - -[1]: https://github.com/sass/libsass/issues?utf8=%E2%9C%93&q=is%3Aissue -[2]: https://github.com/sass/node-sass/issues?utf8=%E2%9C%93&q=is%3Aissue -[3]: https://github.com/sass/sassc -[4]: http://libsass.ocbnet.ch/installer/ -[5]: http://libsass.ocbnet.ch/srcmap/ -[6]: http://www.sassmeister.com/ -[7]: https://rubygems.org/gems/sass - -[8]: http://sass-lang.com/ -[9]: https://github.com/sass/libsass/tree/master/docs -[10]: https://github.com/sass/sass-spec diff --git a/src/libsass/.github/ISSUE_TEMPLATE.md b/src/libsass/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 43ffaaae1..000000000 --- a/src/libsass/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,54 +0,0 @@ -[todo]: # (Title: Be as meaningful as possible) -[todo]: # (Title: Try to use 60 or less chars) - -[todo]: # (This is only a template!) -[todo]: # (remove unneeded bits) -[todo]: # (use github preview!) - -## input.scss - -[todo]: # (always test and report with scss syntax) -[todo]: # (use sass only when results differ from scss) - -```scss -test { - content: bar -} -``` - -## Actual results - -[todo]: # (update version info!) - -[libsass 3.X.y][1] -```css -test { - content: bar; } -``` - -## Expected result - -[todo]: # (update version info!) - -ruby sass 3.X.y -```css -test { - content: bar; } -``` - -[todo]: # (update version info!) -[todo]: # (example for node-sass!) - -version info: -```cmd -$ node-sass --version -node-sass 3.X.y (Wrapper) [JavaScript] -libsass 3.X.y (Sass Compiler) [C/C++] -``` - -[todo]: # (Go to http://libsass.ocbnet.ch/srcmap) -[todo]: # (Enter your SCSS code and hit compile) -[todo]: # (Click `bookmark` and replace the url) -[todo]: # (link is used in actual results above) - -[1]: http://libsass.ocbnet.ch/srcmap/#dGVzdCB7CiAgY29udGVudDogYmFyOyB9Cg== diff --git a/src/libsass/.gitignore b/src/libsass/.gitignore deleted file mode 100644 index f2ee6beac..000000000 --- a/src/libsass/.gitignore +++ /dev/null @@ -1,85 +0,0 @@ -# Miscellaneous stuff - -/sassc -/sass-spec - -VERSION -.DS_Store -.sass-cache -*.gem -*.gcno -.svn/* -.cproject -.project -.settings/ -*.db -*.aps - -# Configuration stuff - -GNUmakefile.in -GNUmakefile -/aclocal.m4 -/autom4te.cache/ -/src/config.h -/config.h.in -/config.log -/config.status -/configure -/libtool -/m4/libtool.m4 -/m4/ltoptions.m4 -/m4/ltsugar.m4 -/m4/ltversion.m4 -/m4/lt~obsolete.m4 -/script/ar-lib -/script/compile -/script/config.guess -/script/config.sub -/script/depcomp -/script/install-sh -/script/ltmain.sh -/script/missing -/script/test-driver -/src/stamp-h1 -/src/Makefile.in -/src/Makefile -libsass/* - -# Build stuff - -*.o -*.lo -*.so -*.dll -*.a -*.suo -*.sdf -*.opendb -*.opensdf -a.out -libsass.js -tester -tester.exe -build/ -config.h.in* -lib/pkgconfig/ - -bin/* -.deps/ -.libs/ -win/bin -*.user -win/*.db - -# Final results - -sassc++ -libsass.la -src/support/libsass.pc - -# Cloned testing dirs -sassc/ -sass-spec/ - -installer/ diff --git a/src/libsass/.travis.yml b/src/libsass/.travis.yml deleted file mode 100644 index 09ca55066..000000000 --- a/src/libsass/.travis.yml +++ /dev/null @@ -1,64 +0,0 @@ -language: cpp -sudo: false - - -# don't create redundant code coverage reports -# - AUTOTOOLS=yes COVERAGE=yes BUILD=static -# - AUTOTOOLS=no COVERAGE=yes BUILD=shared -# - AUTOTOOLS=no COVERAGE=no BUILD=static - -# further speed up day by day travis-ci builds -# re-enable this if you change the makefiles -# this will still catch all coding errors! -# - AUTOTOOLS=yes COVERAGE=no BUILD=static - -# currenty there are various issues when -# built with coverage, clang and autotools -# - AUTOTOOLS=yes COVERAGE=yes BUILD=shared - -matrix: - include : - - os: linux - compiler: gcc - env: AUTOTOOLS=no COVERAGE=yes BUILD=static - - os: linux - compiler: g++-5 - env: AUTOTOOLS=yes COVERAGE=no BUILD=shared - addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-5 - - os: linux - compiler: clang++-3.7 - env: AUTOTOOLS=no COVERAGE=yes BUILD=static - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.7 - packages: - - clang-3.7 - - os: linux - compiler: clang - env: AUTOTOOLS=yes COVERAGE=no BUILD=shared - - os: osx - compiler: clang - env: AUTOTOOLS=no COVERAGE=no BUILD=shared - - os: osx - compiler: clang - env: AUTOTOOLS=no COVERAGE=yes BUILD=static - - os: osx - compiler: clang - env: AUTOTOOLS=yes COVERAGE=no BUILD=shared - -script: - - ./script/ci-build-libsass - - ./script/ci-build-plugin math - - ./script/ci-build-plugin glob - - ./script/ci-build-plugin digest - - ./script/ci-build-plugin tests -before_install: ./script/ci-install-deps -install: ./script/ci-install-compiler -after_success: ./script/ci-report-coverage diff --git a/src/libsass/COPYING b/src/libsass/COPYING deleted file mode 100644 index 8639c1117..000000000 --- a/src/libsass/COPYING +++ /dev/null @@ -1,25 +0,0 @@ - -Copyright (C) 2012 by Hampton Catlin - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -The following files in the spec were taken from the original Ruby Sass project which -is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under -the same license. diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am deleted file mode 100644 index d197261e7..000000000 --- a/src/libsass/GNUmakefile.am +++ /dev/null @@ -1,74 +0,0 @@ -ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4 -I script - -AM_COPT = -Wall -O2 -AM_COVLDFLAGS = - -if ENABLE_COVERAGE - AM_COPT = -Wall -O1 -fno-omit-frame-pointer --coverage - AM_COVLDFLAGS += -lgcov -endif - -AM_CPPFLAGS = -I$(top_srcdir)/include -AM_CFLAGS = $(AM_COPT) -AM_CXXFLAGS = $(AM_COPT) -AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) - -# only needed to support old source tree -# we have moved the files to src folder -AM_CPPFLAGS += -I$(top_srcdir) - -RESOURCES = -if COMPILER_IS_MINGW32 - RESOURCES += res/libsass.rc - AM_CXXFLAGS += -std=gnu++0x -else - AM_CXXFLAGS += -std=c++0x -endif - -TEST_EXTENSIONS = .rb - -if ENABLE_TESTS - -SASS_SASSC_PATH ?= $(top_srcdir)/sassc -SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec - -noinst_PROGRAMS = tester -tester_LDADD = src/libsass.la -tester_LDFLAGS = $(AM_LDFLAGS) -nodist_tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c -SASS_SASSC_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` -tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" -tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" - -if ENABLE_COVERAGE -nodist_EXTRA_tester_SOURCES = non-existent-file-to-force-CXX-linking.cxx -endif - -TESTS = $(SASS_SPEC_PATH)/sass-spec.rb -RB_LOG_COMPILER = ./script/tap-runner -AM_RB_LOG_FLAGS = $(RUBY) - -SASS_TEST_FLAGS = -V 3.5 --impl libsass -SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH) -SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' - -SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb - -test: - $(SASS_TESTER) $(SASS_TEST_FLAGS) - -test_build: - $(SASS_TESTER) $(SASS_TEST_FLAGS) - -test_full: - $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) - -test_probe: - $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) - -.PHONY: test test_build test_full test_probe - -endif - -SUBDIRS = src diff --git a/src/libsass/INSTALL b/src/libsass/INSTALL deleted file mode 100644 index 92e0156bf..000000000 --- a/src/libsass/INSTALL +++ /dev/null @@ -1 +0,0 @@ -// Autotools requires us to have this file. Boo. diff --git a/src/libsass/LICENSE b/src/libsass/LICENSE deleted file mode 100644 index 6bc408500..000000000 --- a/src/libsass/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ - -Copyright (C) 2012-2016 by the Sass Open Source Foundation - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -The following files in the spec were taken from the original Ruby Sass project which -is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under -the same license. diff --git a/src/libsass/Makefile b/src/libsass/Makefile deleted file mode 100644 index f3a294533..000000000 --- a/src/libsass/Makefile +++ /dev/null @@ -1,351 +0,0 @@ -OS ?= $(shell uname -s) -CC ?= gcc -CXX ?= g++ -RM ?= rm -f -CP ?= cp -a -MKDIR ?= mkdir -RMDIR ?= rmdir -WINDRES ?= windres -# Solaris/Illumos flavors -# ginstall from coreutils -ifeq ($(OS),SunOS) -INSTALL ?= ginstall -endif -INSTALL ?= install -CFLAGS ?= -Wall -CXXFLAGS ?= -Wall -LDFLAGS ?= -Wall -ifeq "x$(COVERAGE)" "x" - CFLAGS += -O2 - CXXFLAGS += -O2 - LDFLAGS += -O2 -else - CFLAGS += -O1 -fno-omit-frame-pointer - CXXFLAGS += -O1 -fno-omit-frame-pointer - LDFLAGS += -O1 -fno-omit-frame-pointer -endif -LDFLAGS += -Wl,-undefined,error -CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) - -ifneq (,$(findstring /cygdrive/,$(PATH))) - UNAME := Cygwin -else - ifneq (,$(findstring Windows_NT,$(OS))) - UNAME := Windows - else - ifneq (,$(findstring mingw32,$(MAKE))) - UNAME := Windows - else - ifneq (,$(findstring MINGW32,$(shell uname -s))) - UNAME = Windows - else - UNAME := $(shell uname -s) - endif - endif - endif -endif - -ifeq ($(SASS_LIBSASS_PATH),) - SASS_LIBSASS_PATH = $(abspath $(CURDIR)) -endif - -ifeq ($(LIBSASS_VERSION),) - ifneq ($(wildcard ./.git/ ),) - LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) - endif -endif - -ifeq ($(LIBSASS_VERSION),) - ifneq ($(wildcard VERSION),) - LIBSASS_VERSION ?= $(shell $(CAT) VERSION) - endif -endif - -ifneq ($(LIBSASS_VERSION),) - CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" - CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" -endif - -# enable mandatory flag -ifeq (Windows,$(UNAME)) - ifneq ($(BUILD),shared) - STATIC_ALL ?= 1 - endif - STATIC_LIBGCC ?= 1 - STATIC_LIBSTDCPP ?= 1 - CXXFLAGS += -std=gnu++0x - LDFLAGS += -std=gnu++0x -else - STATIC_ALL ?= 0 - STATIC_LIBGCC ?= 0 - STATIC_LIBSTDCPP ?= 0 - CXXFLAGS += -std=c++0x - LDFLAGS += -std=c++0x -endif - -ifneq ($(SASS_LIBSASS_PATH),) - CFLAGS += -I $(SASS_LIBSASS_PATH)/include - CXXFLAGS += -I $(SASS_LIBSASS_PATH)/include -else - # this is needed for mingw - CFLAGS += -I include - CXXFLAGS += -I include -endif - -ifneq ($(EXTRA_CFLAGS),) - CFLAGS += $(EXTRA_CFLAGS) -endif -ifneq ($(EXTRA_CXXFLAGS),) - CXXFLAGS += $(EXTRA_CXXFLAGS) -endif -ifneq ($(EXTRA_LDFLAGS),) - LDFLAGS += $(EXTRA_LDFLAGS) -endif - -LDLIBS = -lm - -ifneq ($(BUILD),shared) - LDLIBS += -lstdc++ -endif - -# link statically into lib -# makes it a lot more portable -# increases size by about 50KB -ifeq ($(STATIC_ALL),1) - LDFLAGS += -static -endif -ifeq ($(STATIC_LIBGCC),1) - LDFLAGS += -static-libgcc -endif -ifeq ($(STATIC_LIBSTDCPP),1) - LDFLAGS += -static-libstdc++ -endif - -ifeq ($(UNAME),Darwin) - CFLAGS += -stdlib=libc++ - CXXFLAGS += -stdlib=libc++ - LDFLAGS += -stdlib=libc++ -endif - -ifneq (Windows,$(UNAME)) - ifneq (FreeBSD,$(UNAME)) - ifneq (OpenBSD,$(UNAME)) - LDFLAGS += -ldl - LDLIBS += -ldl - endif - endif -endif - -ifneq ($(BUILD),shared) - BUILD := static -endif -ifeq ($(DEBUG),1) - BUILD := debug-$(BUILD) -endif - -ifeq (,$(TRAVIS_BUILD_DIR)) - ifeq ($(OS),SunOS) - PREFIX ?= /opt/local - else - PREFIX ?= /usr/local - endif -else - PREFIX ?= $(TRAVIS_BUILD_DIR) -endif - - -SASS_SASSC_PATH ?= sassc -SASS_SPEC_PATH ?= sass-spec -SASS_SPEC_SPEC_DIR ?= spec -SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc -RUBY_BIN = ruby - -LIB_STATIC = $(SASS_LIBSASS_PATH)/lib/libsass.a -LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.so - -ifeq (Windows,$(UNAME)) - ifeq (shared,$(BUILD)) - CFLAGS += -D ADD_EXPORTS - CXXFLAGS += -D ADD_EXPORTS - LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.dll - endif -else - ifneq (Cygwin,$(UNAME)) - CFLAGS += -fPIC - CXXFLAGS += -fPIC - LDFLAGS += -fPIC - endif -endif - -ifeq (Windows,$(UNAME)) - SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc.exe -endif - -include Makefile.conf - -RESOURCES = -STATICLIB = lib/libsass.a -SHAREDLIB = lib/libsass.so -ifeq (Windows,$(UNAME)) - RESOURCES += res/resource.rc - SHAREDLIB = lib/libsass.dll - ifeq (shared,$(BUILD)) - CFLAGS += -D ADD_EXPORTS - CXXFLAGS += -D ADD_EXPORTS - endif -else - ifneq (Cygwin,$(UNAME)) - CFLAGS += -fPIC - CXXFLAGS += -fPIC - LDFLAGS += -fPIC - endif -endif - -OBJECTS = $(addprefix src/,$(SOURCES:.cpp=.o)) -COBJECTS = $(addprefix src/,$(CSOURCES:.c=.o)) -RCOBJECTS = $(RESOURCES:.rc=.o) - -DEBUG_LVL ?= NONE - -CLEANUPS ?= -CLEANUPS += $(RCOBJECTS) -CLEANUPS += $(COBJECTS) -CLEANUPS += $(OBJECTS) -CLEANUPS += $(LIBSASS_LIB) - -all: $(BUILD) - -debug: $(BUILD) - -debug-static: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS)) -debug-static: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS)) -debug-static: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS)) -debug-static: static - -debug-shared: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS)) -debug-shared: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS)) -debug-shared: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS)) -debug-shared: shared - -lib: - $(MKDIR) lib - -lib/libsass.a: lib $(COBJECTS) $(OBJECTS) - $(AR) rcvs $@ $(COBJECTS) $(OBJECTS) - -lib/libsass.so: lib $(COBJECTS) $(OBJECTS) - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(LDLIBS) - -lib/libsass.dll: lib $(COBJECTS) $(OBJECTS) $(RCOBJECTS) - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -Wl,--subsystem,windows,--out-implib,lib/libsass.a - -%.o: %.c - $(CC) $(CFLAGS) -c -o $@ $< - -%.o: %.rc - $(WINDRES) -i $< -o $@ - -%.o: %.cpp - $(CXX) $(CXXFLAGS) -c -o $@ $< - -%: %.o static - $(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) $(LDLIBS) - -install: install-$(BUILD) - -static: $(STATICLIB) -shared: $(SHAREDLIB) - -$(DESTDIR)$(PREFIX): - $(MKDIR) $(DESTDIR)$(PREFIX) - -$(DESTDIR)$(PREFIX)/lib: $(DESTDIR)$(PREFIX) - $(MKDIR) $(DESTDIR)$(PREFIX)/lib - -$(DESTDIR)$(PREFIX)/include: $(DESTDIR)$(PREFIX) - $(MKDIR) $(DESTDIR)$(PREFIX)/include - -$(DESTDIR)$(PREFIX)/include/sass: $(DESTDIR)$(PREFIX)/include - $(MKDIR) $(DESTDIR)$(PREFIX)/include/sass - -$(DESTDIR)$(PREFIX)/include/%.h: include/%.h \ - $(DESTDIR)$(PREFIX)/include \ - $(DESTDIR)$(PREFIX)/include/sass - $(INSTALL) -v -m0644 "$<" "$@" - -install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ - $(DESTDIR)$(PREFIX)/include/sass2scss.h \ - $(DESTDIR)$(PREFIX)/include/sass/base.h \ - $(DESTDIR)$(PREFIX)/include/sass/version.h \ - $(DESTDIR)$(PREFIX)/include/sass/values.h \ - $(DESTDIR)$(PREFIX)/include/sass/context.h \ - $(DESTDIR)$(PREFIX)/include/sass/functions.h - -$(DESTDIR)$(PREFIX)/lib/%.a: lib/%.a \ - $(DESTDIR)$(PREFIX)/lib - @$(INSTALL) -v -m0755 "$<" "$@" - -$(DESTDIR)$(PREFIX)/lib/%.so: lib/%.so \ - $(DESTDIR)$(PREFIX)/lib - @$(INSTALL) -v -m0755 "$<" "$@" - -$(DESTDIR)$(PREFIX)/lib/%.dll: lib/%.dll \ - $(DESTDIR)$(PREFIX)/lib - @$(INSTALL) -v -m0755 "$<" "$@" - -install-static: $(DESTDIR)$(PREFIX)/lib/libsass.a - -install-shared: $(DESTDIR)$(PREFIX)/lib/libsass.so \ - install-headers - -$(SASSC_BIN): $(BUILD) - $(MAKE) -C $(SASS_SASSC_PATH) build-$(BUILD)-dev - -sassc: $(SASSC_BIN) - $(SASSC_BIN) -v - -version: $(SASSC_BIN) - $(SASSC_BIN) -h - $(SASSC_BIN) -v - -test: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) - -test_build: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) - -test_full: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) - -test_probe: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) - -clean-objects: lib - -$(RM) lib/*.a lib/*.so lib/*.dll lib/*.la - -$(RMDIR) lib -clean: clean-objects - $(RM) $(CLEANUPS) - -clean-all: - $(MAKE) -C $(SASS_SASSC_PATH) clean - -lib-file: lib-file-$(BUILD) -lib-opts: lib-opts-$(BUILD) - -lib-file-static: - @echo $(LIB_STATIC) -lib-file-shared: - @echo $(LIB_SHARED) -lib-opts-static: - @echo -L"$(SASS_LIBSASS_PATH)/lib" -lib-opts-shared: - @echo -L"$(SASS_LIBSASS_PATH)/lib -lsass" - -.PHONY: all static shared sassc \ - version install-headers \ - clean clean-all clean-objects \ - debug debug-static debug-shared \ - install install-static install-shared \ - lib-opts lib-opts-shared lib-opts-static \ - lib-file lib-file-shared lib-file-static -.DELETE_ON_ERROR: diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf deleted file mode 100644 index 5ba968b68..000000000 --- a/src/libsass/Makefile.conf +++ /dev/null @@ -1,55 +0,0 @@ -# this is merely a common Makefile multiple implementers can use -# bigger files (in terms of compile time) tend to go to the top, -# so they don't end up as the last compile unit when compiling -# in parallel. But we also want to mix them a little too avoid -# heavy RAM usage peaks. Other than that the order is arbitrary. - - -SOURCES = \ - ast.cpp \ - node.cpp \ - context.cpp \ - constants.cpp \ - functions.cpp \ - color_maps.cpp \ - environment.cpp \ - ast_fwd_decl.cpp \ - bind.cpp \ - file.cpp \ - util.cpp \ - json.cpp \ - units.cpp \ - values.cpp \ - plugins.cpp \ - position.cpp \ - lexer.cpp \ - parser.cpp \ - prelexer.cpp \ - eval.cpp \ - expand.cpp \ - listize.cpp \ - cssize.cpp \ - extend.cpp \ - output.cpp \ - inspect.cpp \ - emitter.cpp \ - check_nesting.cpp \ - remove_placeholders.cpp \ - sass.cpp \ - sass_util.cpp \ - sass_values.cpp \ - sass_context.cpp \ - sass_functions.cpp \ - sass2scss.cpp \ - backtrace.cpp \ - operators.cpp \ - to_c.cpp \ - to_value.cpp \ - source_map.cpp \ - subset_map.cpp \ - error_handling.cpp \ - memory/SharedPtr.cpp \ - utf8_string.cpp \ - base64vlq.cpp - -CSOURCES = cencode.c diff --git a/src/libsass/Readme.md b/src/libsass/Readme.md deleted file mode 100644 index 908de2dc4..000000000 --- a/src/libsass/Readme.md +++ /dev/null @@ -1,104 +0,0 @@ -LibSass - Sass compiler written in C++ -====================================== - -Currently maintained by Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) -Originally created by Aaron Leung ([@akhleung]) and Hampton Catlin ([@hcatlin]) - -[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass "Travis CI") -[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master "Appveyor CI") -[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3 "Code coverage of spec tests") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Percentage of issues still open") -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Average time to resolve an issue") -[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE "Bountysource") -[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/ "Slack communication channels") - - -[LibSass](https://github.com/sass/libsass "LibSass GitHub Project") is just a library! -If you want to use LibSass to compile Sass, you need an implementer. Some -implementations are only bindings into other programming languages. But most also -ship with a command line interface (CLI) you can use directly. There is also -[SassC](https://github.com/sass/sassc), which is the official lightweight -CLI tool built by the same people as LibSass. - -### Excerpt of "sanctioned" implementations: - -- https://github.com/sass/node-sass (Node.js) -- https://github.com/sass/perl-libsass (Perl) -- https://github.com/sass/libsass-python (Python) -- https://github.com/wellington/go-libsass (Go) -- https://github.com/sass/sassc-ruby (Ruby) -- https://github.com/sass/libsass-net (C#) -- https://github.com/medialize/sass.js (JS) -- https://github.com/bit3/jsass (Java) - -This list does not say anything about the quality of either the listed or not listed [implementations](docs/implementations.md)! -The authors of the listed projects above are just known to work regularly together with LibSass developers. - -About ------ - -LibSass is a C++ port of the original Ruby Sass CSS compiler with a [C API](docs/api-doc.md). -We coded LibSass with portability and efficiency in mind. You can expect LibSass to be a lot -faster than Ruby Sass and on par or faster than the best alternative CSS compilers around. - -Developing ----------- - -As noted above, the LibSass repository does not contain any binaries or other way to execute -LibSass. Therefore, you need an implementer to develop LibSass. Easiest is to start with -the official [SassC](http://github.com/sass/sassc) CLI wrapper. It is *guaranteed* to compile -with the latest code in LibSass master, since it is also used in the CI process. There is no -limitation here, as you may use any other LibSass implementer to test your LibSass branch! - -Testing -------- - -Since LibSass is a pure library, tests are run through the [Sass-Spec](https://github.com/sass/sass-spec) -project using the [SassC](http://github.com/sass/sassc) CLI wrapper. To run the tests against LibSass while -developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and -then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. -Note that the scripts in the `./script` folder are mainly intended for our CI needs. - -Building --------- - -To build LibSass you need GCC 4.6+ or Clang/LLVM. If your OS is older, you may need to upgrade -them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.6+ or VS 2013 -Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows with various build chains -and/or command line interpreters. - -See the [build docs for further instructions](docs/build.md)! - -Compatibility -------------- - -Current LibSass 3.4 should be compatible with Sass 3.4. Please refer to the [sass compatibility -page](http://sass-compatibility.github.io/) for a more detailed comparison. But note that there -are still a few incomplete edges which we are aware of. Otherwise LibSass has reached a good level -of stability, thanks to our ever growing [Sass-Spec test suite](https://github.com/sass/sass-spec). - -About Sass ----------- - -Sass is a CSS pre-processor language to add on exciting, new, awesome features to CSS. Sass was -the first language of its kind and by far the most mature and up to date codebase. - -Sass was originally conceived of by the co-creator of this library, Hampton Catlin ([@hcatlin]). -Most of the language has been the result of years of work by Natalie Weizenbaum ([@nex3]) and -Chris Eppstein ([@chriseppstein]). - -For more information about Sass itself, please visit http://sass-lang.com - -Initial development of LibSass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). - -Licensing ---------- - -Our [MIT license](LICENSE) is designed to be as simple and liberal as possible. - -[@hcatlin]: https://github.com/hcatlin -[@akhleung]: https://github.com/akhleung -[@chriseppstein]: https://github.com/chriseppstein -[@nex3]: https://github.com/nex3 -[@mgreter]: https://github.com/mgreter -[@xzyfer]: https://github.com/xzyfer diff --git a/src/libsass/SECURITY.md b/src/libsass/SECURITY.md deleted file mode 100644 index 90c837c60..000000000 --- a/src/libsass/SECURITY.md +++ /dev/null @@ -1,10 +0,0 @@ -Serious about security -====================== - -The LibSass team recognizes the important contributions the security research -community can make. We therefore encourage reporting security issues with the -code contained in this repository. - -If you believe you have discovered a security vulnerability, please report it at -https://hackerone.com/libsass instead of GitHub. - diff --git a/src/libsass/appveyor.yml b/src/libsass/appveyor.yml deleted file mode 100644 index d964fade4..000000000 --- a/src/libsass/appveyor.yml +++ /dev/null @@ -1,91 +0,0 @@ -os: Visual Studio 2013 - -environment: - CTEST_OUTPUT_ON_FAILURE: 1 - ruby_version: 22-x64 - TargetPath: sassc/bin/sassc.exe - matrix: - - Compiler: msvc - Config: Release - Platform: Win32 - - Compiler: msvc - Config: Debug - Platform: Win32 - - Compiler: msvc - Config: Release - Platform: Win64 - - Compiler: mingw - Build: static - - Compiler: mingw - Build: shared - -cache: - - C:\Ruby%ruby_version%\lib\ruby\gems - - C:\mingw64 - -install: - - git clone https://github.com/sass/sassc.git - - git clone https://github.com/sass/sass-spec.git - - set PATH=C:\Ruby%ruby_version%\bin;%PATH% - - ps: | - if(!(gem which minitest 2>$nul)) { gem install minitest --no-ri --no-rdoc } - if ($env:Compiler -eq "mingw" -AND -Not (Test-Path "C:\mingw64")) { - # Install MinGW. - $file = "x86_64-4.9.2-release-win32-seh-rt_v4-rev3.7z" - wget https://bintray.com/artifact/download/drewwells/generic/$file -OutFile $file - &7z x -oC:\ $file > $null - } - - set PATH=C:\mingw64\bin;%PATH% - - set CC=gcc - -build_script: - - ps: | - if ($env:Compiler -eq "mingw") { - mingw32-make -j4 sassc - } else { - msbuild /m:4 /p:"Configuration=$env:Config;Platform=$env:Platform" sassc\win\sassc.sln - } - - # print the branding art - mv script/branding script/branding.ps1 - script/branding.ps1 - - # print the version info - &$env:TargetPath -v - ruby -v - -test_script: - - ps: | - $PRNR = $env:APPVEYOR_PULL_REQUEST_NUMBER - if ($PRNR) { - echo "Fetching info for PR $PRNR" - wget https://api.github.com/repos/sass/libsass/pulls/$PRNR -OutFile pr.json - $json = cat pr.json -Raw - $SPEC_PR = [regex]::match($json,'sass\/sass-spec(#|\/pull\/)([0-9]+)').Groups[2].Value - if ($SPEC_PR) { - echo "Checkout sass spec PR $SPEC_PR" - git -C sass-spec fetch -q -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR - git -C sass-spec checkout -q --force ci-spec-pr-$SPEC_PR - } - } - $env:TargetPath = Join-Path $pwd.Path $env:TargetPath - If (Test-Path "$env:TargetPath") { - ruby sass-spec/sass-spec.rb -V 3.5 --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec - if(-not($?)) { - echo "sass-spec tests failed" - exit 1 - } - } else { - echo "spec runner not found (compile error?)" - exit 1 - } - Write-Host "Explicitly testing the case when cwd has Cyrillic characters: " -nonewline - # See comments in gh-1774 for details. - cd sass-spec/spec/libsass/Sáss-UŢF8/ - &$env:TargetPath ./input.scss 2>&1>$null - if(-not($?)) { - echo "Failed!" - exit 1 - } else { - echo "Success!" - } diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac deleted file mode 100644 index b5a943217..000000000 --- a/src/libsass/configure.ac +++ /dev/null @@ -1,134 +0,0 @@ -# -*- Autoconf -*- -# Process this file with autoconf to produce a configure script. - -AC_PREREQ([2.61]) - -AC_INIT([libsass], m4_esyscmd_s([./version.sh]), [support@moovweb.com]) -AC_CONFIG_SRCDIR([src/ast.hpp]) -AC_CONFIG_MACRO_DIR([m4]) -AC_CONFIG_HEADERS([src/config.h]) -AC_CONFIG_FILES([include/sass/version.h]) -AC_CONFIG_AUX_DIR([script]) - -# These are flags passed to automake -# Though they look like gcc flags! -AM_INIT_AUTOMAKE([foreign parallel-tests -Wall]) -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([no])]) - -# Checks for programs. -AC_PROG_CC -AC_PROG_CXX -AC_LANG_PUSH([C]) -AC_LANG_PUSH([C++]) -AC_GNU_SOURCE -# Check fails on Travis, but it works fine -# AX_CXX_COMPILE_STDCXX_11([ext],[optional]) -AC_CHECK_TOOL([AR], [ar], [false]) -AC_CHECK_TOOL([DLLTOOL], [dlltool], [false]) -AC_CHECK_TOOL([DLLWRAP], [dllwrap], [false]) -AC_CHECK_TOOL([WINDRES], [windres], [false]) -m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) -LT_INIT([dlopen]) - -# Checks for header files. -AC_CHECK_HEADERS([unistd.h]) - -# Checks for typedefs, structures, and compiler characteristics. -AC_TYPE_SIZE_T - -# Checks for library functions. -AC_FUNC_MALLOC -AC_CHECK_FUNCS([floor getcwd strtol]) - -# Checks for testing. -AC_ARG_ENABLE(tests, AS_HELP_STRING([--enable-tests], [enable testing the build]), - [enable_tests="$enableval"], [enable_tests=no]) - -AS_CASE([$host], [*-*-mingw*], [is_mingw32=yes], [is_mingw32=no]) -AM_CONDITIONAL(COMPILER_IS_MINGW32, test "x$is_mingw32" = "xyes") - -dnl The dlopen() function is in the C library for *BSD and in -dnl libdl on GLIBC-based systems -if test "x$is_mingw32" != "xyes"; then - AC_SEARCH_LIBS([dlopen], [dl dld], [], [ - AC_MSG_ERROR([unable to find the dlopen() function]) - ]) -fi - -if test "x$enable_tests" = "xyes"; then - AC_PROG_CC - AC_PROG_AWK - # test need minitest gem - AC_PATH_PROG(RUBY, [ruby]) - AC_PATH_PROG(TAPOUT, [tapout]) - AC_REQUIRE_AUX_FILE([tap-driver]) - AC_REQUIRE_AUX_FILE([tap-runner]) - AC_ARG_WITH(sassc-dir, - AS_HELP_STRING([--with-sassc-dir=], [specify directory of sassc sources for testing (default: sassc)]), - [sassc_dir="$withval"], [sassc_dir="sassc"]) - AC_CHECK_FILE([$sassc_dir/sassc.c], [], [ - AC_MSG_ERROR([Unable to find sassc directory. -You must clone the sassc repository in this directory or specify -the --with-sassc-dir= argument. -]) - ]) - SASS_SASSC_PATH=$sassc_dir - AC_SUBST(SASS_SASSC_PATH) - - AC_ARG_WITH(sass-spec-dir, - AS_HELP_STRING([--with-sass-spec-dir=], [specify directory of sass-spec for testing (default: sass-spec)]), - [sass_spec_dir="$withval"], [sass_spec_dir="sass-spec"]) - AC_CHECK_FILE([$sass_spec_dir/sass-spec.rb], [], [ - AC_MSG_ERROR([Unable to find sass-spec directory. -You must clone the sass-spec repository in this directory or specify -the --with-sass-spec-dir= argument. -]) - ]) - # Automake doesn't like its tests in an absolute path, so we make it relative. - case $sass_spec_dir in - /*) - SASS_SPEC_PATH=`$RUBY -e "require 'pathname'; puts Pathname.new('$sass_spec_dir').relative_path_from(Pathname.new('$PWD')).to_s"` - ;; - *) - SASS_SPEC_PATH="$sass_spec_dir" - ;; - esac - AC_SUBST(SASS_SPEC_PATH) -else - # we do not really need these paths for non test build - # but automake may error if we do not define them here - SASS_SPEC_PATH=sass-spec - SASS_SASSC_PATH=sassc - AC_SUBST(SASS_SPEC_PATH) - AC_SUBST(SASS_SASSC_PATH) -fi - -AM_CONDITIONAL(ENABLE_TESTS, test "x$enable_tests" = "xyes") - -AC_ARG_ENABLE([coverage], - [AS_HELP_STRING([--enable-coverage], - [enable coverage report for test suite])], - [enable_cov=$enableval], - [enable_cov=no]) - -if test "x$enable_cov" = "xyes"; then - - AC_CHECK_PROG(GCOV, gcov, gcov) - - # Remove all optimization flags from C[XX]FLAGS - changequote({,}) - CFLAGS=`echo "$CFLAGS -O1 -fno-omit-frame-pointer" | $SED -e 's/-O[0-9]*//g'` - CXXFLAGS=`echo "$CXXFLAGS -O1 -fno-omit-frame-pointer" | $SED -e 's/-O[0-9]*//g'` - changequote([,]) - - AC_SUBST(GCOV) -fi - -AM_CONDITIONAL(ENABLE_COVERAGE, test "x$enable_cov" = "xyes") - -AC_SUBST(PACKAGE_VERSION) - -AC_MSG_NOTICE([Building libsass ($VERSION)]) - -AC_CONFIG_FILES([GNUmakefile src/GNUmakefile src/support/libsass.pc]) -AC_OUTPUT diff --git a/src/libsass/contrib/libsass.spec b/src/libsass/contrib/libsass.spec deleted file mode 100644 index a83d5f0cb..000000000 --- a/src/libsass/contrib/libsass.spec +++ /dev/null @@ -1,66 +0,0 @@ -Name: libsass -Version: %{version} -Release: 1%{?dist} -Summary: A C/C++ implementation of a Sass compiler - -License: MIT -URL: http://libsass.org -Source0: %{name}-%{version}.tar.gz - -BuildRequires: gcc-c++ >= 4.7 -BuildRequires: autoconf -BuildRequires: automake -BuildRequires: libtool - - -%description -LibSass is a C/C++ port of the Sass engine. The point is to be simple, fast, and easy to integrate. - -%package devel -Summary: Development files for %{name} -Requires: %{name}%{?_isa} = %{version}-%{release} - - -%description devel -The %{name}-devel package contains libraries and header files for -developing applications that use %{name}. - - -%prep -%setup -q -autoreconf --force --install - - -%build -%configure --disable-static \ - --disable-tests \ - --enable-shared - -make %{?_smp_mflags} - - -%install -%make_install -find $RPM_BUILD_ROOT -name '*.la' -exec rm -f {} ';' - - -%post -p /sbin/ldconfig - -%postun -p /sbin/ldconfig - - -%files -%doc Readme.md LICENSE -%{_libdir}/*.so.* - -%files devel -%doc -%{_includedir}/* -%{_libdir}/*.so -%{_libdir}/pkgconfig/*.pc - - -%changelog -* Tue Feb 10 2015 Gawain Lynch - 3.1.0-1 -- Initial SPEC file - diff --git a/src/libsass/contrib/plugin.cpp b/src/libsass/contrib/plugin.cpp deleted file mode 100644 index 2f67bb371..000000000 --- a/src/libsass/contrib/plugin.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include - -// gcc: g++ -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass -// mingw: g++ -shared plugin.cpp -o plugin.dll -Llib -lsass - -extern "C" const char* ADDCALL libsass_get_version() { - return libsass_version(); -} - -union Sass_Value* custom_function(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) -{ - // get context/option struct associated with this compiler - struct Sass_Context* ctx = sass_compiler_get_context(comp); - struct Sass_Options* opts = sass_compiler_get_options(comp); - // get the cookie from function descriptor - void* cookie = sass_function_get_cookie(cb); - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); -} - -extern "C" Sass_Function_List ADDCALL libsass_load_functions() -{ - // allocate a custom function caller - Sass_Function_Entry c_func = - sass_make_function("foo()", custom_function, (void*)42); - // create list of all custom functions - Sass_Function_List fn_list = sass_make_function_list(1); - // put the only function in this plugin to the list - sass_function_set_list_entry(fn_list, 0, c_func); - // return the list - return fn_list; -} - -Sass_Import_List custom_importer(const char* cur_path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) -{ - // get the cookie from importer descriptor - void* cookie = sass_importer_get_cookie(cb); - // create a list to hold our import entries - Sass_Import_List incs = sass_make_import_list(1); - // create our only import entry (route path back) - incs[0] = sass_make_import_entry(cur_path, 0, 0); - // return imports - return incs; -} - -extern "C" Sass_Importer_List ADDCALL libsass_load_importers() -{ - // allocate a custom function caller - Sass_Importer_Entry c_imp = - sass_make_importer(custom_importer, - 99, (void*)42); - // create list of all custom functions - Sass_Importer_List imp_list = sass_make_importer_list(1); - // put the only function in this plugin to the list - sass_importer_set_list_entry(imp_list, 0, c_imp); - // return the list - return imp_list; -} diff --git a/src/libsass/docs/README.md b/src/libsass/docs/README.md deleted file mode 100644 index a233fae48..000000000 --- a/src/libsass/docs/README.md +++ /dev/null @@ -1,20 +0,0 @@ -Welcome to the LibSass documentation! - -## First Off -LibSass is just a library. To run the code locally (i.e. to compile your stylesheets), you need an implementer. SassC (get it?) is an implementer written in C. There are a number of other implementations of LibSass - for example Node. We encourage you to write your own port - the whole point of LibSass is that we want to bring Sass to many other languages, not just Ruby! - -We're working hard on moving to full parity with Ruby Sass... learn more at the [The-LibSass-Compatibility-Plan](compatibility-plan.md)! - -### Implementing LibSass - -If you're interested in implementing LibSass in your own project see the [API Documentation](api-doc.md) which now includes implementing -your own [Sass functions](api-function.md). You may wish to [look at other implementations](implementations.md) for your language of choice. -Or make your own! - -### Contributing to LibSass - -| Issue Tracker | Issue Triage | Community Guidelines | -|-------------------|----------------------------------|-----------------------------| -| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [[Sass Community Guidelines|http://sass-lang.com/community-guidelines]]. Be nice and everyone else will be nice too! | - -Please refer to the steps on [Building LibSass](build.md) diff --git a/src/libsass/docs/api-context-example.md b/src/libsass/docs/api-context-example.md deleted file mode 100644 index 4f2a2a0ce..000000000 --- a/src/libsass/docs/api-context-example.md +++ /dev/null @@ -1,45 +0,0 @@ -## Example main.c - -```C -#include -#include "sass/context.h" - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // configure some options ... - sass_option_set_precision(ctx_opt, 10); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -### Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: 21px * 2; }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` - diff --git a/src/libsass/docs/api-context-internal.md b/src/libsass/docs/api-context-internal.md deleted file mode 100644 index 1a2818b34..000000000 --- a/src/libsass/docs/api-context-internal.md +++ /dev/null @@ -1,163 +0,0 @@ -```C -// Input behaviours -enum Sass_Input_Style { - SASS_CONTEXT_NULL, - SASS_CONTEXT_FILE, - SASS_CONTEXT_DATA, - SASS_CONTEXT_FOLDER -}; - -// sass config options structure -struct Sass_Inspect_Options { - - // Output style for the generated css code - // A value from above SASS_STYLE_* constants - enum Sass_Output_Style output_style; - - // Precision for fractional numbers - int precision; - -}; - -// sass config options structure -struct Sass_Output_Options : Sass_Inspect_Options { - - // String to be used for indentation - const char* indent; - // String to be used to for line feeds - const char* linefeed; - - // Emit comments in the generated CSS indicating - // the corresponding source line. - bool source_comments; - -}; - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* include_path; - char* plugin_path; - - // Include paths (linked string list) - struct string_list* include_paths; - // Plugin paths (linked string list) - struct string_list* plugin_paths; - - // Path to source map file - // Enables source map generation - // Used to create sourceMappingUrl - char* source_map_file; - - // Directly inserted in source maps - char* source_map_root; - - // Custom functions that can be called from sccs code - Sass_Function_List c_functions; - - // Callback to overload imports - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - const char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; -``` - diff --git a/src/libsass/docs/api-context.md b/src/libsass/docs/api-context.md deleted file mode 100644 index dfd10c181..000000000 --- a/src/libsass/docs/api-context.md +++ /dev/null @@ -1,295 +0,0 @@ -Sass Contexts come in two flavors: - -- `Sass_File_Context` -- `Sass_Data_Context` - -### Basic Usage - -```C -#include "sass/context.h" -``` - -***Sass_Options*** - -```C -// Precision for fractional numbers -int precision; -``` -```C -// Output style for the generated css code -// A value from above SASS_STYLE_* constants -int output_style; -``` -```C -// Emit comments in the generated CSS indicating -// the corresponding source line. -bool source_comments; -``` -```C -// embed sourceMappingUrl as data uri -bool source_map_embed; -``` -```C -// embed include contents in maps -bool source_map_contents; -``` -```C -// create file urls for sources -bool source_map_file_urls; -``` -```C -// Disable sourceMappingUrl in css output -bool omit_source_map_url; -``` -```C -// Treat source_string as sass (as opposed to scss) -bool is_indented_syntax_src; -``` -```C -// The input path is used for source map -// generating. It can be used to define -// something with string compilation or to -// overload the input file path. It is -// set to "stdin" for data contexts and -// to the input file on file contexts. -char* input_path; -``` -```C -// The output path is used for source map -// generating. LibSass will not write to -// this file, it is just used to create -// information in source-maps etc. -char* output_path; -``` -```C -// String to be used for indentation -const char* indent; -``` -```C -// String to be used to for line feeds -const char* linefeed; -``` -```C -// Colon-separated list of paths -// Semicolon-separated on Windows -char* include_path; -char* plugin_path; -``` -```C -// Additional include paths -// Must be null delimited -char** include_paths; -char** plugin_paths; -``` -```C -// Path to source map file -// Enables the source map generating -// Used to create sourceMappingUrl -char* source_map_file; -``` -```C -// Directly inserted in source maps -char* source_map_root; -``` -```C -// Custom functions that can be called from Sass code -Sass_C_Function_List c_functions; -``` -```C -// Callback to overload imports -Sass_C_Import_Callback importer; -``` - -***Sass_Context*** - -```C -// store context type info -enum Sass_Input_Style type; -```` -```C -// generated output data -char* output_string; -``` -```C -// generated source map json -char* source_map_string; -``` -```C -// error status -int error_status; -char* error_json; -char* error_text; -char* error_message; -// error position -char* error_file; -size_t error_line; -size_t error_column; -``` -```C -// report imported files -char** included_files; -``` - -***Sass_File_Context*** - -```C -// no additional fields required -// input_path is already on options -``` - -***Sass_Data_Context*** - -```C -// provided source string -char* source_string; -``` - -### Sass Context API - -```C -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Create and initialize an option struct -struct Sass_Options* sass_make_options (void); -// Create and initialize a specific context -struct Sass_File_Context* sass_make_file_context (const char* input_path); -struct Sass_Data_Context* sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -int sass_compile_file_context (struct Sass_File_Context* ctx); -int sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ctx); -struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Usefull if you only want to query the included files -int sass_compiler_parse (struct Sass_Compiler* compiler); -int sass_compiler_execute (struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -void sass_delete_compiler (struct Sass_Compiler* compiler); -void sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -void sass_delete_file_context (struct Sass_File_Context* ctx); -void sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for Context from specific implementation -struct Sass_Context* sass_file_context_get_context (struct Sass_File_Context* file_ctx); -struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - -// Getters for Context_Options from Sass_Context -struct Sass_Options* sass_context_get_options (struct Sass_Context* ctx); -struct Sass_Options* sass_file_context_get_options (struct Sass_File_Context* file_ctx); -struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* data_ctx); -void sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -void sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - -// Getters for Sass_Context values -const char* sass_context_get_output_string (struct Sass_Context* ctx); -int sass_context_get_error_status (struct Sass_Context* ctx); -const char* sass_context_get_error_json (struct Sass_Context* ctx); -const char* sass_context_get_error_text (struct Sass_Context* ctx); -const char* sass_context_get_error_message (struct Sass_Context* ctx); -const char* sass_context_get_error_file (struct Sass_Context* ctx); -size_t sass_context_get_error_line (struct Sass_Context* ctx); -size_t sass_context_get_error_column (struct Sass_Context* ctx); -const char* sass_context_get_source_map_string (struct Sass_Context* ctx); -char** sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options (query import stack) -size_t sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -Sass_Import_Entry sass_compiler_get_last_import(struct Sass_Compiler* compiler); -Sass_Import_Entry sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -// Getters for Sass_Compiler options (query function stack) -size_t sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -Sass_Callee_Entry sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -Sass_Callee_Entry sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// Take ownership of memory (value on context is set to 0) -char* sass_context_take_error_json (struct Sass_Context* ctx); -char* sass_context_take_error_text (struct Sass_Context* ctx); -char* sass_context_take_error_message (struct Sass_Context* ctx); -char* sass_context_take_error_file (struct Sass_Context* ctx); -char* sass_context_take_output_string (struct Sass_Context* ctx); -char* sass_context_take_source_map_string (struct Sass_Context* ctx); -``` - -### Sass Options API - -```C -// Getters for Context_Option values -int sass_option_get_precision (struct Sass_Options* options); -enum Sass_Output_Style sass_option_get_output_style (struct Sass_Options* options); -bool sass_option_get_source_comments (struct Sass_Options* options); -bool sass_option_get_source_map_embed (struct Sass_Options* options); -bool sass_option_get_source_map_contents (struct Sass_Options* options); -bool sass_option_get_source_map_file_urls (struct Sass_Options* options); -bool sass_option_get_omit_source_map_url (struct Sass_Options* options); -bool sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -const char* sass_option_get_indent (struct Sass_Options* options); -const char* sass_option_get_linefeed (struct Sass_Options* options); -const char* sass_option_get_input_path (struct Sass_Options* options); -const char* sass_option_get_output_path (struct Sass_Options* options); -const char* sass_option_get_source_map_file (struct Sass_Options* options); -const char* sass_option_get_source_map_root (struct Sass_Options* options); -Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); -Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); - -// Getters for Context_Option include path array -size_t sass_option_get_include_path_size(struct Sass_Options* options); -const char* sass_option_get_include_path(struct Sass_Options* options, size_t i); -// Plugin paths to load dynamic libraries work the same -size_t sass_option_get_plugin_path_size(struct Sass_Options* options); -const char* sass_option_get_plugin_path(struct Sass_Options* options, size_t i); - -// Setters for Context_Option values -void sass_option_set_precision (struct Sass_Options* options, int precision); -void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); -void sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); -void sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -void sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -void sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -void sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -void sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -void sass_option_set_indent (struct Sass_Options* options, const char* indent); -void sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); -void sass_option_set_input_path (struct Sass_Options* options, const char* input_path); -void sass_option_set_output_path (struct Sass_Options* options, const char* output_path); -void sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); -void sass_option_set_include_path (struct Sass_Options* options, const char* include_path); -void sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); -void sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); -void sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); -void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); - -// Push function for paths (no manipulation support for now) -void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -void sass_option_push_include_path (struct Sass_Options* options, const char* path); - -// Resolve a file via the given include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -char* sass_find_file (const char* path, struct Sass_Options* opt); -char* sass_find_include (const char* path, struct Sass_Options* opt); - -// Resolve a file relative to last import or include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -char* sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -char* sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); -``` - -### More links - -- [Sass Context Example](api-context-example.md) -- [Sass Context Internal](api-context-internal.md) - diff --git a/src/libsass/docs/api-doc.md b/src/libsass/docs/api-doc.md deleted file mode 100644 index 376561612..000000000 --- a/src/libsass/docs/api-doc.md +++ /dev/null @@ -1,215 +0,0 @@ -## Introduction - -LibSass wouldn't be much good without a way to interface with it. These -interface documentations describe the various functions and data structures -available to implementers. They are split up over three major components, which -have all their own source files (plus some common functionality). - -- [Sass Context](api-context.md) - Trigger and handle the main Sass compilation -- [Sass Value](api-value.md) - Exchange values and its format with LibSass -- [Sass Function](api-function.md) - Get invoked by LibSass for function statments -- [Sass Importer](api-importer.md) - Get invoked by LibSass for @import statments - -### Basic usage - -First you will need to include the header file! -This will automatically load all other headers too! - -```C -#include "sass/context.h" -``` - -## Basic C Example - -```C -#include -#include "sass/context.h" - -int main() { - puts(libsass_version()); - return 0; -} -``` - -```bash -gcc -Wall version.c -lsass -o version && ./version -``` - -## More C Examples - -- [Sample code for Sass Context](api-context-example.md) -- [Sample code for Sass Value](api-value-example.md) -- [Sample code for Sass Function](api-function-example.md) -- [Sample code for Sass Importer](api-importer-example.md) - -## Compiling your code - -The most important is your sass file (or string of sass code). With this, you -will want to start a LibSass compiler. Here is some pseudocode describing the -process. The compiler has two different modes: direct input as a string with -`Sass_Data_Context` or LibSass will do file reading for you by using -`Sass_File_Context`. See the code for a list of options available -[Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) - -**Building a file compiler** - - context = sass_make_file_context("file.scss") - options = sass_file_context_get_options(context) - sass_option_set_precision(options, 1) - sass_option_set_source_comments(options, true) - - sass_file_context_set_options(context, options) - - compiler = sass_make_file_compiler(sass_context) - sass_compiler_parse(compiler) - sass_compiler_execute(compiler) - - output = sass_context_get_output_string(context) - // Retrieve errors during compilation - error_status = sass_context_get_error_status(context) - json_error = sass_context_get_error_json(context) - // Release memory dedicated to the C compiler - sass_delete_compiler(compiler) - -**Building a data compiler** - - context = sass_make_data_context("div { a { color: blue; } }") - options = sass_data_context_get_options(context) - sass_option_set_precision(options, 1) - sass_option_set_source_comments(options, true) - - sass_data_context_set_options(context, options) - - compiler = sass_make_data_compiler(context) - sass_compiler_parse(compiler) - sass_compiler_execute(compiler) - - output = sass_context_get_output_string(context) - // div a { color: blue; } - // Retrieve errors during compilation - error_status = sass_context_get_error_status(context) - json_error = sass_context_get_error_json(context) - // Release memory dedicated to the C compiler - sass_delete_compiler(compiler) - -## Sass Context Internals - -Everything is stored in structs: - -```C -struct Sass_Options; -struct Sass_Context : Sass_Options; -struct Sass_File_context : Sass_Context; -struct Sass_Data_context : Sass_Context; -``` - -This mirrors very well how `libsass` uses these structures. - -- `Sass_Options` holds everything you feed in before the compilation. It also hosts -`input_path` and `output_path` options, because they are used to generate/calculate -relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. -- `Sass_Context` holds all the data returned by the compilation step. -- `Sass_File_Context` is a specific implementation that requires no additional fields -- `Sass_Data_Context` is a specific implementation that adds the `input_source` field - -Structs can be down-casted to access `context` or `options`! - -## Memory handling and life-cycles - -We keep memory around for as long as the main [context](api-context.md) object -is not destroyed (`sass_delete_context`). LibSass will create copies of most -inputs/options beside the main sass code. You need to allocate and fill that -buffer before passing it to LibSass. You may also overtake memory management -from libsass for certain return values (i.e. `sass_context_take_output_string`). - -```C -// to allocate buffer to be filled -void* sass_alloc_memory(size_t size); -// to allocate a buffer from existing string -char* sass_copy_c_string(const char* str); -// to free overtaken memory when done -void sass_free_memory(void* ptr); -``` - -## Miscellaneous API functions - -```C -// Some convenient string helper function -char* sass_string_unquote (const char* str); -char* sass_string_quote (const char* str, const char quote_mark); - -// Get compiled libsass version -const char* libsass_version(void); - -// Implemented sass language version -// Hardcoded version 3.4 for time being -const char* libsass_language_version(void); -``` - -## Common Pitfalls - -**input_path** - -The `input_path` is part of `Sass_Options`, but it also is the main option for -`Sass_File_Context`. It is also used to generate relative file links in source- -maps. Therefore it is pretty usefull to pass this information if you have a -`Sass_Data_Context` and know the original path. - -**output_path** - -Be aware that `libsass` does not write the output file itself. This option -merely exists to give `libsass` the proper information to generate links in -source-maps. The file has to be written to the disk by the -binding/implementation. If the `output_path` is omitted, `libsass` tries to -extrapolate one from the `input_path` by replacing (or adding) the file ending -with `.css`. - -## Error Codes - -The `error_code` is integer value which indicates the type of error that -occurred inside the LibSass process. Following is the list of error codes along -with the short description: - -* 1: normal errors like parsing or `eval` errors -* 2: bad allocation error (memory error) -* 3: "untranslated" C++ exception (`throw std::exception`) -* 4: legacy string exceptions ( `throw const char*` or `std::string` ) -* 5: Some other unknown exception - -Although for the API consumer, error codes do not offer much value except -indicating whether *any* error occurred during the compilation, it helps -debugging the LibSass internal code paths. - -## Real-World Implementations - -The proof is in the pudding, so we have highlighted a few implementations that -should be on par with the latest LibSass interface version. Some of them may not -have all features implemented! - -1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) -2. [Go Example](https://godoc.org/github.com/wellington/go-libsass#example-Compiler--Stdin) -3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) - -## ABI forward compatibility - -We use a functional API to make dynamic linking more robust and future -compatible. The API is not yet 100% stable, so we do not yet guarantee -[ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward -compatibility. - -## Plugins (experimental) - -LibSass can load plugins from directories. Just define `plugin_path` on context -options to load all plugins from the directories. To implement plugins, please -consult the following example implementations. - -- https://github.com/mgreter/libsass-glob -- https://github.com/mgreter/libsass-math -- https://github.com/mgreter/libsass-digest - -## Internal Structs - -- [Sass Context Internals](api-context-internal.md) -- [Sass Value Internals](api-value-internal.md) -- [Sass Function Internals](api-function-internal.md) -- [Sass Importer Internals](api-importer-internal.md) diff --git a/src/libsass/docs/api-function-example.md b/src/libsass/docs/api-function-example.md deleted file mode 100644 index 38608e1a2..000000000 --- a/src/libsass/docs/api-function-example.md +++ /dev/null @@ -1,67 +0,0 @@ -## Example main.c - -```C -#include -#include -#include "sass/context.h" - -union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) -{ - // get context/option struct associated with this compiler - struct Sass_Context* ctx = sass_compiler_get_context(comp); - struct Sass_Options* opts = sass_compiler_get_options(comp); - // get information about previous importer entry from the stack - Sass_Import_Entry import = sass_compiler_get_last_import(comp); - const char* prev_abs_path = sass_import_get_abs_path(import); - const char* prev_imp_path = sass_import_get_imp_path(import); - // get the cookie from function descriptor - void* cookie = sass_function_get_cookie(cb); - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate a custom function caller - Sass_Function_Entry fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - - // create list of all custom functions - Sass_Function_List fn_list = sass_make_function_list(1); - sass_function_set_list_entry(fn_list, 0, fn_foo); - sass_option_set_c_functions(ctx_opt, fn_list); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -### Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: foo(); }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` - diff --git a/src/libsass/docs/api-function-internal.md b/src/libsass/docs/api-function-internal.md deleted file mode 100644 index 69d81d04d..000000000 --- a/src/libsass/docs/api-function-internal.md +++ /dev/null @@ -1,8 +0,0 @@ -```C -// Struct to hold custom function callback -struct Sass_Function { - const char* signature; - Sass_Function_Fn function; - void* cookie; -}; -``` diff --git a/src/libsass/docs/api-function.md b/src/libsass/docs/api-function.md deleted file mode 100644 index 8d9d97ca4..000000000 --- a/src/libsass/docs/api-function.md +++ /dev/null @@ -1,74 +0,0 @@ -Sass functions are used to define new custom functions callable by Sass code. They are also used to overload debug or error statements. You can also define a fallback function, which is called for every unknown function found in the Sass code. Functions get passed zero or more `Sass_Values` (a `Sass_List` value) and they must also return a `Sass_Value`. Return a `Sass_Error` if you want to signal an error. - -## Special signatures - -- `*` - Fallback implementation -- `@warn` - Overload warn statements -- `@error` - Overload error statements -- `@debug` - Overload debug statements - -Note: The fallback implementation will be given the name of the called function as the first argument, before all the original function arguments. These features are pretty new and should be considered experimental. - -### Basic Usage - -```C -#include "sass/functions.h" -``` - -## Sass Function API - -```C -// Forward declaration -struct Sass_Compiler; -struct Sass_Function; - -// Typedef helpers for custom functions lists -typedef struct Sass_Function (*Sass_Function_Entry); -typedef struct Sass_Function* (*Sass_Function_List); -// Typedef defining function signature and return type -typedef union Sass_Value* (*Sass_Function_Fn) - (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); - -// Creators for sass function list and function descriptors -Sass_Function_List sass_make_function_list (size_t length); -Sass_Function_Entry sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); -// In case you need to free them yourself -void sass_delete_function (Sass_Function_Entry entry); -void sass_delete_function_list (Sass_Function_List list); - -// Setters and getters for callbacks on function lists -Sass_Function_Entry sass_function_get_list_entry(Sass_Function_List list, size_t pos); -void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); -Sass_Import_Entry sass_import_get_list_entry (Sass_Import_List list, size_t idx); - -// Getters for custom function descriptors -const char* sass_function_get_signature (Sass_Function_Entry cb); -Sass_Function_Fn sass_function_get_function (Sass_Function_Entry cb); -void* sass_function_get_cookie (Sass_Function_Entry cb); - -// Getters for callee entry -const char* sass_callee_get_name (Sass_Callee_Entry); -const char* sass_callee_get_path (Sass_Callee_Entry); -size_t sass_callee_get_line (Sass_Callee_Entry); -size_t sass_callee_get_column (Sass_Callee_Entry); -enum Sass_Callee_Type sass_callee_get_type (Sass_Callee_Entry); -Sass_Env_Frame sass_callee_get_env (Sass_Callee_Entry); - -// Getters and Setters for environments (lexical, local and global) -union Sass_Value* sass_env_get_lexical (Sass_Env_Frame, const char*); -void sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); -union Sass_Value* sass_env_get_local (Sass_Env_Frame, const char*); -void sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); -union Sass_Value* sass_env_get_global (Sass_Env_Frame, const char*); -void sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); -``` - -### More links - -- [Sass Function Example](api-function-example.md) -- [Sass Function Internal](api-function-internal.md) - diff --git a/src/libsass/docs/api-importer-example.md b/src/libsass/docs/api-importer-example.md deleted file mode 100644 index d83bf2609..000000000 --- a/src/libsass/docs/api-importer-example.md +++ /dev/null @@ -1,112 +0,0 @@ -## Example importer.c - -```C -#include -#include -#include "sass/context.h" - -Sass_Import_List sass_importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) -{ - // get the cookie from importer descriptor - void* cookie = sass_importer_get_cookie(cb); - Sass_Import_List list = sass_make_import_list(2); - char* local = sass_copy_c_string("local { color: green; }"); - char* remote = sass_copy_c_string("remote { color: red; }"); - list[0] = sass_make_import_entry("/tmp/styles.scss", local, 0); - list[1] = sass_make_import_entry("http://www.example.com", remote, 0); - return list; -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate custom importer - Sass_Importer_Entry c_imp = - sass_make_importer(sass_importer, 0, 0); - // create list for all custom importers - Sass_Importer_List imp_list = sass_make_importer_list(1); - // put only the importer on to the list - sass_importer_set_list_entry(imp_list, 0, c_imp); - // register list on to the context options - sass_option_set_c_importers(ctx_opt, imp_list); - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -Compile importer.c - -```bash -gcc -c importer.c -o importer.o -gcc -o importer importer.o -lsass -echo "@import 'foobar';" > importer.scss -./importer importer.scss -``` - -## Importer Behavior Examples - -```C -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass handle the import request - return NULL; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass handle the request - // swallows »@import "http://…"« pass-through - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry(path, 0, 0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // return an error to halt execution - Sass_Import_List list = sass_make_import_list(1); - const char* message = "some error message"; - list[0] = sass_make_import_entry(path, 0, 0); - sass_import_set_error(list[0], sass_copy_c_string(message), 0, 0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass load the file identifed by the importer - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry("/tmp/file.scss", 0, 0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // completely hide the import - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(0); - return list; -} - -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // completely hide the import - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry(0, 0, 0); - return list; -} -``` diff --git a/src/libsass/docs/api-importer-internal.md b/src/libsass/docs/api-importer-internal.md deleted file mode 100644 index 63d70fe75..000000000 --- a/src/libsass/docs/api-importer-internal.md +++ /dev/null @@ -1,20 +0,0 @@ -```C -// External import entry -struct Sass_Import { - char* imp_path; // path as found in the import statement - char *abs_path; // path after importer has resolved it - char* source; - char* srcmap; - // error handling - char* error; - size_t line; - size_t column; -}; - -// Struct to hold importer callback -struct Sass_Importer { - Sass_Importer_Fn importer; - double priority; - void* cookie; -}; -``` diff --git a/src/libsass/docs/api-importer.md b/src/libsass/docs/api-importer.md deleted file mode 100644 index b6265002e..000000000 --- a/src/libsass/docs/api-importer.md +++ /dev/null @@ -1,86 +0,0 @@ -By using custom importers, Sass stylesheets can be implemented in any possible way, such as by being loaded via a remote server. Please note: this feature is experimental and is implemented differently than importers in Ruby Sass. Imports must be relative to the parent import context and therefore we need to pass this information to the importer callback. This is currently done by passing the complete import string/path of the previous import context. - -## Return Imports - -You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). - -```C -Sass_Import_Entry* rv = sass_make_import_list(1); -rv[0] = sass_make_import(rel, abs, source, srcmap); -``` - -Every import will then be included in LibSass. You are allowed to only return a file path without any loaded source. This way you can ie. implement rewrite rules for import paths and leave the loading part for LibSass. - -Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not deprecate the C-API once support has been implemented. It will be used to re-map the actual sourcemap with the provided ones. - -### Basic Usage - -```C -#include "sass/functions.h" -``` - -## Sass Importer API - -```C -// Forward declaration -struct Sass_Import; - -// Forward declaration -struct Sass_C_Import_Descriptor; - -// Typedef defining the custom importer callback -typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); -// Typedef defining the importer c function prototype -typedef Sass_Import_Entry* (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); - -// Creators for custom importer callback (with some additional pointer) -// The pointer is mostly used to store the callback into the actual function -Sass_C_Import_Callback sass_make_importer (Sass_C_Import_Fn, void* cookie); - -// Getters for import function descriptors -Sass_C_Import_Fn sass_import_get_function (Sass_C_Import_Callback fn); -void* sass_import_get_cookie (Sass_C_Import_Callback fn); - -// Deallocator for associated memory -void sass_delete_importer (Sass_C_Import_Callback fn); - -// Creator for sass custom importer return argument list -Sass_Import_Entry* sass_make_import_list (size_t length); -// Creator for a single import entry returned by the custom importer inside the list -Sass_Import_Entry sass_make_import_entry (const char* path, char* source, char* srcmap); -Sass_Import_Entry sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); - -// set error message to abort import and to print out a message (path from existing object is used in output) -Sass_Import_Entry sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (Sass_Import_Entry* list, size_t idx, Sass_Import_Entry entry); -Sass_Import_Entry sass_import_get_list_entry (Sass_Import_Entry* list, size_t idx); - -// Getters for import entry -const char* sass_import_get_imp_path (Sass_Import_Entry); -const char* sass_import_get_abs_path (Sass_Import_Entry); -const char* sass_import_get_source (Sass_Import_Entry); -const char* sass_import_get_srcmap (Sass_Import_Entry); -// Explicit functions to take ownership of these items -// The property on our struct will be reset to NULL -char* sass_import_take_source (Sass_Import_Entry); -char* sass_import_take_srcmap (Sass_Import_Entry); - -// Getters for import error entries -size_t sass_import_get_error_line (Sass_Import_Entry); -size_t sass_import_get_error_column (Sass_Import_Entry); -const char* sass_import_get_error_message (Sass_Import_Entry); - -// Deallocator for associated memory (incl. entries) -void sass_delete_import_list (Sass_Import_Entry*); -// Just in case we have some stray import structs -void sass_delete_import (Sass_Import_Entry); -``` - -### More links - -- [Sass Importer Example](api-importer-example.md) -- [Sass Importer Internal](api-importer-internal.md) - diff --git a/src/libsass/docs/api-value-example.md b/src/libsass/docs/api-value-example.md deleted file mode 100644 index 690654eaf..000000000 --- a/src/libsass/docs/api-value-example.md +++ /dev/null @@ -1,55 +0,0 @@ -## Example operation.c - -```C -#include -#include -#include "sass/values.h" - -int main( int argc, const char* argv[] ) -{ - - // create two new sass values to be added - union Sass_Value* string = sass_make_string("String"); - union Sass_Value* number = sass_make_number(42, "nits"); - - // invoke the add operation which returns a new sass value - union Sass_Value* total = sass_value_op(ADD, string, number); - - // no further use for the two operands - sass_delete_value(string); - sass_delete_value(number); - - // this works since libsass will always return a - // string for add operations with a string as the - // left hand side. But you should never rely on it! - puts(sass_string_get_value(total)); - - // invoke stringification (uncompressed with precision of 5) - union Sass_Value* result = sass_value_stringify(total, false, 5); - - // no further use for the sum - sass_delete_value(total); - - // print the result - you may want to make - // sure result is indeed a string, altough - // stringify guarantees to return a string - // if (sass_value_is_string(result)) {} - // really depends on your level of paranoia - puts(sass_string_get_value(result)); - - // finally free result - sass_delete_value(result); - - // exit status - return 0; - -} -``` - -## Compile operation.c - -```bash -gcc -c operation.c -o operation.o -gcc -o operation operation.o -lsass -./operation # => String42nits -``` diff --git a/src/libsass/docs/api-value-internal.md b/src/libsass/docs/api-value-internal.md deleted file mode 100644 index fed402256..000000000 --- a/src/libsass/docs/api-value-internal.md +++ /dev/null @@ -1,76 +0,0 @@ -```C -struct Sass_Unknown { - enum Sass_Tag tag; -}; - -struct Sass_Boolean { - enum Sass_Tag tag; - bool value; -}; - -struct Sass_Number { - enum Sass_Tag tag; - double value; - char* unit; -}; - -struct Sass_Color { - enum Sass_Tag tag; - double r; - double g; - double b; - double a; -}; - -struct Sass_String { - enum Sass_Tag tag; - char* value; -}; - -struct Sass_List { - enum Sass_Tag tag; - enum Sass_Separator separator; - size_t length; - // null terminated "array" - union Sass_Value** values; -}; - -struct Sass_Map { - enum Sass_Tag tag; - size_t length; - struct Sass_MapPair* pairs; -}; - -struct Sass_Null { - enum Sass_Tag tag; -}; - -struct Sass_Error { - enum Sass_Tag tag; - char* message; -}; - -struct Sass_Warning { - enum Sass_Tag tag; - char* message; -}; - -union Sass_Value { - struct Sass_Unknown unknown; - struct Sass_Boolean boolean; - struct Sass_Number number; - struct Sass_Color color; - struct Sass_String string; - struct Sass_List list; - struct Sass_Map map; - struct Sass_Null null; - struct Sass_Error error; - struct Sass_Warning warning; -}; - -struct Sass_MapPair { - union Sass_Value* key; - union Sass_Value* value; -}; -``` - diff --git a/src/libsass/docs/api-value.md b/src/libsass/docs/api-value.md deleted file mode 100644 index d78625875..000000000 --- a/src/libsass/docs/api-value.md +++ /dev/null @@ -1,154 +0,0 @@ -`Sass_Values` are used to pass values and their types between the implementer -and LibSass. Sass knows various different value types (including nested arrays -and hash-maps). If you implement a binding to another programming language, you -have to find a way to [marshal][1] (convert) `Sass_Values` between the target -language and C. `Sass_Values` are currently only used by custom functions, but -it should also be possible to use them without a compiler context. - -[1]: https://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 - -### Basic Usage - -```C -#include "sass/values.h" -``` - -```C -// Type for Sass values -enum Sass_Tag { - SASS_BOOLEAN, - SASS_NUMBER, - SASS_COLOR, - SASS_STRING, - SASS_LIST, - SASS_MAP, - SASS_NULL, - SASS_ERROR, - SASS_WARNING -}; - -// Tags for denoting Sass list separators -enum Sass_Separator { - SASS_COMMA, - SASS_SPACE, - // only used internally to represent a hash map before evaluation - // otherwise we would be too early to check for duplicate keys - SASS_HASH -}; - -// Value Operators -enum Sass_OP { - AND, OR, // logical connectives - EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations - ADD, SUB, MUL, DIV, MOD, // arithmetic functions - NUM_OPS // so we know how big to make the op table -}; -``` - -### Sass Value API - -```C -// Forward declaration -union Sass_Value; - -// Creator functions for all value types -union Sass_Value* sass_make_null (void); -union Sass_Value* sass_make_boolean (bool val); -union Sass_Value* sass_make_string (const char* val); -union Sass_Value* sass_make_qstring (const char* val); -union Sass_Value* sass_make_number (double val, const char* unit); -union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -union Sass_Value* sass_make_map (size_t len); -union Sass_Value* sass_make_error (const char* msg); -union Sass_Value* sass_make_warning (const char* msg); - -// Generic destructor function for all types -// Will release memory of all associated Sass_Values -// Means we will delete recursively for lists and maps -void sass_delete_value (union Sass_Value* val); - -// Make a deep cloned copy of the given sass value -union Sass_Value* sass_clone_value (const union Sass_Value* val); - -// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) -union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); - -// Execute an operation for two Sass_Values and return the result as a Sass_Value too -union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); - -// Return the sass tag for a generic sass value -// Check is needed before accessing specific values! -enum Sass_Tag sass_value_get_tag (const union Sass_Value* v); - -// Check value to be of a specific type -// Can also be used before accessing properties! -bool sass_value_is_null (const union Sass_Value* v); -bool sass_value_is_number (const union Sass_Value* v); -bool sass_value_is_string (const union Sass_Value* v); -bool sass_value_is_boolean (const union Sass_Value* v); -bool sass_value_is_color (const union Sass_Value* v); -bool sass_value_is_list (const union Sass_Value* v); -bool sass_value_is_map (const union Sass_Value* v); -bool sass_value_is_error (const union Sass_Value* v); -bool sass_value_is_warning (const union Sass_Value* v); - -// Getters and setters for Sass_Number -double sass_number_get_value (const union Sass_Value* v); -void sass_number_set_value (union Sass_Value* v, double value); -const char* sass_number_get_unit (const union Sass_Value* v); -void sass_number_set_unit (union Sass_Value* v, char* unit); - -// Getters and setters for Sass_String -const char* sass_string_get_value (const union Sass_Value* v); -void sass_string_set_value (union Sass_Value* v, char* value); -bool sass_string_is_quoted(const union Sass_Value* v); -void sass_string_set_quoted(union Sass_Value* v, bool quoted); - -// Getters and setters for Sass_Boolean -bool sass_boolean_get_value (const union Sass_Value* v); -void sass_boolean_set_value (union Sass_Value* v, bool value); - -// Getters and setters for Sass_Color -double sass_color_get_r (const union Sass_Value* v); -void sass_color_set_r (union Sass_Value* v, double r); -double sass_color_get_g (const union Sass_Value* v); -void sass_color_set_g (union Sass_Value* v, double g); -double sass_color_get_b (const union Sass_Value* v); -void sass_color_set_b (union Sass_Value* v, double b); -double sass_color_get_a (const union Sass_Value* v); -void sass_color_set_a (union Sass_Value* v, double a); - -// Getter for the number of items in list -size_t sass_list_get_length (const union Sass_Value* v); -// Getters and setters for Sass_List -enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); -void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); -bool sass_list_get_is_bracketed (const union Sass_Value* v); -void sass_list_set_is_bracketed (union Sass_Value* v, bool value); -// Getters and setters for Sass_List values -union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); -void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); - -// Getter for the number of items in map -size_t sass_map_get_length (const union Sass_Value* v); -// Getters and setters for Sass_Map keys and values -union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); -void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); -union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); -void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); - -// Getters and setters for Sass_Error -char* sass_error_get_message (const union Sass_Value* v); -void sass_error_set_message (union Sass_Value* v, char* msg); - -// Getters and setters for Sass_Warning -char* sass_warning_get_message (const union Sass_Value* v); -void sass_warning_set_message (union Sass_Value* v, char* msg); -``` - -### More links - -- [Sass Value Example](api-value-example.md) -- [Sass Value Internal](api-value-internal.md) - diff --git a/src/libsass/docs/build-on-darwin.md b/src/libsass/docs/build-on-darwin.md deleted file mode 100644 index 119a5350e..000000000 --- a/src/libsass/docs/build-on-darwin.md +++ /dev/null @@ -1,27 +0,0 @@ -To install LibSass, make sure the OS X build tools are installed: - - xcode-select --install - -## Homebrew - -To install homebrew, see [http://brew.sh](http://brew.sh) - - ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - -You can install the latest version of LibSass quite easily with brew. - - brew install --HEAD libsass - -To update this, do: - - brew reinstall --HEAD libsass - -Brew will build static and shared libraries, and a `libsass.pc` file in `/usr/local/lib/pkgconfig`. - -To use `libsass.pc`, make sure this path is in your `PKG_CONFIG_PATH` - - export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig - -## Manually - -See the linux instructions [Building-with-autotools](build-with-autotools.md) or [Building-with-makefiles](build-with-makefiles.md) diff --git a/src/libsass/docs/build-on-gentoo.md b/src/libsass/docs/build-on-gentoo.md deleted file mode 100644 index 601b1fe5e..000000000 --- a/src/libsass/docs/build-on-gentoo.md +++ /dev/null @@ -1,55 +0,0 @@ -Here are two ebuilds to compile LibSass and sassc on gentoo linux. If you do not know how to use these ebuilds, you should probably read the gentoo wiki page about [portage overlays](http://wiki.gentoo.org/wiki/Overlay). - -## www-misc/libsass/libsass-9999.ebuild -```ebuild -EAPI=4 - -inherit eutils git-2 autotools - -DESCRIPTION="A C/C++ implementation of a Sass compiler." -HOMEPAGE="http://libsass.org/" -EGIT_PROJECT='libsass' -EGIT_REPO_URI="https://github.com/sass/libsass.git" -LICENSE="MIT" -SLOT="0" -KEYWORDS="" -IUSE="" -DEPEND="" -RDEPEND="${DEPEND}" -DEPEND="${DEPEND}" - -pkg_pretend() { - # older gcc is not supported - local major=$(gcc-major-version) - local minor=$(gcc-minor-version) - [[ "${MERGE_TYPE}" != "binary" && ( $major > 4 || ( $major == 4 && $minor < 5 ) ) ]] && \ - die "Sorry, but gcc earlier than 4.5 will not work for LibSass." -} - -src_prepare() { - eautoreconf -} -``` - -## www-misc/sassc/sassc-9999.ebuild -```ebuild -EAPI=4 - -inherit eutils git-2 autotools - -DESCRIPTION="Command Line Tool for LibSass." -HOMEPAGE="http://libsass.org/" -EGIT_PROJECT='sassc' -EGIT_REPO_URI="https://github.com/sass/sassc.git" -LICENSE="MIT" -SLOT="0" -KEYWORDS="" -IUSE="" -DEPEND="www-misc/libsass" -RDEPEND="${DEPEND}" -DEPEND="${DEPEND}" - -src_prepare() { - eautoreconf -} -``` diff --git a/src/libsass/docs/build-on-windows.md b/src/libsass/docs/build-on-windows.md deleted file mode 100644 index 0afaa2e4c..000000000 --- a/src/libsass/docs/build-on-windows.md +++ /dev/null @@ -1,139 +0,0 @@ -We support builds via MingGW and via Visual Studio Community 2013. -Both should be considered experimental (MinGW was better tested)! - -## Building via MingGW (makefiles) - -First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. - -You need to have the following components installed: -![Visualization of components installed in the interface](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) - -Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. - -If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: - -```bash -gem install minitest -``` - -### Mount the mingw root directory - -As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: - -``` -C:\MinGW /mingw -``` - -### Starting a "MingGW" console - -Create a batch file with this content: -```bat -@echo off -set PATH=C:\MinGW\bin;%PATH% -REM only needed if not already available -set PATH=%PROGRAMFILES%\git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! - -### Get the sources - -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Decide for static or shared library - -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: - -```bat -set BUILD="shared" -``` - -### Compile the library -```bash -mingw32-make -C libsass -``` - -### Results can be found in -```bash -$ ls libsass/lib -libsass.a libsass.dll libsass.so -``` - -### Run the spec test-suite -```bash -mingw32-make -C libsass test_build -``` - -## Building via MingGW 64bit (makefiles) -Building libass to dll on window 64bit. - -+ downloads [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) , and unzip to "C:\mingw64". - -+ Create a batch file with this content: - -```bat -@echo off -set PATH=C:\mingw64\bin;%PATH% -set CC=gcc -REM only needed if not already available -set PATH=%PROGRAMFILES%\Git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -+ By default , mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​" , we can modify Makefile to fix this:(add "-static") - -``` bash -lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) - $(MKDIR) lib - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a -``` - -+ Compile the library - -```bash -mingw32-make -C libsass -``` - -By the way , if you are using java jna , [JNAerator](http://jnaerator.googlecode.com/) is a good tool. - -## Building via Visual Studio Community 2013 - -Open a Visual Studio 2013 command prompt: -- `VS2013 x86 Native Tools Command Prompt` - -Note: When I installed the community edition, I only got the 2012 command prompts. I copied them from the Startmenu to the Desktop and adjusted the paths from `Visual Studio 11.0` to `Visual Studio 12.0`. Since `libsass` uses some `C++11` features, you need at least a MSVC 2013 compiler (v120). - -### Get the source -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -git clone https://github.com/sass/sassc.git libsass/sassc -# only needed if you want to run the testsuite -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Compile sassc - -Sometimes `msbuild` seems not available from the command prompt. Just search for it and add it to the global path. It seems to be included in the .net folders too. - -```bat -cd libsass -REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin -msbuild /m:4 /p:Configuration=Release win\libsass.sln -REM running the spec test-suite manually (needs ruby and minitest gem) -ruby sass-spec\sass-spec.rb -V 3.5 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec -cd .. -``` - -[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files -[2]: https://msysgit.github.io/ -[3]: http://rubyinstaller.org/ diff --git a/src/libsass/docs/build-shared-library.md b/src/libsass/docs/build-shared-library.md deleted file mode 100644 index 3c143b46a..000000000 --- a/src/libsass/docs/build-shared-library.md +++ /dev/null @@ -1,35 +0,0 @@ -This page is mostly intended for people that want to build a system library that gets distributed via RPMs or other means. This is currently in a experimental phase, as we currently do not really guarantee any ABI forward compatibility. The C API was rewritten to make this possible in the future, but we want to wait some more time till we can call this final and stable. - -Building via autotools --- - -You want to build a system library only via autotools, since it will create the proper `libtool` files to make it loadable on multiple systems. We hope this works correctly, but nobody of the `libsass` core team has much knowledge in this area. Therefore we are open for comments or improvements by people that have more experience in that matter (like package maintainers from various linux distributions). - -```bash -apt-get install autoconf libtool -git clone https://github.com/sass/libsass.git -cd libsass -autoreconf --force --install -./configure \ - --disable-tests \ - --disable-static \ - --enable-shared \ - --prefix=/usr -make -j5 install -cd .. -``` - -This should install these files -```bash -# $ ls -la /usr/lib/libsass.* -/usr/lib/libsass.la -/usr/lib/libsass.so -> libsass.so.0.0.9 -/usr/lib/libsass.so.0 -> libsass.so.0.0.9 -/usr/lib/libsass.so.0.0.9 -# $ ls -la /usr/include/sass* -/usr/include/sass.h -/usr/include/sass2scss.h -/usr/include/sass/context.h -/usr/include/sass/functions.h -/usr/include/sass/values.h -``` diff --git a/src/libsass/docs/build-with-autotools.md b/src/libsass/docs/build-with-autotools.md deleted file mode 100644 index a48ed18aa..000000000 --- a/src/libsass/docs/build-with-autotools.md +++ /dev/null @@ -1,78 +0,0 @@ -### Get the sources -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Prerequisites - -In order to run autotools you need a few tools installed on your system. -```bash -yum install automake libtool # RedHat Linux -emerge -a automake libtool # Gentoo Linux -pkgin install automake libtool # SmartOS -``` - - -### Create configure script -```bash -cd libsass -autoreconf --force --install -cd .. -``` - -### Create custom makefiles -```bash -cd libsass -./configure \ - --disable-tests \ - --disable-shared \ - --prefix=/usr -cd .. -``` - -### Build the library -```bash -make -C libsass -j5 -``` - -### Install the library -The library will be installed to the location given as `prefix` to `configure`. This is standard behavior for autotools and not `libsass` specific. -```bash -make -C libsass -j5 install -``` - -### Configure options -The `configure` script is created by autotools. To get an overview of available options you can call `./configure --help`. When you execute this script, it will create specific makefiles, which you then use via the regular make command. - -There are some `libsass` specific options: - -``` -Optional Features: - --enable-tests enable testing the build - --enable-coverage enable coverage report for test suite - --enable-shared build shared libraries [default=yes] - --enable-static build static libraries [default=yes] - -Optional Packages: - --with-sassc-dir= specify directory of sassc sources for - testing (default: sassc) - --with-sass-spec-dir= specify directory of sass-spec for testing - (default: sass-spec) -``` - -### Build sassc and run spec test-suite - -```bash -cd libsass -autoreconf --force --install -./configure \ - --enable-tests \ - --enable-shared \ - --prefix=/usr -make -j5 test_build -cd .. -``` diff --git a/src/libsass/docs/build-with-makefiles.md b/src/libsass/docs/build-with-makefiles.md deleted file mode 100644 index 7ae2e33d6..000000000 --- a/src/libsass/docs/build-with-makefiles.md +++ /dev/null @@ -1,68 +0,0 @@ -### Get the sources -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Decide for static or shared library - -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: - -```bash -export BUILD="shared" -``` - -Alternatively you can also define it directly when calling make: - -```bash -BUILD="shared" make ... -``` - -### Compile the library -```bash -make -C libsass -j5 -``` - -### Results can be found in -```bash -$ ls libsass/lib -libsass.a libsass.so -``` - -### Install onto the system - -We recommend to use [autotools to install](build-with-autotools.md) libsass onto the -system, since that brings all the benefits of using libtools as the main install method. -If you still want to install libsass via the makefile, you need to make sure that gnu -`install` utility (or compatible) is installed on your system. -```bash -yum install coreutils # RedHat Linux -emerge -a coreutils # Gentoo Linux -pkgin install coreutils # SmartOS -``` - -You can set the install location by setting `PREFIX` -```bash -PREFIX="/opt/local" make install -``` - - -### Compling sassc - -```bash -# Let build know library location -export SASS_LIBSASS_PATH="`pwd`/libsass" -# Invokes the sassc makefile -make -C libsass -j5 sassc -``` - -### Run the spec test-suite - -```bash -# needs ruby available -# also gem install minitest -make -C libsass -j5 test_build -``` diff --git a/src/libsass/docs/build-with-mingw.md b/src/libsass/docs/build-with-mingw.md deleted file mode 100644 index 416507f3c..000000000 --- a/src/libsass/docs/build-with-mingw.md +++ /dev/null @@ -1,107 +0,0 @@ -## Building LibSass with MingGW (makefiles) - -First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. - -You need to have the following components installed: -![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) - -Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. - -If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: - -```bash -gem install minitest -``` - -### Mount the mingw root directory - -As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: - -``` -C:\MinGW /mingw -``` - -### Starting a "MingGW" console - -Create a batch file with this content: -```bat -@echo off -set PATH=C:\MinGW\bin;%PATH% -REM only needed if not already available -set PATH=%PROGRAMFILES%\git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! - -### Get the sources - -```bash -# using git is preferred -git clone https://github.com/sass/libsass.git -# only needed for sassc and/or testsuite -git clone https://github.com/sass/sassc.git libsass/sassc -git clone https://github.com/sass/sass-spec.git libsass/sass-spec -``` - -### Decide for static or shared library - -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: - -```bat -set BUILD="shared" -``` - -### Compile the library -```bash -mingw32-make -C libsass -``` - -### Results can be found in -```bash -$ ls libsass/lib -libsass.a libsass.dll libsass.so -``` - -### Run the spec test-suite -```bash -mingw32-make -C libsass test_build -``` - -## Building via MingGW 64bit (makefiles) -Building libass to dll on window 64bit. - -Download [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) and unzip to "C:\mingw64". - -Create a batch file with this content: - -```bat -@echo off -set PATH=C:\mingw64\bin;%PATH% -set CC=gcc -REM only needed if not already available -set PATH=%PROGRAMFILES%\Git\bin;%PATH% -REM C:\MinGW\msys\1.0\msys.bat -cmd -``` - -By default, mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​", we can modify Makefile to fix this:(add "-static") - -``` bash -lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) - $(MKDIR) lib - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a -``` - -Compile the library - -```bash -mingw32-make -C libsass -``` - -By the way, if you are using java jna, [JNAerator](http://jnaerator.googlecode.com/) is a good tool. - -[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files -[2]: https://msysgit.github.io/ -[3]: http://rubyinstaller.org/ diff --git a/src/libsass/docs/build-with-visual-studio.md b/src/libsass/docs/build-with-visual-studio.md deleted file mode 100644 index 275b917b8..000000000 --- a/src/libsass/docs/build-with-visual-studio.md +++ /dev/null @@ -1,90 +0,0 @@ -## Building LibSass with Visual Studio - -### Requirements: - -The minimum requirement to build LibSass with Visual Studio is "Visual Studio 2013 Express for Desktop". - -Additionally, it is recommended to have `git` installed and available in `PATH`, so to deduce the `libsass` version information. For instance, if GitHub for Windows (https://windows.github.com/) is installed, the `PATH` will have an entry resembling: `X:\Users\\AppData\Local\GitHub\PortableGit_\cmd\` (where `X` is the drive letter of system drive). If `git` is not available, inquiring the LibSass version will result in `[NA]`. - -### Build Steps: - -#### From Visual Studio: - -On opening the `win\libsass.sln` solution and build (Ctrl+Shift+B) to build `libsass.dll`. - -To Build LibSass as a static Library, it is recommended to set an environment variable `LIBSASS_STATIC_LIB` before launching the project: - -```cmd -cd path\to\libsass -SET LIBSASS_STATIC_LIB=1 -:: -:: or in PowerShell: -:: $env:LIBSASS_STATIC_LIB=1 -:: -win\libsass.sln -``` - -Visual Studio will form the filtered source tree as shown below: - -![image](https://cloud.githubusercontent.com/assets/3840695/9298985/aae9e072-44bf-11e5-89eb-e7995c098085.png) - -`Header Files` contains the .h and .hpp files, while `Source Files` covers `.c` and `.cpp`. The other used headers/sources will appear under `External Dependencies`. - -If there is a LibSass code file appearing under External Dependencies, it can be changed by altering the `win\libsass.vcxproj.filters` file or dragging in Solution Explorer. - -#### From Command Prompt: - -Notice that in the following commands: - -* If the platform is 32-bit Windows, replace `ProgramFiles(x86)` with `ProgramFiles`. -* To build with Visual Studio 2015, replace `12.0` with `14.0` in the aforementioned command. - -Open a command prompt: - -To build dynamic/shared library (`libsass.dll`): - -```cmd -:: debug build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln - -:: release build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:Configuration=Release -``` - -To build static library (`libsass.lib`): - -```cmd -:: debug build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:LIBSASS_STATIC_LIB=1 - -:: release build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release -``` - -#### From PowerShell: - -To build dynamic/shared library (`libsass.dll`): - -```powershell -# debug build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln - -# release build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:Configuration=Release -``` - -To build static library (`libsass.lib`): - -```powershell -# build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:LIBSASS_STATIC_LIB=1 - -# release build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release -``` diff --git a/src/libsass/docs/build.md b/src/libsass/docs/build.md deleted file mode 100644 index c656d8839..000000000 --- a/src/libsass/docs/build.md +++ /dev/null @@ -1,97 +0,0 @@ -`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line][6]. Or some [bindings|Implementations][9] to use it within your favorite programming language. You should be able to get [`sassc`][6] running by following the instructions in this guide. - -Before starting, see [setup dev environment](setup-environment.md). - -Building on different Operating Systems --- - -We try to keep the code as OS independent and standard compliant as possible. Reading files from the file-system has some OS depending code, but will ultimately fall back to a posix compatible implementation. We do use some `C++11` features, but are so far only committed to use `unordered_map`. This means you will need a pretty recent compiler on most systems (gcc 4.5 seems to be the minimum). - -### Building on Linux (and other *nix flavors) - -Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). - -- [Building with makefiles][1] -- [Building with autotools][2] - -### Building on Windows (experimental) - -Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! - -- [Building with MinGW][3] -- [Building with Visual Studio][11] - -### Building on Max OS X (untested) - -Works the same as on linux, but you can also install LibSass via `homebrew`. - -- [Building on Mac OS X][10] - -### Building a system library (experimental) - -Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. - -- [Building shared system library][4] - -Compiling with clang instead of gcc --- - -To use clang you just need to set the appropriate environment variables: - -```bash -export CC=/usr/bin/clang -export CXX=/usr/bin/clang++ -``` - -Running the spec test-suite --- - -We constantly and automatically test `libsass` against the official [spec test-suite][5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: - -```bash -ruby -v -gem install minitest -# should be optional -gem install minitap -``` - -Including the LibSass version --- - -There is a function in `libsass` to query the current version. This has to be defined at compile time. We use a C macro for this, which can be defined by calling `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not need to do anything if you use the makefiles or autotools. They will try to fetch the version via git directly. If you only have the sources without the git repo, you can pass the version as an environment variable to `make` or `configure`: - -``` -export LIBSASS_VERSION="x.y.z." -``` - -Continuous Integration --- - -We use two CI services to automatically test all commits against the latest [spec test-suite][5]. - -- [LibSass on Travis-CI (linux)][7] -[![Build Status](https://travis-ci.org/sass/libsass.png?branch=master)](https://travis-ci.org/sass/libsass) -- [LibSass on AppVeyor (windows)][8] -[![Build status](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/mgreter/libsass-513/branch/master) - -Why not using CMake? --- - -There were some efforts to get `libsass` to compile with CMake, which should make it easier to create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! - -Miscellaneous --- - -- [Ebuilds for Gentoo Linux](build-on-gentoo.md) - -[1]: build-with-makefiles.md -[2]: build-with-autotools.md -[3]: build-with-mingw.md -[4]: build-shared-library.md -[5]: https://github.com/sass/sass-spec -[6]: https://github.com/sass/sassc -[7]: https://github.com/sass/libsass/blob/master/.travis.yml -[8]: https://github.com/sass/libsass/blob/master/appveyor.yml -[9]: implementations.md -[10]: build-on-darwin.md -[11]: build-with-visual-studio.md diff --git a/src/libsass/docs/compatibility-plan.md b/src/libsass/docs/compatibility-plan.md deleted file mode 100644 index d8e538fa4..000000000 --- a/src/libsass/docs/compatibility-plan.md +++ /dev/null @@ -1,48 +0,0 @@ -This document is to serve as a living, changing plan for getting LibSass caught up with Ruby Sass. - -_Note: an "s" preceeding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ - -# Goal -**Our goal is to reach full s3.4 compatibility as soon as possible. LibSass version 3.4 will behave just like Ruby Sass 3.4** - -I highlight the goal, because there are some things that are *not* currently priorities. To be clear, they WILL be priorities, but they are not at the moment: - -* Performance Improvements -* Extensibility - -The overriding goal is correctness. - -## Verifying Correctness -LibSass uses the spec for its testing. The spec was originally based off s3.2 tests. Many things have changed in Ruby Sass since then and some of the tests need to be updated and changed in order to get them to match both LibSass and Ruby Sass. - -Until this project is complete, the spec will be primarily a place to test LibSass. By the time LibSass reaches 3.4, it is our goal that sass-spec will be fully usable as an official testing source for ALL implementations of Sass. - -## Version Naming -Until LibSass reaches parity with Ruby Sass, we will be aggressively bumping versions, and LibSass 3.4 will be the peer to Ruby Sass 3.4 in every way. - -# Release Plan - -## 3.0 -The goal of 3.0 is to introduce some of the most demanded features for LibSass. That is, we are focusing on issues and features that have kept adoption down. This is a mongrel release wrt which version of Sass it's targeting. It's often a mixture of 3.2 / 3.3 / 3.4 behaviours. This is not ideal, but it's favourable to not existing. Targeting 3.4 strictly during this release would mean we never actually release. - -# 3.1 -The goal of 3.1 is to update all the passing specs to agree with 3.4. This will not be a complete representation of s3.4 (aka, there will me missing features), but the goal is to change existing features and implemented features to match 3.4 behaviour. - -By the end of this, the sass-spec must pass against 3.4. - -Major issues: -* Variable Scoping -* Color Handling -* Precision - -# 3.2 -This version will focus on edge case fixes. There are a LOT of edge cases in the _todo_ tests and this is the release where we hunt those down like dogs (not that we want to hurt dogs, it's just a figure of speech in English). - -# 3.3 -Dress rehearsal. When we are 99% sure that we've fixed the main issues keeping us from saying we are compliant in s3.4 behaviour. - -# 3.4 -Compass Compatibility. We need to be able to work with Compass and all the other libraries out there. At this point, we are calling LibSass "mature" - -# Beyond 3.4 -Obviously, there is matching Sass 3.5 behaviour. But, beyond that, we'll want to focus on performance, stability, and error handling. These can always be improved upon and are the life's work of an open source project. We'll have to work closely with Sass in the future. diff --git a/src/libsass/docs/contributing.md b/src/libsass/docs/contributing.md deleted file mode 100644 index 4a2d470ef..000000000 --- a/src/libsass/docs/contributing.md +++ /dev/null @@ -1,17 +0,0 @@ -First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! - -## I'm a programmer - -Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! - -To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). - -## I'm not a backend programmer - -COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. - -Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. - -## I don't know what a computer is? - -Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/src/libsass/docs/custom-functions-internal.md b/src/libsass/docs/custom-functions-internal.md deleted file mode 100644 index 57fec82b8..000000000 --- a/src/libsass/docs/custom-functions-internal.md +++ /dev/null @@ -1,122 +0,0 @@ -# Developer Documentation - -Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. - -## Sass_C_Function_Descriptor - -```C -struct Sass_C_Function_Descriptor { - const char* signature; - Sass_C_Function function; - void* cookie; -}; -``` - -- `signature`: The function declaration, like `foo($bar, $baz:1)` -- `function`: Reference to the C function callback -- `cookie`: any pointer you want to attach - -### signature - -The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. - - foo($bar, $baz: 2) - -In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. - -### function - -The callback function needs to be of the following form: - -```C -union Sass_Value* call_sass_function( - const union Sass_Value* s_args, - void* cookie -) { - return sass_clone_value(s_args); -} -``` - -### cookie - -The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! - -## Sass_Values - -```C -// allocate memory (copies passed strings) -union Sass_Value* sass_make_null (void); -union Sass_Value* sass_make_boolean (bool val); -union Sass_Value* sass_make_string (const char* val); -union Sass_Value* sass_make_qstring (const char* val); -union Sass_Value* sass_make_number (double val, const char* unit); -union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -union Sass_Value* sass_make_map (size_t len); -union Sass_Value* sass_make_error (const char* msg); -union Sass_Value* sass_make_warning (const char* msg); - -// Make a deep cloned copy of the given sass value -union Sass_Value* sass_clone_value (const union Sass_Value* val); - -// deallocate memory (incl. all copied memory) -void sass_delete_value (const union Sass_Value* val); -``` - -## Example main.c - -```C -#include -#include -#include "sass/context.h" - -union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) -{ - // we actually abuse the void* to store an "int" - return sass_make_number((size_t)cookie, "px"); -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate a custom function caller - Sass_C_Function_Callback fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - - // create list of all custom functions - Sass_C_Function_List fn_list = sass_make_function_list(1); - sass_function_set_list_entry(fn_list, 0, fn_foo); - sass_option_set_c_functions(ctx_opt, fn_list); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); - - // print the result or the error to the stdout - if (status == 0) puts(sass_context_get_output_string(ctx)); - else puts(sass_context_get_error_message(ctx)); - - // release allocated memory - sass_delete_file_context(file_ctx); - - // exit status - return status; - -} -``` - -## Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: foo(); }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` diff --git a/src/libsass/docs/dev-ast-memory.md b/src/libsass/docs/dev-ast-memory.md deleted file mode 100644 index 31004bcf2..000000000 --- a/src/libsass/docs/dev-ast-memory.md +++ /dev/null @@ -1,223 +0,0 @@ -# LibSass smart pointer implementation - -LibSass uses smart pointers very similar to `shared_ptr` known -by Boost or C++11. Implementation is a bit less modular since -it was not needed. Various compile time debug options are -available if you need to debug memory life-cycles. - - -## Memory Classes - -### SharedObj - -Base class for the actual node implementations. This ensures -that every object has a reference counter and other values. - -```c++ -class AST_Node : public SharedObj { ... }; -``` - -### SharedPtr (base class for SharedImpl) - -Base class that holds on to the pointer. The reference counter -is stored inside the pointer object directly (`SharedObj`). - -### SharedImpl (inherits from SharedPtr) - -This is the main base class for objects you use in your code. It -will make sure that the memory it points at will be deleted once -all copies to the same object/memory go out of scope. - -```c++ -Class* pointer = new Class(...); -SharedImpl obj(pointer); -``` - -To spare the developer of typing the templated class every time, -we created typedefs for each available AST Node specialization. - -```c++ -typedef SharedImpl Number_Obj; -Number_Obj number = SASS_MEMORY_NEW(...); -``` - - -## Memory life-cycles - -### Pointer pickups - -I often use the terminology of "pickup". This means the moment when -a raw pointer not under any control is assigned to a reference counted -object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be -automatically released once the object goes out of scope (but only -if the reference counter reaches zero). Main point beeing, you don't -have to worry about memory management yourself. - -### Object detach - -Sometimes we can't return reference counted objects directly (see -invalid covariant return types problems below). But we often still -need to use reference objects inside a function to avoid leaks when -something throws. For this you can use `detach`, which basically -detaches the pointer memory from the reference counted object. So -when the reference counted object goes out of scope, it will not -free the attached memory. You are now again in charge of freeing -the memory (just assign it to a reference counted object again). - - -## Circular references - -Reference counted memory implementations are prone to circular references. -This can be addressed by using a multi generation garbage collector. But -for our use-case that seems overkill. There is no way so far for users -(sass code) to create circular references. Therefore we can code around -this possible issue. But developers should be aware of this limitation. - -There are AFAIR two places where circular references could happen. One is -the `sources` member on every `Selector`. The other one can happen in the -extend code (Node handling). The easy way to avoid this is to only assign -complete object clones to these members. If you know the objects lifetime -is longer than the reference you create, you can also just store the raw -pointer. Once needed this could be solved with weak pointers. - - -## Addressing the invalid covariant return types problems - -If you are not familiar with the mentioned problem, you may want -to read up on covariant return types and virtual functions, i.e. - -- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers -- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers -- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr - -We hit this issue at least with the CRTP visitor pattern (eval, expand, -listize and so forth). This means we cannot return reference counted -objects directly. We are forced to return raw pointers or we would need -to have a lot of explicit and expensive upcasts by callers/consumers. - -### Simple functions that allocate new AST Nodes - -In the parser step we often create new objects and can just return a -unique pointer (meaning ownership clearly shifts back to the caller). -The caller/consumer is responsible that the memory is freed. - -```c++ -typedef Number* Number_Ptr; -int parse_integer() { - ... // do the parsing - return 42; -} -Number_Ptr parse_number() { - Number_Ptr p_nr = SASS_MEMORY_NEW(...); - p_nr->value(parse_integer()); - return p_nr; -} -Number_Obj nr = parse_number(); -``` - -The above would be the encouraged pattern for such simple cases. - -### Allocate new AST Nodes in functions that can throw - -There is a major caveat with the previous example, considering this -more real-life implementation that throws an error. The throw may -happen deep down in another function. Holding raw pointers that -we need to free would leak in this case. - -```c++ -int parse_integer() { - ... // do the parsing - if (error) throw(error); - return 42; -} -``` - -With this `parse_integer` function the previous example would leak memory. -I guess it is pretty obvious, as the allocated memory will not be freed, -as it was never assigned to a SharedObj value. Therefore the above code -would better be written as: - -```c++ -typedef Number* Number_Ptr; -int parse_integer() { - ... // do the parsing - if (error) throw(error); - return 42; -} -// this leaks due to pointer return -// should return Number_Obj instead -// though not possible for virtuals! -Number_Ptr parse_number() { - Number_Obj nr = SASS_MEMORY_NEW(...); - nr->value(parse_integer()); // throws - return &nr; // Ptr from Obj -} -Number_Obj nr = parse_number(); -// will now be freed automatically -``` - -The example above unfortunately will not work as is, since we return a -`Number_Ptr` from that function. Therefore the object allocated inside -the function is already gone when it is picked up again by the caller. -The easy fix for the given simplified use case would be to change the -return type of `parse_number` to `Number_Obj`. Indeed we do it exactly -this way in the parser. But as stated above, this will not work for -virtual functions due to invalid covariant return types! - -### Return managed objects from virtual functions - -The easy fix would be to just create a new copy on the heap and return -that. But this seems like a very inelegant solution to this problem. I -mean why can't we just tell the object to treat it like a newly allocated -object? And indeed we can. I've added a `detach` method that will tell -the object to survive deallocation until the next pickup. This means -that it will leak if it is not picked up by consumer. - -```c++ -typedef Number* Number_Ptr; -int parse_integer() { - ... // do the parsing - if (error) throw(error); - return 42; -} -Number_Ptr parse_number() { - Number_Obj nr = SASS_MEMORY_NEW(...); - nr->value(parse_integer()); // throws - return nr.detach(); -} -Number_Obj nr = parse_number(); -// will now be freed automatically -``` - - -## Compile time debug options - -To enable memory debugging you need to define `DEBUG_SHARED_PTR`. -This can i.e. be done in `include/sass/base.h` - -```c++ -define DEBUG_SHARED_PTR -``` - -This will print lost memory on exit to stderr. You can also use -`setDbg(true)` on sepecific variables to emit reference counter -increase, decrease and other events. - - -## Why reinvent the wheel when there is `shared_ptr` from C++11 - -First, implementing a smart pointer class is not really that hard. It -was indeed also a learning experience for myself. But there are more -profound advantages: - -- Better GCC 4.4 compatibility (which most code still has OOTB) -- Not thread safe (give us some free performance on some compiler) -- Beeing able to track memory allocations for debugging purposes -- Adding additional features if needed (as seen in `detach`) -- Optional: optimized weak pointer implementation possible - -### Thread Safety - -As said above, this is not thread safe currently. But we don't need -this ATM anyway. And I guess we probably never will share AST Nodes -across different threads. \ No newline at end of file diff --git a/src/libsass/docs/implementations.md b/src/libsass/docs/implementations.md deleted file mode 100644 index 5239adcde..000000000 --- a/src/libsass/docs/implementations.md +++ /dev/null @@ -1,65 +0,0 @@ -There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. - -### C -* [sassc](https://github.com/hcatlin/sassc) - -### Crystal -* [sass.cr](https://github.com/straight-shoota/sass.cr) - -### Elixir -* [sass.ex](https://github.com/scottdavis/sass.ex) - -### Go -* [go-libsass](https://github.com/wellington/go-libsass) -* [go_sass](https://github.com/suapapa/go_sass) -* [go-sass](https://github.com/SamWhited/go-sass) - -### Haskell -* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) -* [hSass](https://github.com/jakubfijalkowski/hsass) - -### Java -* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) -* [jsass](https://github.com/bit3/jsass) - -### JavaScript -* [sass.js](https://github.com/medialize/sass.js) - -### Lua -* [lua-sass](https://github.com/craigbarnes/lua-sass) - -### .NET -* [libsass-net](https://github.com/darrenkopp/libsass-net) -* [NSass](https://github.com/TBAPI-0KA/NSass) -* [Sass.Net](https://github.com/andyalm/Sass.Net) -* [SharpScss](https://github.com/xoofx/SharpScss) -* [LibSassHost](https://github.com/Taritsyn/LibSassHost) - -### Nim -* [nim-sass](https://github.com/zacharycarter/nim-sass) - -### node.js -* [node-sass](https://github.com/sass/node-sass) - -### Perl -* [CSS::Sass](https://github.com/caldwell/CSS-Sass) -* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) - -### PHP -* [sassphp](https://github.com/sensational/sassphp) -* [php-sass](https://github.com/lesstif/php-sass) - -### Python -* [libsass-python](https://github.com/dahlia/libsass-python) -* [SassPython](https://github.com/marianoguerra/SassPython) -* [pylibsass](https://github.com/rsenk330/pylibsass) -* [python-scss](https://github.com/pistolero/python-scss) - -### Ruby -* [sassruby](https://github.com/hcatlin/sassruby) - -### Scala -* [Sass-Scala](https://github.com/kkung/Sass-Scala) - -### Tcl -* [tclsass](https://github.com/flightaware/tclsass) diff --git a/src/libsass/docs/plugins.md b/src/libsass/docs/plugins.md deleted file mode 100644 index a9711e3e1..000000000 --- a/src/libsass/docs/plugins.md +++ /dev/null @@ -1,47 +0,0 @@ -Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). - -## plugin.cpp - -```C++ -#include -#include -#include -#include "sass_values.h" - -union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) -{ - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); -} - -extern "C" const char* ADDCALL libsass_get_version() { - return libsass_version(); -} - -extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() -{ - // allocate a custom function caller - Sass_C_Function_Callback fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - // create list of all custom functions - Sass_C_Function_List fn_list = sass_make_function_list(1); - // put the only function in this plugin to the list - sass_function_set_list_entry(fn_list, 0, fn_foo); - // return the list - return fn_list; -} -``` - -To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). - -## Compile with gcc on linux - -```bash -g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass -``` - -## Compile with mingw on windows - -```bash -g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass -``` diff --git a/src/libsass/docs/setup-environment.md b/src/libsass/docs/setup-environment.md deleted file mode 100644 index 805613656..000000000 --- a/src/libsass/docs/setup-environment.md +++ /dev/null @@ -1,68 +0,0 @@ -## Requirements -In order to install and setup your local development environment, there are some prerequisites: - -* git -* gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) -* ruby w/ bundler - -OS X: -First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: - -```` -xcode-select --install -```` - -## Cloning the Projects - -First, clone the project and then add a line to your `~/.bash_profile` that will let other programs know where the LibSass dev files are. - -```` -git clone git@github.com:sass/libsass.git -cd libsass -echo "export SASS_LIBSASS_PATH=$(pwd)" >> ~/.bash_profile - -```` - -Then, if you run the "bootstrap" script, it should clone all the other required projects. - -```` -./script/bootstrap -```` - -You should now have a `sass-spec` and `sassc` folder within the libsass folder. Both of these are clones of their respective git projects. If you want to do a pull request, remember to work in those folders. For instance, if you want to add a test (see other documentation for how to do that), make sure to commit it to your *fork* of the sass-spec github project. Also, whenever you are running tests, make sure to `pull` from the origin! We want to make sure we are testing against the newest libsass, sassc, and sass-spec! - -Now, try and see if you can build the project. We do that with the `make` command. - -```` -make -```` - -At this point, if you get an error, something is most likely wrong with your compiler installation. Yikes. It's hard to cover how to fix this in an article. Feel free to open an issue and we'll try and help! But, remember, before you do that, googling the error message is your friend! Many problems are solved quickly that way. - -## Running The Spec Against LibSass - -Then, to run the spec against LibSass, just run: - -```` -./script/spec -```` - -If you get an error about `SASS_LIBSASS_PATH`, you may still need to set a variable pointing to the libsass folder, like this: - -```` -export SASS_LIBSASS_PATH=/Users/you/path/libsass -```` - -...where the latter part is to the `libsass` directory you've cloned. You can get this path by typing `pwd` in the Terminal - -## Running the Spec Against Ruby Sass - -Go into the sass-spec folder that should have been cloned earlier with the "bootstrap" command. Run the following. - -```` -bundle install -./sass-spec.rb -```` - -Voila! Now you are testing against Sass too! - diff --git a/src/libsass/docs/source-map-internals.md b/src/libsass/docs/source-map-internals.md deleted file mode 100644 index 50f83b54f..000000000 --- a/src/libsass/docs/source-map-internals.md +++ /dev/null @@ -1,51 +0,0 @@ -This document is mainly intended for developers! - -# Documenting some of the source map internals - -Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. - -The main storage for SourceMap mappings is the `mappings` vector: - -``` -# in source_map.hpp -vector mappings -# in mappings.hpp -struct Mapping ... - Position original_position; - Position generated_position; -``` - -## Every parsed token has its source associated - -LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: - -``` -if (lex< custom_property_name >()) { - Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); - return new (ctx.mem) Declaration(path, prop->position(), prop, ...); -} -``` - -## How is the `source_position` calculated - -This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! - -``` -lex< exactly < empty_str > >(); -end = new (ctx.mem) String_Constant(path, source_position, lexed); -``` - -## How are mappings for the output created - -So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: - -``` -# in source_map.hpp -void add_mapping(AST_Node* node); -``` - -This method is called in two places: -- `Inspect::append_to_buffer` -- `Output_[Nested|Compressed]::append_to_buffer` - -Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/src/libsass/docs/trace.md b/src/libsass/docs/trace.md deleted file mode 100644 index 4a57c901f..000000000 --- a/src/libsass/docs/trace.md +++ /dev/null @@ -1,26 +0,0 @@ -## This is proposed interface in https://github.com/sass/libsass/pull/1288 - -Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. - -Both macros simulate a string stream, so they can be used like this: - - TRACE() << "Reached."; - -produces: - - [LibSass] parse_value parser.cpp:1384 Reached. - -`TRACE()` - logs function name, source filename, source file name to the standard error and the attached - stream to the standard error. - -`TRACEINST(obj)` - logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: - - TRACEINST(this) << "String_Constant created " << this; - -produces: - - [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") - -The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/src/libsass/docs/triage.md b/src/libsass/docs/triage.md deleted file mode 100644 index 0fc11784c..000000000 --- a/src/libsass/docs/triage.md +++ /dev/null @@ -1,17 +0,0 @@ -This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: - -1. Issue is reported by a user. -2. If the issue seems like a bug, then the "bug" tag is added. -3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. -4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. -5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. -6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! -7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. -8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". -9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". -10. A C++ developer will then work on the issue and issue a pull request to fix the issue. -11. A core member verifies that the fix does actually fix the spec tests. -12. The fix is merged into the project. -13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues -14. The issue is closed -15. Have a soda pop or enjoyable beverage of your choice diff --git a/src/libsass/docs/unicode.md b/src/libsass/docs/unicode.md deleted file mode 100644 index a1eb5b1cf..000000000 --- a/src/libsass/docs/unicode.md +++ /dev/null @@ -1,45 +0,0 @@ -LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your -input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected -behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! - -### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) - -This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. - -Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). - -### Current status on LibSass unicode support - -LibSass should/is fully UTF (and therefore plain ASCII) compatible. - -~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ - -LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. - -### Current encoding auto detection - -LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! - -### What is currently not supported - -- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) -- Using non ASCII characters in different encodings in different includes - -### What is missing to support the above cases - -- A way to convert between encodings (like libiconv/ICU) -- Sniffing the charset inside the file (source is available) -- Handling the conversion on import (and export) -- Optional: Make output encoding configurable -- Optional: Add optional/mandatory BOM (configurable) - -### Low priority feature - -I guess the current implementation should handle more than 99% of all real world use cases. -A) Unicode characters are still seldomly seen (as they can be written escaped) -~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. -Although I'm not sure how this applies to asian and other "exotic" codepages!~~ - -I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). - -I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/src/libsass/extconf.rb b/src/libsass/extconf.rb deleted file mode 100644 index 3e6d00bc9..000000000 --- a/src/libsass/extconf.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'mkmf' -# .. more stuff -#$LIBPATH.push(Config::CONFIG['libdir']) -$CFLAGS << " #{ENV["CFLAGS"]}" -$LIBS << " #{ENV["LIBS"]}" -create_makefile("libsass") diff --git a/src/libsass/include/sass.h b/src/libsass/include/sass.h deleted file mode 100644 index 1dd8b06dc..000000000 --- a/src/libsass/include/sass.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SASS_H -#define SASS_H - -// #define DEBUG 1 - -// include API headers -#include -#include -#include -#include -#include -#include - -#endif - diff --git a/src/libsass/include/sass/base.h b/src/libsass/include/sass/base.h deleted file mode 100644 index 88dd8d303..000000000 --- a/src/libsass/include/sass/base.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef SASS_BASE_H -#define SASS_BASE_H - -// #define DEBUG_SHARED_PTR - -#ifdef _MSC_VER - #pragma warning(disable : 4503) - #ifndef _SCL_SECURE_NO_WARNINGS - #define _SCL_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_NONSTDC_NO_DEPRECATE - #define _CRT_NONSTDC_NO_DEPRECATE - #endif -#endif - -#include -#include - -#ifdef __GNUC__ - #define DEPRECATED(func) func __attribute__ ((deprecated)) -#elif defined(_MSC_VER) - #define DEPRECATED(func) __declspec(deprecated) func -#else - #pragma message("WARNING: You need to implement DEPRECATED for this compiler") - #define DEPRECATED(func) func -#endif - -#ifdef _WIN32 - - /* You should define ADD_EXPORTS *only* when building the DLL. */ - #ifdef ADD_EXPORTS - #define ADDAPI __declspec(dllexport) - #define ADDCALL __cdecl - #else - #define ADDAPI - #define ADDCALL - #endif - -#else /* _WIN32 not defined. */ - - /* Define with no value on non-Windows OSes. */ - #define ADDAPI - #define ADDCALL - -#endif - -/* Make sure functions are exported with C linkage under C++ compilers. */ -#ifdef __cplusplus -extern "C" { -#endif - - -// Different render styles -enum Sass_Output_Style { - SASS_STYLE_NESTED, - SASS_STYLE_EXPANDED, - SASS_STYLE_COMPACT, - SASS_STYLE_COMPRESSED, - // only used internaly - SASS_STYLE_INSPECT, - SASS_STYLE_TO_SASS -}; - -// to allocate buffer to be filled -ADDAPI void* ADDCALL sass_alloc_memory(size_t size); -// to allocate a buffer from existing string -ADDAPI char* ADDCALL sass_copy_c_string(const char* str); -// to free overtaken memory when done -ADDAPI void ADDCALL sass_free_memory(void* ptr); - -// Some convenient string helper function -ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); -ADDAPI char* ADDCALL sass_string_unquote (const char* str); - -// Implemented sass language version -// Hardcoded version 3.4 for time being -ADDAPI const char* ADDCALL libsass_version(void); - -// Get compiled libsass language -ADDAPI const char* ADDCALL libsass_language_version(void); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/src/libsass/include/sass/context.h b/src/libsass/include/sass/context.h deleted file mode 100644 index 29754b75f..000000000 --- a/src/libsass/include/sass/context.h +++ /dev/null @@ -1,173 +0,0 @@ -#ifndef SASS_C_CONTEXT_H -#define SASS_C_CONTEXT_H - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; // base struct -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// Create and initialize an option struct -ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); -// Create and initialize a specific context -ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); -ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); -ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Usefull if you only want to query the included files -ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); -ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); -ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); -ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for context from specific implementation -ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - -// Getters for Context_Options from Sass_Context -ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); -ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); -ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - - -// Getters for Context_Option values -ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); -ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); -ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); - -// Setters for Context_Option values -ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); -ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); -ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); -ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); -ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); -ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); -ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); -ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); -ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); -ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); -ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); -ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); -ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); -ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); - - -// Getters for Sass_Context values -ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); -ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for options include path array -ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); - -// Calculate the size of the stored null terminated array -ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); - -// Take ownership of memory (value on context is set to 0) -ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options -ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); -ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// Push function for import extenions -ADDAPI void ADDCALL sass_option_push_import_extension (struct Sass_Options* options, const char* ext); - -// Push function for paths (no manipulation support for now) -ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); - -// Resolve a file via the given include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); -ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); - -// Resolve a file relative to last import or include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/src/libsass/include/sass/functions.h b/src/libsass/include/sass/functions.h deleted file mode 100644 index ac47e8ede..000000000 --- a/src/libsass/include/sass/functions.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef SASS_C_FUNCTIONS_H -#define SASS_C_FUNCTIONS_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declaration -struct Sass_Env; -struct Sass_Callee; -struct Sass_Import; -struct Sass_Options; -struct Sass_Compiler; -struct Sass_Importer; -struct Sass_Function; - -// Typedef helpers for callee lists -typedef struct Sass_Env (*Sass_Env_Frame); -// Typedef helpers for callee lists -typedef struct Sass_Callee (*Sass_Callee_Entry); -// Typedef helpers for import lists -typedef struct Sass_Import (*Sass_Import_Entry); -typedef struct Sass_Import* (*Sass_Import_List); -// Typedef helpers for custom importer lists -typedef struct Sass_Importer (*Sass_Importer_Entry); -typedef struct Sass_Importer* (*Sass_Importer_List); -// Typedef defining importer signature and return type -typedef Sass_Import_List (*Sass_Importer_Fn) - (const char* url, Sass_Importer_Entry cb, struct Sass_Compiler* compiler); - -// Typedef helpers for custom functions lists -typedef struct Sass_Function (*Sass_Function_Entry); -typedef struct Sass_Function* (*Sass_Function_List); -// Typedef defining function signature and return type -typedef union Sass_Value* (*Sass_Function_Fn) - (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); - -// Type of function calls -enum Sass_Callee_Type { - SASS_CALLEE_MIXIN, - SASS_CALLEE_FUNCTION, - SASS_CALLEE_C_FUNCTION, -}; - -// Creator for sass custom importer return argument list -ADDAPI Sass_Importer_List ADDCALL sass_make_importer_list (size_t length); -ADDAPI Sass_Importer_Entry ADDCALL sass_importer_get_list_entry (Sass_Importer_List list, size_t idx); -ADDAPI void ADDCALL sass_importer_set_list_entry (Sass_Importer_List list, size_t idx, Sass_Importer_Entry entry); -ADDAPI void ADDCALL sass_delete_importer_list (Sass_Importer_List list); - - -// Creators for custom importer callback (with some additional pointer) -// The pointer is mostly used to store the callback into the actual binding -ADDAPI Sass_Importer_Entry ADDCALL sass_make_importer (Sass_Importer_Fn importer, double priority, void* cookie); - -// Getters for import function descriptors -ADDAPI Sass_Importer_Fn ADDCALL sass_importer_get_function (Sass_Importer_Entry cb); -ADDAPI double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb); -ADDAPI void* ADDCALL sass_importer_get_cookie (Sass_Importer_Entry cb); - -// Deallocator for associated memory -ADDAPI void ADDCALL sass_delete_importer (Sass_Importer_Entry cb); - -// Creator for sass custom importer return argument list -ADDAPI Sass_Import_List ADDCALL sass_make_import_list (size_t length); -// Creator for a single import entry returned by the custom importer inside the list -ADDAPI Sass_Import_Entry ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); -ADDAPI Sass_Import_Entry ADDCALL sass_make_import (const char* imp_path, const char* abs_base, char* source, char* srcmap); -// set error message to abort import and to print out a message (path from existing object is used in output) -ADDAPI Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); -ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx); - -// Getters for callee entry -ADDAPI const char* ADDCALL sass_callee_get_name (Sass_Callee_Entry); -ADDAPI const char* ADDCALL sass_callee_get_path (Sass_Callee_Entry); -ADDAPI size_t ADDCALL sass_callee_get_line (Sass_Callee_Entry); -ADDAPI size_t ADDCALL sass_callee_get_column (Sass_Callee_Entry); -ADDAPI enum Sass_Callee_Type ADDCALL sass_callee_get_type (Sass_Callee_Entry); -ADDAPI Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry); - -// Getters and Setters for environments (lexical, local and global) -ADDAPI union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); - -// Getters for import entry -ADDAPI const char* ADDCALL sass_import_get_imp_path (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_abs_path (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_source (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_srcmap (Sass_Import_Entry); -// Explicit functions to take ownership of these items -// The property on our struct will be reset to NULL -ADDAPI char* ADDCALL sass_import_take_source (Sass_Import_Entry); -ADDAPI char* ADDCALL sass_import_take_srcmap (Sass_Import_Entry); -// Getters from import error entry -ADDAPI size_t ADDCALL sass_import_get_error_line (Sass_Import_Entry); -ADDAPI size_t ADDCALL sass_import_get_error_column (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_error_message (Sass_Import_Entry); - -// Deallocator for associated memory (incl. entries) -ADDAPI void ADDCALL sass_delete_import_list (Sass_Import_List); -// Just in case we have some stray import structs -ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry); - - - -// Creators for sass function list and function descriptors -ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); -ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); -ADDAPI void ADDCALL sass_delete_function (Sass_Function_Entry entry); -ADDAPI void ADDCALL sass_delete_function_list (Sass_Function_List list); - -// Setters and getters for callbacks on function lists -ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); -ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); - -// Getters for custom function descriptors -ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); -ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); -ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); - - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/src/libsass/include/sass/values.h b/src/libsass/include/sass/values.h deleted file mode 100644 index 9832038b7..000000000 --- a/src/libsass/include/sass/values.h +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef SASS_C_VALUES_H -#define SASS_C_VALUES_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declaration -union Sass_Value; - -// Type for Sass values -enum Sass_Tag { - SASS_BOOLEAN, - SASS_NUMBER, - SASS_COLOR, - SASS_STRING, - SASS_LIST, - SASS_MAP, - SASS_NULL, - SASS_ERROR, - SASS_WARNING -}; - -// Tags for denoting Sass list separators -enum Sass_Separator { - SASS_COMMA, - SASS_SPACE, - // only used internally to represent a hash map before evaluation - // otherwise we would be too early to check for duplicate keys - SASS_HASH -}; - -// Value Operators -enum Sass_OP { - AND, OR, // logical connectives - EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations - ADD, SUB, MUL, DIV, MOD, // arithmetic functions - NUM_OPS // so we know how big to make the op table -}; - -// Creator functions for all value types -ADDAPI union Sass_Value* ADDCALL sass_make_null (void); -ADDAPI union Sass_Value* ADDCALL sass_make_boolean (bool val); -ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); -ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); -ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); -ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); -ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); -ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); -ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg); - -// Generic destructor function for all types -// Will release memory of all associated Sass_Values -// Means we will delete recursively for lists and maps -ADDAPI void ADDCALL sass_delete_value (union Sass_Value* val); - -// Make a deep cloned copy of the given sass value -ADDAPI union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val); - -// Execute an operation for two Sass_Values and return the result as a Sass_Value too -ADDAPI union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); - -// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) -ADDAPI union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); - -// Return the sass tag for a generic sass value -// Check is needed before accessing specific values! -ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v); - -// Check value to be of a specific type -// Can also be used before accessing properties! -ADDAPI bool ADDCALL sass_value_is_null (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_number (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_string (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_boolean (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_color (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_list (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_map (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_error (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_warning (const union Sass_Value* v); - -// Getters and setters for Sass_Number -ADDAPI double ADDCALL sass_number_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_number_set_value (union Sass_Value* v, double value); -ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v); -ADDAPI void ADDCALL sass_number_set_unit (union Sass_Value* v, char* unit); - -// Getters and setters for Sass_String -ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_string_set_value (union Sass_Value* v, char* value); -ADDAPI bool ADDCALL sass_string_is_quoted(const union Sass_Value* v); -ADDAPI void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted); - -// Getters and setters for Sass_Boolean -ADDAPI bool ADDCALL sass_boolean_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_boolean_set_value (union Sass_Value* v, bool value); - -// Getters and setters for Sass_Color -ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r); -ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g); -ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b); -ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a); - -// Getter for the number of items in list -ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); -// Getters and setters for Sass_List -ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v); -ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); -ADDAPI bool ADDCALL sass_list_get_is_bracketed (const union Sass_Value* v); -ADDAPI void ADDCALL sass_list_set_is_bracketed (union Sass_Value* v, bool value); -// Getters and setters for Sass_List values -ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); - -// Getter for the number of items in map -ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v); -// Getters and setters for Sass_Map keys and values -ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); - -// Getters and setters for Sass_Error -ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v); -ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg); - -// Getters and setters for Sass_Warning -ADDAPI char* ADDCALL sass_warning_get_message (const union Sass_Value* v); -ADDAPI void ADDCALL sass_warning_set_message (union Sass_Value* v, char* msg); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/src/libsass/include/sass/version.h b/src/libsass/include/sass/version.h deleted file mode 100644 index 56ea016a2..000000000 --- a/src/libsass/include/sass/version.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SASS_VERSION_H -#define SASS_VERSION_H - -#ifndef LIBSASS_VERSION -#define LIBSASS_VERSION "[NA]" -#endif - -#ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.5" -#endif - -#endif diff --git a/src/libsass/include/sass/version.h.in b/src/libsass/include/sass/version.h.in deleted file mode 100644 index b8d4072d4..000000000 --- a/src/libsass/include/sass/version.h.in +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SASS_VERSION_H -#define SASS_VERSION_H - -#ifndef LIBSASS_VERSION -#define LIBSASS_VERSION "@PACKAGE_VERSION@" -#endif - -#ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.5" -#endif - -#endif diff --git a/src/libsass/include/sass2scss.h b/src/libsass/include/sass2scss.h deleted file mode 100644 index 8736b2cb9..000000000 --- a/src/libsass/include/sass2scss.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifndef SASS2SCSS_H -#define SASS2SCSS_H - -#ifdef _WIN32 - - /* You should define ADD_EXPORTS *only* when building the DLL. */ - #ifdef ADD_EXPORTS - #define ADDAPI __declspec(dllexport) - #define ADDCALL __cdecl - #else - #define ADDAPI - #define ADDCALL - #endif - -#else /* _WIN32 not defined. */ - - /* Define with no value on non-Windows OSes. */ - #define ADDAPI - #define ADDCALL - -#endif - -#ifdef __cplusplus - -#include -#include -#include -#include -#include - -#ifndef SASS2SCSS_VERSION -// Hardcode once the file is copied from -// https://github.com/mgreter/sass2scss -#define SASS2SCSS_VERSION "1.1.1" -#endif - -// add namespace for c++ -namespace Sass -{ - - // pretty print options - const int SASS2SCSS_PRETTIFY_0 = 0; - const int SASS2SCSS_PRETTIFY_1 = 1; - const int SASS2SCSS_PRETTIFY_2 = 2; - const int SASS2SCSS_PRETTIFY_3 = 3; - - // remove one-line comment - const int SASS2SCSS_KEEP_COMMENT = 32; - // remove multi-line comments - const int SASS2SCSS_STRIP_COMMENT = 64; - // convert one-line to multi-line - const int SASS2SCSS_CONVERT_COMMENT = 128; - - // String for finding something interesting - const std::string SASS2SCSS_FIND_WHITESPACE = " \t\n\v\f\r"; - - // converter struct - // holding all states - struct converter - { - // bit options - int options; - // is selector - bool selector; - // concat lists - bool comma; - // has property - bool property; - // has semicolon - bool semicolon; - // comment context - std::string comment; - // flag end of file - bool end_of_file; - // whitespace buffer - std::string whitespace; - // context/block stack - std::stack indents; - }; - - // function only available in c++ code - char* sass2scss (const std::string& sass, const int options); - -} -// EO namespace - -// declare for c -extern "C" { -#endif - - // prettyfy print options - #define SASS2SCSS_PRETTIFY_0 0 - #define SASS2SCSS_PRETTIFY_1 1 - #define SASS2SCSS_PRETTIFY_2 2 - #define SASS2SCSS_PRETTIFY_3 3 - - // keep one-line comments - #define SASS2SCSS_KEEP_COMMENT 32 - // remove multi-line comments - #define SASS2SCSS_STRIP_COMMENT 64 - // convert one-line to multi-line - #define SASS2SCSS_CONVERT_COMMENT 128 - - // available to c and c++ code - ADDAPI char* ADDCALL sass2scss (const char* sass, const int options); - - // Get compiled sass2scss version - ADDAPI const char* ADDCALL sass2scss_version(void); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif \ No newline at end of file diff --git a/src/libsass/m4/.gitkeep b/src/libsass/m4/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 b/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 deleted file mode 100644 index 395b13d2a..000000000 --- a/src/libsass/m4/m4-ax_cxx_compile_stdcxx_11.m4 +++ /dev/null @@ -1,167 +0,0 @@ -# ============================================================================ -# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html -# ============================================================================ -# -# SYNOPSIS -# -# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) -# -# DESCRIPTION -# -# Check for baseline language coverage in the compiler for the C++11 -# standard; if necessary, add switches to CXXFLAGS to enable support. -# -# The first argument, if specified, indicates whether you insist on an -# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. -# -std=c++11). If neither is specified, you get whatever works, with -# preference for an extended mode. -# -# The second argument, if specified 'mandatory' or if left unspecified, -# indicates that baseline C++11 support is required and that the macro -# should error out if no mode with that support is found. If specified -# 'optional', then configuration proceeds regardless, after defining -# HAVE_CXX11 if and only if a supporting mode is found. -# -# LICENSE -# -# Copyright (c) 2008 Benjamin Kosnik -# Copyright (c) 2012 Zack Weinberg -# Copyright (c) 2013 Roy Stogner -# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 11 - -m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ - template - struct check - { - static_assert(sizeof(int) <= sizeof(T), "not big enough"); - }; - - struct Base { - virtual void f() {} - }; - struct Child : public Base { - virtual void f() override {} - }; - - typedef check> right_angle_brackets; - - int a; - decltype(a) b; - - typedef check check_type; - check_type c; - check_type&& cr = static_cast(c); - - auto d = a; - auto l = [](){}; - // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] - struct use_l { use_l() { l(); } }; - - // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae - // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this - namespace test_template_alias_sfinae { - struct foo {}; - - template - using member = typename T::member_type; - - template - void func(...) {} - - template - void func(member*) {} - - void test(); - - void test() { - func(0); - } - } -]]) - -AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl - m4_if([$1], [], [], - [$1], [ext], [], - [$1], [noext], [], - [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl - m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], - [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], - [$2], [optional], [ax_cxx_compile_cxx11_required=false], - [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) - AC_LANG_PUSH([C++])dnl - ac_success=no - AC_CACHE_CHECK(whether $CXX supports C++11 features by default, - ax_cv_cxx_compile_cxx11, - [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], - [ax_cv_cxx_compile_cxx11=yes], - [ax_cv_cxx_compile_cxx11=no])]) - if test x$ax_cv_cxx_compile_cxx11 = xyes; then - ac_success=yes - fi - - m4_if([$1], [noext], [], [dnl - if test x$ac_success = xno; then - for switch in -std=gnu++11 -std=gnu++0x; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, - $cachevar, - [ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXXFLAGS="$ac_save_CXXFLAGS"]) - if eval test x\$$cachevar = xyes; then - CXXFLAGS="$CXXFLAGS $switch" - ac_success=yes - break - fi - done - fi]) - - m4_if([$1], [ext], [], [dnl - if test x$ac_success = xno; then - dnl HP's aCC needs +std=c++11 according to: - dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf - for switch in -std=c++11 -std=c++0x +std=c++11; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, - $cachevar, - [ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXXFLAGS="$ac_save_CXXFLAGS"]) - if eval test x\$$cachevar = xyes; then - CXXFLAGS="$CXXFLAGS $switch" - ac_success=yes - break - fi - done - fi]) - AC_LANG_POP([C++]) - if test x$ax_cxx_compile_cxx11_required = xtrue; then - if test x$ac_success = xno; then - AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) - fi - else - if test x$ac_success = xno; then - HAVE_CXX11=0 - AC_MSG_NOTICE([No compiler with C++11 support was found]) - else - HAVE_CXX11=1 - AC_DEFINE(HAVE_CXX11,1, - [define if the compiler supports basic C++11 syntax]) - fi - - AC_SUBST(HAVE_CXX11) - fi -]) diff --git a/src/libsass/res/resource.rc b/src/libsass/res/resource.rc deleted file mode 100644 index fc49e6a87..000000000 --- a/src/libsass/res/resource.rc +++ /dev/null @@ -1,35 +0,0 @@ -#include - -// DLL version information. -VS_VERSION_INFO VERSIONINFO -FILEVERSION 1,0,0,0 -PRODUCTVERSION 1,0,0,0 -FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE -#else - FILEFLAGS 0 -#endif -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_DLL -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "080904b0" - BEGIN - VALUE "CompanyName", "Sass Open Source Foundation" - VALUE "FileDescription", "A C/C++ implementation of a Sass compiler" - VALUE "FileVersion", "1.0.0.0" - VALUE "InternalName", "libsass" - VALUE "LegalCopyright", "\251 2017 libsass.org" - VALUE "OriginalFilename", "libsass.dll" - VALUE "ProductName", "LibSass Library" - VALUE "ProductVersion", "1.0.0.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x809, 1200 - END -END diff --git a/src/libsass/script/bootstrap b/src/libsass/script/bootstrap deleted file mode 100755 index ab82fac94..000000000 --- a/src/libsass/script/bootstrap +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -script/branding - -: ${SASS_SPEC_PATH:="sass-spec"} -: ${SASS_SASSC_PATH:="sassc" } - -if [ ! -d $SASS_SPEC_PATH ]; then - git clone https://github.com/sass/sass-spec.git $SASS_SPEC_PATH -fi -if [ ! -d $SASS_SASSC_PATH ]; then - git clone https://github.com/sass/sassc.git $SASS_SASSC_PATH -fi diff --git a/src/libsass/script/branding b/src/libsass/script/branding deleted file mode 100755 index cd8cb2a52..000000000 --- a/src/libsass/script/branding +++ /dev/null @@ -1,10 +0,0 @@ -#! /bin/bash - -echo " " -echo " _ ___ ____ ____ _ ____ ____ " -echo "| | |_ _| __ ) ___| / \ / ___/ ___| " -echo "| | | || _ \___ \ / _ \ \___ \___ \ " -echo "| |___ | || |_) |__) / ___ \ ___) |__) |" -echo "|_____|___|____/____/_/ \_\____/____/ " -echo " " - diff --git a/src/libsass/script/ci-build-libsass b/src/libsass/script/ci-build-libsass deleted file mode 100755 index 40ea22ff7..000000000 --- a/src/libsass/script/ci-build-libsass +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/bash - -set -e - -script/bootstrap - -# export this path right here (was in script/spec before) -export SASS_LIBSASS_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../ && pwd )" - -# use some defaults if not running under travis ci -if [ "x$CONTINUOUS_INTEGRATION" == "x" ]; then export CONTINUOUS_INTEGRATION=true; fi -if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then export TRAVIS_BUILD_DIR=$(pwd); fi -if [ "x$SASS_SASSC_PATH" == "x" ]; then export SASS_SASSC_PATH=$(pwd)/sassc; fi -if [ "x$SASS_SPEC_PATH" == "x" ]; then export SASS_SPEC_PATH=$(pwd)/sass-spec; fi - -# try to get the os name from uname (and filter via perl - probably not the most portable way?) -if [ "x$TRAVIS_OS_NAME" == "x" ]; then export TRAVIS_OS_NAME=`uname -s | perl -ne 'print lc \$1 if\(/^([a-zA-Z]+)/'\)`; fi - -if [ "x$COVERAGE" == "xyes" ]; then - COVERAGE_OPT="--enable-coverage" - export EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" - export EXTRA_CXXFLAGS="-fprofile-arcs -ftest-coverage" - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - # osx doesn't seem to know gcov lib? - export EXTRA_LDFLAGS="--coverage" - else - export EXTRA_LDFLAGS="-lgcov --coverage" - fi -else - COVERAGE_OPT="--disable-coverage" -fi - -if [ "x$BUILD" == "xstatic" ]; then - SHARED_OPT="--disable-shared --enable-static" - MAKE_TARGET="static" -else - # Makefile of sassc wants to link to static - SHARED_OPT="--enable-shared --enable-static" - MAKE_TARGET="shared" -fi - -if [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then - MAKE_OPTS="$MAKE_OPTS -j1 V=1" -else - MAKE_OPTS="$MAKE_OPTS -j5 V=1" -fi - -if [ "x$PREFIX" == "x" ]; then - if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then - PREFIX=$SASS_LIBSASS_PATH/build - else - PREFIX=$TRAVIS_BUILD_DIR/build - fi -fi - -# enable address sanitation -# https://en.wikipedia.org/wiki/AddressSanitizer -if [ "x$CC" == "xclang" ]; then - if [ "x$COVERAGE" != "xyes" ]; then - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - export EXTRA_CFLAGS="$EXTRA_CFLAGS -fsanitize=address" - export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -fsanitize=address" - export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fsanitize=address" - fi - fi -fi - -echo SASS_LIBSASS_PATH: $SASS_LIBSASS_PATH -echo TRAVIS_BUILD_DIR: $TRAVIS_BUILD_DIR -echo SASS_SASSC_PATH: $SASS_SASSC_PATH -echo SASS_SPEC_PATH: $SASS_SPEC_PATH -echo INSTALL_LOCATION: $PREFIX - -if [ "x$AUTOTOOLS" == "xyes" ]; then - - echo -en 'travis_fold:start:configure\r' - autoreconf --force --install - ./configure --enable-tests $COVERAGE_OPT \ - --disable-silent-rules \ - --with-sassc-dir=$SASS_SASSC_PATH \ - --with-sass-spec-dir=$SASS_SPEC_PATH \ - --prefix=$PREFIX \ - ${SHARED_OPT} - echo -en 'travis_fold:end:configure\r' - - make $MAKE_OPTS clean - - # install to prefix directory - PREFIX="$PREFIX" make $MAKE_OPTS install - -else - - make $MAKE_OPTS clean - -fi - -# install to prefix directory -PREFIX="$PREFIX" make $MAKE_OPTS install - -ls -la $PREFIX/* - -echo successfully compiled libsass -echo AUTOTOOLS=$AUTOTOOLS COVERAGE=$COVERAGE BUILD=$BUILD - -if [ "$CONTINUOUS_INTEGRATION" == "true" ] && [ "$TRAVIS_PULL_REQUEST" != "false" ] && [ "x$TRAVIS_PULL_REQUEST" != "x" ] && - ([ "$TRAVIS_OS_NAME" == "linux" ] || [ "$TRAVIS_OS_NAME" == "osx" ] || [ "$TRAVIS_OS_NAME" == "cygwin" ]); -then - - echo "Fetching PR $TRAVIS_PULL_REQUEST" - - JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) - - if [[ $JSON =~ "API rate limit exceeded" ]]; - then - echo "Travis rate limit on github exceeded" - echo "Retrying via 'special purpose proxy'" - JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) - fi - - RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" - - if [[ $JSON =~ $RE_SPEC_PR ]]; - then - SPEC_PR="${BASH_REMATCH[2]}" - echo "Fetching Sass Spec PR $SPEC_PR" - git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR - git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR - LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe - else - LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe - fi -else - LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe -fi diff --git a/src/libsass/script/ci-build-plugin b/src/libsass/script/ci-build-plugin deleted file mode 100755 index 3660c6224..000000000 --- a/src/libsass/script/ci-build-plugin +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -PLUGIN=$1 -RUBY_BIN=ruby -SASS_SPEC_PATH=sass-spec -SASSC_BIN=sassc/bin/sassc -SASS_SPEC_SPEC_DIR=plugins/libsass-${PLUGIN}/test - -if [ -e ./tester ] ; then - SASSC_BIN=./tester -fi - -if [ -d ./build/lib ] ; then - cp -a build/lib lib -fi - -if [ "x$1" == "x" ] ; then - echo "No plugin name given" - exit 1 -fi - -if [ "x$COVERAGE" == "0" ] ; then - unset COVERAGE -fi - -export EXTRA_CFLAGS="" -export EXTRA_CXXFLAGS="" -if [ "$TRAVIS_OS_NAME" == "osx" ]; then - # osx doesn't seem to know gcov lib? - export EXTRA_LDFLAGS="--coverage" -else - export EXTRA_LDFLAGS="-lgcov --coverage" -fi - -mkdir -p plugins -if [ ! -d plugins/libsass-${PLUGIN} ] ; then - git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} -fi -if [ ! -d plugins/libsass-${PLUGIN}/build ] ; then - mkdir plugins/libsass-${PLUGIN}/build -fi -RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi - -cd plugins/libsass-${PLUGIN}/build -cmake -G "Unix Makefiles" -D LIBSASS_DIR="../../.." .. -RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi -make VERBOSE=1 -j2 -RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi -cd ../../.. - -# glob only works on paths relative to imports -if [ "x$PLUGIN" == "xglob" ]; then - ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss > ${SASS_SPEC_SPEC_DIR}/basic/result.css - ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss --sourcemap > /dev/null -else - cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic > ${SASS_SPEC_SPEC_DIR}/basic/result.css - cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic --sourcemap > /dev/null -fi -RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi - -diff ${SASS_SPEC_SPEC_DIR}/basic/expected_output.css ${SASS_SPEC_SPEC_DIR}/basic/result.css -RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi diff --git a/src/libsass/script/ci-install-compiler b/src/libsass/script/ci-install-compiler deleted file mode 100755 index 3a68b3a39..000000000 --- a/src/libsass/script/ci-install-compiler +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -gem install minitest -gem install minitap - -pip2 install --user 'requests[security]' diff --git a/src/libsass/script/ci-install-deps b/src/libsass/script/ci-install-deps deleted file mode 100755 index 27b485aa7..000000000 --- a/src/libsass/script/ci-install-deps +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -if [ "x$COVERAGE" == "xyes" ]; then - pip2 install --user gcovr - pip2 install --user cpp-coveralls -else - echo "no dependencies to install" -fi - -if [ "x$AUTOTOOLS" == "xyes" ]; then - AUTOTOOLS=yes - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - sudo add-apt-repository -y ppa:rbose-debianizer/automake &> /dev/null - sudo apt-get -qq update - sudo apt-get -qq install automake - fi - -fi - -exit 0 diff --git a/src/libsass/script/ci-report-coverage b/src/libsass/script/ci-report-coverage deleted file mode 100755 index 495cb05cb..000000000 --- a/src/libsass/script/ci-report-coverage +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -if [ "x$COVERAGE" = "xyes" ]; then - - # find / -name "gcovr" - # find / -name "coveralls" - # this is only needed for mac os x builds! - PATH=$PATH:/Users/travis/Library/Python/2.7/bin/ - - - # exclude some directories from profiling (.libs is from autotools) - export EXCLUDE_COVERAGE="--exclude plugins - --exclude sassc/sassc.c - --exclude src/sass-spec - --exclude src/.libs - --exclude src/debug.hpp - --exclude src/json.cpp - --exclude src/json.hpp - --exclude src/cencode.c - --exclude src/b64 - --exclude src/utf8 - --exclude src/utf8_string.hpp - --exclude src/utf8.h - --exclude src/utf8_string.cpp - --exclude src/sass2scss.h - --exclude src/sass2scss.cpp - --exclude src/test - --exclude src/posix - --exclude src/debugger.hpp" - # debug used gcov version - # option not available on mac - if [ "$TRAVIS_OS_NAME" != "osx" ]; then - gcov -v - fi - # create summarized report - gcovr -r . - # submit report to coveralls.io - coveralls $EXCLUDE_COVERAGE --gcov-options '\-lp' - -else - echo "skip coverage reporting" -fi diff --git a/src/libsass/script/spec b/src/libsass/script/spec deleted file mode 100755 index d0b864a13..000000000 --- a/src/libsass/script/spec +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -script/bootstrap - -make $MAKE_OPTS test_build diff --git a/src/libsass/script/tap-driver b/src/libsass/script/tap-driver deleted file mode 100755 index ed8a9a9dd..000000000 --- a/src/libsass/script/tap-driver +++ /dev/null @@ -1,652 +0,0 @@ -#!/usr/bin/env sh -# Copyright (C) 2011-2013 Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# This file is maintained in Automake, please report -# bugs to or send patches to -# . - -scriptversion=2011-12-27.17; # UTC - -# Make unconditional expansion of undefined variables an error. This -# helps a lot in preventing typo-related bugs. -set -u - -me=tap-driver.sh - -fatal () -{ - echo "$me: fatal: $*" >&2 - exit 1 -} - -usage_error () -{ - echo "$me: $*" >&2 - print_usage >&2 - exit 2 -} - -print_usage () -{ - cat < - # - trap : 1 3 2 13 15 - if test $merge -gt 0; then - exec 2>&1 - else - exec 2>&3 - fi - "$@" - echo $? - ) | LC_ALL=C ${AM_TAP_AWK-awk} \ - -v me="$me" \ - -v test_script_name="$test_name" \ - -v log_file="$log_file" \ - -v trs_file="$trs_file" \ - -v expect_failure="$expect_failure" \ - -v merge="$merge" \ - -v ignore_exit="$ignore_exit" \ - -v comments="$comments" \ - -v diag_string="$diag_string" \ -' -# FIXME: the usages of "cat >&3" below could be optimized when using -# FIXME: GNU awk, and/on on systems that supports /dev/fd/. - -# Implementation note: in what follows, `result_obj` will be an -# associative array that (partly) simulates a TAP result object -# from the `TAP::Parser` perl module. - -## ----------- ## -## FUNCTIONS ## -## ----------- ## - -function fatal(msg) -{ - print me ": " msg | "cat >&2" - exit 1 -} - -function abort(where) -{ - fatal("internal error " where) -} - -# Convert a boolean to a "yes"/"no" string. -function yn(bool) -{ - return bool ? "yes" : "no"; -} - -function add_test_result(result) -{ - if (!test_results_index) - test_results_index = 0 - test_results_list[test_results_index] = result - test_results_index += 1 - test_results_seen[result] = 1; -} - -# Whether the test script should be re-run by "make recheck". -function must_recheck() -{ - for (k in test_results_seen) - if (k != "XFAIL" && k != "PASS" && k != "SKIP") - return 1 - return 0 -} - -# Whether the content of the log file associated to this test should -# be copied into the "global" test-suite.log. -function copy_in_global_log() -{ - for (k in test_results_seen) - if (k != "PASS") - return 1 - return 0 -} - -# FIXME: this can certainly be improved ... -function get_global_test_result() -{ - if ("ERROR" in test_results_seen) - return "ERROR" - if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) - return "FAIL" - all_skipped = 1 - for (k in test_results_seen) - if (k != "SKIP") - all_skipped = 0 - if (all_skipped) - return "SKIP" - return "PASS"; -} - -function stringify_result_obj(result_obj) -{ - if (result_obj["is_unplanned"] || result_obj["number"] != testno) - return "ERROR" - - if (plan_seen == LATE_PLAN) - return "ERROR" - - if (result_obj["directive"] == "TODO") - return result_obj["is_ok"] ? "XPASS" : "XFAIL" - - if (result_obj["directive"] == "SKIP") - return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; - - if (length(result_obj["directive"])) - abort("in function stringify_result_obj()") - - return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL -} - -function decorate_result(result) -{ - color_name = color_for_result[result] - if (color_name) - return color_map[color_name] "" result "" color_map["std"] - # If we are not using colorized output, or if we do not know how - # to colorize the given result, we should return it unchanged. - return result -} - -function report(result, details) -{ - if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) - { - msg = ": " test_script_name - add_test_result(result) - } - else if (result == "#") - { - msg = " " test_script_name ":" - } - else - { - abort("in function report()") - } - if (length(details)) - msg = msg " " details - # Output on console might be colorized. - print decorate_result(result) msg - # Log the result in the log file too, to help debugging (this is - # especially true when said result is a TAP error or "Bail out!"). - print result msg | "cat >&3"; -} - -function testsuite_error(error_message) -{ - report("ERROR", "- " error_message) -} - -function handle_tap_result() -{ - details = result_obj["number"]; - if (length(result_obj["description"])) - details = details " " result_obj["description"] - - if (plan_seen == LATE_PLAN) - { - details = details " # AFTER LATE PLAN"; - } - else if (result_obj["is_unplanned"]) - { - details = details " # UNPLANNED"; - } - else if (result_obj["number"] != testno) - { - details = sprintf("%s # OUT-OF-ORDER (expecting %d)", - details, testno); - } - else if (result_obj["directive"]) - { - details = details " # " result_obj["directive"]; - if (length(result_obj["explanation"])) - details = details " " result_obj["explanation"] - } - - report(stringify_result_obj(result_obj), details) -} - -# `skip_reason` should be empty whenever planned > 0. -function handle_tap_plan(planned, skip_reason) -{ - planned += 0 # Avoid getting confused if, say, `planned` is "00" - if (length(skip_reason) && planned > 0) - abort("in function handle_tap_plan()") - if (plan_seen) - { - # Error, only one plan per stream is acceptable. - testsuite_error("multiple test plans") - return; - } - planned_tests = planned - # The TAP plan can come before or after *all* the TAP results; we speak - # respectively of an "early" or a "late" plan. If we see the plan line - # after at least one TAP result has been seen, assume we have a late - # plan; in this case, any further test result seen after the plan will - # be flagged as an error. - plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) - # If testno > 0, we have an error ("too many tests run") that will be - # automatically dealt with later, so do not worry about it here. If - # $plan_seen is true, we have an error due to a repeated plan, and that - # has already been dealt with above. Otherwise, we have a valid "plan - # with SKIP" specification, and should report it as a particular kind - # of SKIP result. - if (planned == 0 && testno == 0) - { - if (length(skip_reason)) - skip_reason = "- " skip_reason; - report("SKIP", skip_reason); - } -} - -function extract_tap_comment(line) -{ - if (index(line, diag_string) == 1) - { - # Strip leading `diag_string` from `line`. - line = substr(line, length(diag_string) + 1) - # And strip any leading and trailing whitespace left. - sub("^[ \t]*", "", line) - sub("[ \t]*$", "", line) - # Return what is left (if any). - return line; - } - return ""; -} - -# When this function is called, we know that line is a TAP result line, -# so that it matches the (perl) RE "^(not )?ok\b". -function setup_result_obj(line) -{ - # Get the result, and remove it from the line. - result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) - sub("^(not )?ok[ \t]*", "", line) - - # If the result has an explicit number, get it and strip it; otherwise, - # automatically assing the next progresive number to it. - if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) - { - match(line, "^[0-9]+") - # The final `+ 0` is to normalize numbers with leading zeros. - result_obj["number"] = substr(line, 1, RLENGTH) + 0 - line = substr(line, RLENGTH + 1) - } - else - { - result_obj["number"] = testno - } - - if (plan_seen == LATE_PLAN) - # No further test results are acceptable after a "late" TAP plan - # has been seen. - result_obj["is_unplanned"] = 1 - else if (plan_seen && testno > planned_tests) - result_obj["is_unplanned"] = 1 - else - result_obj["is_unplanned"] = 0 - - # Strip trailing and leading whitespace. - sub("^[ \t]*", "", line) - sub("[ \t]*$", "", line) - - # This will have to be corrected if we have a "TODO"/"SKIP" directive. - result_obj["description"] = line - result_obj["directive"] = "" - result_obj["explanation"] = "" - - if (index(line, "#") == 0) - return # No possible directive, nothing more to do. - - # Directives are case-insensitive. - rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" - - # See whether we have the directive, and if yes, where. - pos = match(line, rx "$") - if (!pos) - pos = match(line, rx "[^a-zA-Z0-9_]") - - # If there was no TAP directive, we have nothing more to do. - if (!pos) - return - - # Let`s now see if the TAP directive has been escaped. For example: - # escaped: ok \# SKIP - # not escaped: ok \\# SKIP - # escaped: ok \\\\\# SKIP - # not escaped: ok \ # SKIP - if (substr(line, pos, 1) == "#") - { - bslash_count = 0 - for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) - bslash_count += 1 - if (bslash_count % 2) - return # Directive was escaped. - } - - # Strip the directive and its explanation (if any) from the test - # description. - result_obj["description"] = substr(line, 1, pos - 1) - # Now remove the test description from the line, that has been dealt - # with already. - line = substr(line, pos) - # Strip the directive, and save its value (normalized to upper case). - sub("^[ \t]*#[ \t]*", "", line) - result_obj["directive"] = toupper(substr(line, 1, 4)) - line = substr(line, 5) - # Now get the explanation for the directive (if any), with leading - # and trailing whitespace removed. - sub("^[ \t]*", "", line) - sub("[ \t]*$", "", line) - result_obj["explanation"] = line -} - -function get_test_exit_message(status) -{ - if (status == 0) - return "" - if (status !~ /^[1-9][0-9]*$/) - abort("getting exit status") - if (status < 127) - exit_details = "" - else if (status == 127) - exit_details = " (command not found?)" - else if (status >= 128 && status <= 255) - exit_details = sprintf(" (terminated by signal %d?)", status - 128) - else if (status > 256 && status <= 384) - # We used to report an "abnormal termination" here, but some Korn - # shells, when a child process die due to signal number n, can leave - # in $? an exit status of 256+n instead of the more standard 128+n. - # Apparently, both behaviours are allowed by POSIX (2008), so be - # prepared to handle them both. See also Austing Group report ID - # 0000051 - exit_details = sprintf(" (terminated by signal %d?)", status - 256) - else - # Never seen in practice. - exit_details = " (abnormal termination)" - return sprintf("exited with status %d%s", status, exit_details) -} - -function write_test_results() -{ - print ":global-test-result: " get_global_test_result() > trs_file - print ":recheck: " yn(must_recheck()) > trs_file - print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file - for (i = 0; i < test_results_index; i += 1) - print ":test-result: " test_results_list[i] > trs_file - close(trs_file); -} - -BEGIN { - -## ------- ## -## SETUP ## -## ------- ## - -'"$init_colors"' - -# Properly initialized once the TAP plan is seen. -planned_tests = 0 - -COOKED_PASS = expect_failure ? "XPASS": "PASS"; -COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; - -# Enumeration-like constants to remember which kind of plan (if any) -# has been seen. It is important that NO_PLAN evaluates "false" as -# a boolean. -NO_PLAN = 0 -EARLY_PLAN = 1 -LATE_PLAN = 2 - -testno = 0 # Number of test results seen so far. -bailed_out = 0 # Whether a "Bail out!" directive has been seen. - -# Whether the TAP plan has been seen or not, and if yes, which kind -# it is ("early" is seen before any test result, "late" otherwise). -plan_seen = NO_PLAN - -## --------- ## -## PARSING ## -## --------- ## - -is_first_read = 1 - -while (1) - { - # Involutions required so that we are able to read the exit status - # from the last input line. - st = getline - if (st < 0) # I/O error. - fatal("I/O error while reading from input stream") - else if (st == 0) # End-of-input - { - if (is_first_read) - abort("in input loop: only one input line") - break - } - if (is_first_read) - { - is_first_read = 0 - nextline = $0 - continue - } - else - { - curline = nextline - nextline = $0 - $0 = curline - } - # Copy any input line verbatim into the log file. - print | "cat >&3" - # Parsing of TAP input should stop after a "Bail out!" directive. - if (bailed_out) - continue - - # TAP test result. - if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) - { - testno += 1 - setup_result_obj($0) - handle_tap_result() - } - # TAP plan (normal or "SKIP" without explanation). - else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) - { - # The next two lines will put the number of planned tests in $0. - sub("^1\\.\\.", "") - sub("[^0-9]*$", "") - handle_tap_plan($0, "") - continue - } - # TAP "SKIP" plan, with an explanation. - else if ($0 ~ /^1\.\.0+[ \t]*#/) - { - # The next lines will put the skip explanation in $0, stripping - # any leading and trailing whitespace. This is a little more - # tricky in truth, since we want to also strip a potential leading - # "SKIP" string from the message. - sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") - sub("[ \t]*$", ""); - handle_tap_plan(0, $0) - } - # "Bail out!" magic. - # Older versions of prove and TAP::Harness (e.g., 3.17) did not - # recognize a "Bail out!" directive when preceded by leading - # whitespace, but more modern versions (e.g., 3.23) do. So we - # emulate the latter, "more modern" behaviour. - else if ($0 ~ /^[ \t]*Bail out!/) - { - bailed_out = 1 - # Get the bailout message (if any), with leading and trailing - # whitespace stripped. The message remains stored in `$0`. - sub("^[ \t]*Bail out![ \t]*", ""); - sub("[ \t]*$", ""); - # Format the error message for the - bailout_message = "Bail out!" - if (length($0)) - bailout_message = bailout_message " " $0 - testsuite_error(bailout_message) - } - # Maybe we have too look for dianogtic comments too. - else if (comments != 0) - { - comment = extract_tap_comment($0); - if (length(comment)) - report("#", comment); - } - } - -## -------- ## -## FINISH ## -## -------- ## - -# A "Bail out!" directive should cause us to ignore any following TAP -# error, as well as a non-zero exit status from the TAP producer. -if (!bailed_out) - { - if (!plan_seen) - { - testsuite_error("missing test plan") - } - else if (planned_tests != testno) - { - bad_amount = testno > planned_tests ? "many" : "few" - testsuite_error(sprintf("too %s tests run (expected %d, got %d)", - bad_amount, planned_tests, testno)) - } - if (!ignore_exit) - { - # Fetch exit status from the last line. - exit_message = get_test_exit_message(nextline) - if (exit_message) - testsuite_error(exit_message) - } - } - -write_test_results() - -exit 0 - -} # End of "BEGIN" block. -' - -# TODO: document that we consume the file descriptor 3 :-( -} 3>"$log_file" - -test $? -eq 0 || fatal "I/O or internal error" - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: diff --git a/src/libsass/script/tap-runner b/src/libsass/script/tap-runner deleted file mode 100755 index 56c13bfb4..000000000 --- a/src/libsass/script/tap-runner +++ /dev/null @@ -1 +0,0 @@ -$@ $TEST_FLAGS --tap --silent | tapout tap diff --git a/src/libsass/script/test-leaks.pl b/src/libsass/script/test-leaks.pl deleted file mode 100755 index bfb8653f4..000000000 --- a/src/libsass/script/test-leaks.pl +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/perl -############################################################ -# this perl script is meant for developers only! -# it will run all spec-tests (without verifying the -# results) via valgrind to detect possible leaks. -# expect that it takes 1h or more to finish! -############################################################ -# Prerequisite install: `cpan Parallel::Runner` -# You may also need to install `cpan File::Find` -# You may also need to install `cpan IPC::Run3` -############################################################ -# usage: `perl test-leaks.pl [threads]` -# example: `time perl test-leaks.pl 4` -############################################################ -# leaks will be reported in "mem-leaks.log" -############################################################ - -use strict; -use warnings; - -############################################################ -# configurations (you may adjust) -############################################################ - -# number of threads to use -my $threads = $ARGV[0] || 8; - -# the github repositories to checkout -# if you need other branch, clone manually! -my $sassc = "https://www.github.com/sass/sassc"; -my $specs = "https://www.github.com/sass/sass-spec"; - -############################################################ -# load modules -############################################################ - -use IPC::Run3; -use IO::Handle; -use Fcntl qw(:flock); -use File::Find::Rule; -use Parallel::Runner; -use List::Util qw(shuffle); - -############################################################ -# check prerequisites -############################################################ - -unless (-d "../sassc") { - warn "sassc folder not found\n"; - warn "trying to checkout via git\n"; - system("git", "clone", $sassc, "../sassc"); - die "git command did not exit gracefully" if $?; -} - -unless (-d "../sass-spec") { - warn "sass-spec folder not found\n"; - warn "trying to checkout via git\n"; - system("git", "clone", $specs, "../sass-spec"); - die "git command did not exit gracefully" if $?; -} - -unless (-f "../sassc/bin/sassc") { - warn "sassc executable not found\n"; - warn "trying to compile via make\n"; - system("make", "-C", "../sassc", "-j", $threads); - die "make command did not exit gracefully" if $?; -} - -############################################################ -# main runner code -############################################################ - -my $root = "../sass-spec/spec"; -my @files = File::Find::Rule->file() - ->name('input.scss')->in($root); - -open(my $leaks, ">", "mem-leaks.log"); -die "Cannot open log" unless $leaks; -my $runner = Parallel::Runner->new($threads); -die "Cannot start runner" unless $runner; - -print "##########################\n"; -print "Testing $#files spec files\n"; -print "##########################\n"; - -foreach my $file (shuffle @files) { - $runner->run(sub { - $| = 1; select STDOUT; - my $cmd = sprintf('../sassc/bin/sassc %s', $file); - my $check = sprintf('valgrind --leak-check=yes %s', $cmd); - run3($check, undef, \ my $out, \ my $err); - if ($err =~ m/in use at exit: 0 bytes in 0 blocks/) { - print "."; # print success indicator - } else { - print "F"; # print error indicator - flock($leaks, LOCK_EX) or die "Cannot lock log"; - $leaks->printflush("#" x 80, "\n", $err, "\n"); - flock($leaks, LOCK_UN) or die "Cannot unlock log"; - } - }); -} - -$runner->finish; diff --git a/src/libsass/src/GNUmakefile.am b/src/libsass/src/GNUmakefile.am deleted file mode 100644 index fee9312f9..000000000 --- a/src/libsass/src/GNUmakefile.am +++ /dev/null @@ -1,54 +0,0 @@ -ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4 -I script - -AM_COPT = -Wall -O2 -AM_COVLDFLAGS = - -if ENABLE_COVERAGE - AM_COPT = -O0 --coverage - AM_COVLDFLAGS += -lgcov -endif - -AM_CPPFLAGS = -I$(top_srcdir)/include -AM_CFLAGS = $(AM_COPT) -AM_CXXFLAGS = $(AM_COPT) -AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) - -if COMPILER_IS_MINGW32 - AM_CXXFLAGS += -std=gnu++0x -else - AM_CXXFLAGS += -std=c++0x -endif - -EXTRA_DIST = \ - COPYING \ - INSTALL \ - LICENSE \ - Readme.md - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = support/libsass.pc - -lib_LTLIBRARIES = libsass.la - -include $(top_srcdir)/Makefile.conf - -libsass_la_SOURCES = ${CSOURCES} ${SOURCES} - -libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 1:0:0 - -if ENABLE_TESTS -if ENABLE_COVERAGE -nodist_EXTRA_libsass_la_SOURCES = non-existent-file-to-force-CXX-linking.cxx -endif -endif - -include_HEADERS = $(top_srcdir)/include/sass.h \ - $(top_srcdir)/include/sass2scss.h - -sass_includedir = $(includedir)/sass - -sass_include_HEADERS = $(top_srcdir)/include/sass/base.h \ - $(top_srcdir)/include/sass/values.h \ - $(top_srcdir)/include/sass/version.h \ - $(top_srcdir)/include/sass/context.h \ - $(top_srcdir)/include/sass/functions.h diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp deleted file mode 100644 index c3b38efb9..000000000 --- a/src/libsass/src/ast.cpp +++ /dev/null @@ -1,2226 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace Sass { - - static Null sass_null(ParserState("null")); - - bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - if (selector_) { - if (selector_->find(f)) return true; - } - // execute last - return f(this); - } - - bool Selector_List::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - for (Complex_Selector_Obj sel : elements()) { - if (sel->find(f)) return true; - } - // execute last - return f(this); - } - - bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - for (Simple_Selector_Obj sel : elements()) { - if (sel->find(f)) return true; - } - // execute last - return f(this); - } - - bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - if (head_ && head_->find(f)) return true; - if (tail_ && tail_->find(f)) return true; - // execute last - return f(this); - } - - bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { - if (Supports_Operator_Obj op = Cast(cond)) { - return op->operand() != operand(); - } - return Cast(cond) != NULL; - } - - bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const { - return Cast(cond) || - Cast(cond); - } - - void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v") - { - str.erase( str.find_last_not_of( delimiters ) + 1 ); - } - - void String_Constant::rtrim() - { - str_rtrim(value_); - } - - void String_Schema::rtrim() - { - if (!empty()) { - if (String_Ptr str = Cast(last())) str->rtrim(); - } - } - - void Argument::set_delayed(bool delayed) - { - if (value_) value_->set_delayed(delayed); - is_delayed(delayed); - } - - void Arguments::set_delayed(bool delayed) - { - for (Argument_Obj arg : elements()) { - if (arg) arg->set_delayed(delayed); - } - is_delayed(delayed); - } - - - bool At_Root_Query::exclude(std::string str) - { - bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; - List_Ptr l = static_cast(value().ptr()); - std::string v; - - if (with) - { - if (!l || l->length() == 0) return str.compare("rule") != 0; - for (size_t i = 0, L = l->length(); i < L; ++i) - { - v = unquote((*l)[i]->to_string()); - if (v.compare("all") == 0 || v == str) return false; - } - return true; - } - else - { - if (!l || !l->length()) return str.compare("rule") == 0; - for (size_t i = 0, L = l->length(); i < L; ++i) - { - v = unquote((*l)[i]->to_string()); - if (v.compare("all") == 0 || v == str) return true; - } - return false; - } - } - - void AST_Node::update_pstate(const ParserState& pstate) - { - pstate_.offset += pstate - pstate_ + pstate.offset; - } - - bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const - { - // https://github.com/sass/sass/issues/2229 - if ((has_ns_ == r.has_ns_) || - (has_ns_ && ns_.empty()) || - (r.has_ns_ && r.ns_.empty()) - ) { - if (ns_.empty() && r.ns() == "*") return false; - else if (r.ns().empty() && ns() == "*") return false; - else return ns() == r.ns(); - } - return false; - } - - bool Compound_Selector::operator< (const Compound_Selector& rhs) const - { - size_t L = std::min(length(), rhs.length()); - for (size_t i = 0; i < L; ++i) - { - Simple_Selector_Obj l = (*this)[i]; - Simple_Selector_Obj r = rhs[i]; - if (!l && !r) return false; - else if (!r) return false; - else if (!l) return true; - else if (*l != *r) - { return *l < *r; } - } - // just compare the length now - return length() < rhs.length(); - } - - bool Compound_Selector::has_parent_ref() const - { - for (Simple_Selector_Obj s : *this) { - if (s && s->has_parent_ref()) return true; - } - return false; - } - - bool Compound_Selector::has_real_parent_ref() const - { - for (Simple_Selector_Obj s : *this) { - if (s && s->has_real_parent_ref()) return true; - } - return false; - } - - bool Complex_Selector::has_parent_ref() const - { - return (head() && head()->has_parent_ref()) || - (tail() && tail()->has_parent_ref()); - } - - bool Complex_Selector::has_real_parent_ref() const - { - return (head() && head()->has_real_parent_ref()) || - (tail() && tail()->has_real_parent_ref()); - } - - bool Complex_Selector::operator< (const Complex_Selector& rhs) const - { - // const iterators for tails - Complex_Selector_Ptr_Const l = this; - Complex_Selector_Ptr_Const r = &rhs; - Compound_Selector_Ptr l_h = NULL; - Compound_Selector_Ptr r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - // process all tails - while (true) - { - #ifdef DEBUG - // skip empty ancestor first - if (l && l->is_empty_ancestor()) - { - l_h = NULL; - l = l->tail(); - if(l) l_h = l->head(); - continue; - } - // skip empty ancestor first - if (r && r->is_empty_ancestor()) - { - r_h = NULL; - r = r->tail(); - if (r) r_h = r->head(); - continue; - } - #endif - // check for valid selectors - if (!l) return !!r; - if (!r) return false; - // both are null - else if (!l_h && !r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next headers - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // one side is null - else if (!r_h) return true; - else if (!l_h) return false; - // heads ok and equal - else if (*l_h == *r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next headers - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // heads are not equal - else return *l_h < *r_h; - } - } - - bool Complex_Selector::operator== (const Complex_Selector& rhs) const - { - // const iterators for tails - Complex_Selector_Ptr_Const l = this; - Complex_Selector_Ptr_Const r = &rhs; - Compound_Selector_Ptr l_h = NULL; - Compound_Selector_Ptr r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - // process all tails - while (true) - { - #ifdef DEBUG - // skip empty ancestor first - if (l && l->is_empty_ancestor()) - { - l_h = NULL; - l = l->tail(); - if (l) l_h = l->head(); - continue; - } - // skip empty ancestor first - if (r && r->is_empty_ancestor()) - { - r_h = NULL; - r = r->tail(); - if (r) r_h = r->head(); - continue; - } - #endif - // check the pointers - if (!r) return !l; - if (!l) return !r; - // both are null - if (!l_h && !r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next heads - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // equals if other head is empty - else if ((!l_h && !r_h) || - (!l_h && r_h->empty()) || - (!r_h && l_h->empty()) || - (l_h && r_h && *l_h == *r_h)) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() == r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next heads - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // abort - else break; - } - // unreachable - return false; - } - - Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs) - { - if (empty()) return rhs; - Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); - for (size_t i = 0, L = length(); i < L; ++i) - { - if (unified.isNull()) break; - unified = at(i)->unify_with(unified); - } - return unified.detach(); - } - - bool Complex_Selector::operator== (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - - bool Complex_Selector::operator< (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Compound_Selector::operator== (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Compound_Selector::operator< (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Selector_Schema::operator== (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Selector_Schema::operator< (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Simple_Selector::operator== (const Selector& rhs) const - { - if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this == *sp; - return false; - } - - bool Simple_Selector::operator< (const Selector& rhs) const - { - if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this < *sp; - return false; - } - - bool Simple_Selector::operator== (const Simple_Selector& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (const Element_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (name_ == rhs.name_) - { return is_ns_eq(rhs); } - else return false; - } - - bool Simple_Selector::operator< (const Simple_Selector& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs < rhs; } - else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs < rhs; } - else if (const Element_Selector* lhs = Cast(this)) {return *lhs < rhs; } - else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs < rhs; } - if (is_ns_eq(rhs)) - { return name_ < rhs.name_; } - return ns_ < rhs.ns_; - } - - bool Selector_List::operator== (const Selector& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (Selector_List_Ptr_Const sl = Cast(&rhs)) { return *this == *sl; } - else if (Complex_Selector_Ptr_Const cpx = Cast(&rhs)) { return *this == *cpx; } - else if (Compound_Selector_Ptr_Const cpd = Cast(&rhs)) { return *this == *cpd; } - // no compare method - return this == &rhs; - } - - // Selector lists can be compared to comma lists - bool Selector_List::operator== (const Expression& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (List_Ptr_Const ls = Cast(&rhs)) { return *ls == *this; } - if (Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } - // compare invalid (maybe we should error?) - return false; - } - - bool Selector_List::operator== (const Selector_List& rhs) const - { - // for array access - size_t i = 0, n = 0; - size_t iL = length(); - size_t nL = rhs.length(); - // create temporary vectors and sort them - std::vector l_lst = this->elements(); - std::vector r_lst = rhs.elements(); - std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); - std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); - // process loop - while (true) - { - // first check for valid index - if (i == iL) return iL == nL; - else if (n == nL) return iL == nL; - // the access the vector items - Complex_Selector_Obj l = l_lst[i]; - Complex_Selector_Obj r = r_lst[n]; - // skip nulls - if (!l) ++i; - else if (!r) ++n; - // do the check - else if (*l != *r) - { return false; } - // advance - ++i; ++n; - } - // there is no break?! - } - - bool Selector_List::operator< (const Selector& rhs) const - { - if (Selector_List_Ptr_Const sp = Cast(&rhs)) return *this < *sp; - return false; - } - - bool Selector_List::operator< (const Selector_List& rhs) const - { - size_t l = rhs.length(); - if (length() < l) l = length(); - for (size_t i = 0; i < l; i ++) { - if (*at(i) < *rhs.at(i)) return true; - } - return false; - } - - Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs) - { - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { if (to_string() == rhs->at(i)->to_string()) return rhs; } - - // check for pseudo elements because they are always last - size_t i, L; - bool found = false; - if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector) || typeid(*this) == typeid(Attribute_Selector)) - { - for (i = 0, L = rhs->length(); i < L; ++i) - { - if ((Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) - { found = true; break; } - } - } - else - { - for (i = 0, L = rhs->length(); i < L; ++i) - { - if (Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) - { found = true; break; } - } - } - if (!found) - { - rhs->append(this); - } else { - rhs->elements().insert(rhs->elements().begin() + i, this); - } - return rhs; - } - - Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs) - { - // check if ns can be extended - // true for no ns or universal - if (has_universal_ns()) - { - // but dont extend with universal - // true for valid ns and universal - if (!rhs->is_universal_ns()) - { - // overwrite the name if star is given as name - if (this->name() == "*") { this->name(rhs->name()); } - // now overwrite the namespace name and flag - this->ns(rhs->ns()); this->has_ns(rhs->has_ns()); - // return copy - return this; - } - } - // namespace may changed, check the name now - // overwrite star (but not with another star) - if (name() == "*" && rhs->name() != "*") - { - // simply set the new name - this->name(rhs->name()); - // return copy - return this; - } - // return original - return this; - } - - Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs) - { - // TODO: handle namespaces - - // if the rhs is empty, just return a copy of this - if (rhs->length() == 0) { - rhs->append(this); - return rhs; - } - - Simple_Selector_Ptr rhs_0 = rhs->at(0); - // otherwise, this is a tag name - if (name() == "*") - { - if (typeid(*rhs_0) == typeid(Element_Selector)) - { - // if rhs is universal, just return this tagname + rhs's qualifiers - Element_Selector_Ptr ts = Cast(rhs_0); - rhs->at(0) = this->unify_with(ts); - return rhs; - } - else if (Cast(rhs_0) || Cast(rhs_0)) { - // qualifier is `.class`, so we can prefix with `ns|*.class` - if (has_ns() && !rhs_0->has_ns()) { - if (ns() != "*") rhs->elements().insert(rhs->begin(), this); - } - return rhs; - } - - - return rhs; - } - - if (typeid(*rhs_0) == typeid(Element_Selector)) - { - // if rhs is universal, just return this tagname + rhs's qualifiers - if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; - // otherwise create new compound and unify first simple selector - rhs->at(0) = this->unify_with(rhs_0); - return rhs; - - } - // else it's a tag name and a bunch of qualifiers -- just append them - if (name() != "*") rhs->elements().insert(rhs->begin(), this); - return rhs; - } - - Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs) - { - rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs); - } - - Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs) - { - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if (Id_Selector_Ptr sel = Cast(rhs->at(i))) { - if (sel->name() != name()) return 0; - } - } - rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs); - } - - Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs) - { - if (is_pseudo_element()) - { - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if (Pseudo_Selector_Ptr sel = Cast(rhs->at(i))) { - if (sel->is_pseudo_element() && sel->name() != name()) return 0; - } - } - } - return Simple_Selector::unify_with(rhs); - } - - bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const - { - if (is_ns_eq(rhs)) { - if (name() == rhs.name()) { - if (matcher() == rhs.matcher()) { - bool no_lhs_val = value().isNull(); - bool no_rhs_val = rhs.value().isNull(); - if (no_lhs_val && no_rhs_val) return false; // equal - else if (no_lhs_val) return true; // lhs is null - else if (no_rhs_val) return false; // rhs is null - return *value() < *rhs.value(); // both are given - } else { return matcher() < rhs.matcher(); } - } else { return name() < rhs.name(); } - } else { return ns() < rhs.ns(); } - } - - bool Attribute_Selector::operator< (const Simple_Selector& rhs) const - { - if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const - { - // get optional value state - bool no_lhs_val = value().isNull(); - bool no_rhs_val = rhs.value().isNull(); - // both are null, therefore equal - if (no_lhs_val && no_rhs_val) { - return (name() == rhs.name()) - && (matcher() == rhs.matcher()) - && (is_ns_eq(rhs)); - } - // both are defined, evaluate - if (no_lhs_val == no_rhs_val) { - return (name() == rhs.name()) - && (matcher() == rhs.matcher()) - && (is_ns_eq(rhs)) - && (*value() == *rhs.value()); - } - // not equal - return false; - - } - - bool Attribute_Selector::operator== (const Simple_Selector& rhs) const - { - if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) - { - return is_ns_eq(rhs) && - name() == rhs.name() && - *this == *w; - } - return false; - } - - bool Element_Selector::operator< (const Element_Selector& rhs) const - { - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Element_Selector::operator< (const Simple_Selector& rhs) const - { - if (Element_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Element_Selector::operator== (const Element_Selector& rhs) const - { - return is_ns_eq(rhs) && - name() == rhs.name(); - } - - bool Element_Selector::operator== (const Simple_Selector& rhs) const - { - if (Element_Selector_Ptr_Const w = Cast(&rhs)) - { - return is_ns_eq(rhs) && - name() == rhs.name() && - *this == *w; - } - return false; - } - - bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { - String_Obj lhs_ex = expression(); - String_Obj rhs_ex = rhs.expression(); - if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; - else return lhs_ex.ptr() == rhs_ex.ptr(); - } - else return false; - } - - bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const - { - if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this == *w; - } - return is_ns_eq(rhs) && - name() == rhs.name(); - } - - bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { - String_Obj lhs_ex = expression(); - String_Obj rhs_ex = rhs.expression(); - if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; - else return lhs_ex.ptr() < rhs_ex.ptr(); - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const - { - if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { return *(selector()) == *(rhs.selector()); } - else return false; - } - - bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const - { - if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this == *w; - } - return is_ns_eq(rhs) && - name() == rhs.name(); - } - - bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { return *(selector()) < *(rhs.selector()); } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const - { - if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub) - { - if (this->name() != sub->name()) return false; - if (this->name() == ":current") return false; - if (Selector_List_Obj rhs_list = Cast(sub->selector())) { - if (Selector_List_Obj lhs_list = Cast(selector())) { - return lhs_list->is_superselector_of(rhs_list); - } - } - coreError("is_superselector expected a Selector_List", sub->pstate()); - return false; - } - - bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped) - { - for (Complex_Selector_Obj item : rhs->elements()) { - if (is_superselector_of(item, wrapped)) return true; - } - return false; - } - - bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped) - { - if (rhs->head()) return is_superselector_of(rhs->head(), wrapped); - return false; - } - - bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) - { - Compound_Selector_Ptr lhs = this; - Simple_Selector_Ptr lbase = lhs->base(); - Simple_Selector_Ptr rbase = rhs->base(); - - // Check if pseudo-elements are the same between the selectors - - std::set lpsuedoset, rpsuedoset; - for (size_t i = 0, L = length(); i < L; ++i) - { - if ((*this)[i]->is_pseudo_element()) { - std::string pseudo((*this)[i]->to_string()); - pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - lpsuedoset.insert(pseudo); - } - } - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if ((*rhs)[i]->is_pseudo_element()) { - std::string pseudo((*rhs)[i]->to_string()); - pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - rpsuedoset.insert(pseudo); - } - } - if (lpsuedoset != rpsuedoset) { - return false; - } - - // would like to replace this without stringification - // https://github.com/sass/sass/issues/2229 - // SimpleSelectorSet lset, rset; - std::set lset, rset; - - if (lbase && rbase) - { - if (lbase->to_string() == rbase->to_string()) { - for (size_t i = 1, L = length(); i < L; ++i) - { lset.insert((*this)[i]->to_string()); } - for (size_t i = 1, L = rhs->length(); i < L; ++i) - { rset.insert((*rhs)[i]->to_string()); } - return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); - } - return false; - } - - for (size_t i = 0, iL = length(); i < iL; ++i) - { - Selector_Obj wlhs = (*this)[i]; - // very special case for wrapped matches selector - if (Wrapped_Selector_Obj wrapped = Cast(wlhs)) { - if (wrapped->name() == ":not") { - if (Selector_List_Obj not_list = Cast(wrapped->selector())) { - if (not_list->is_superselector_of(rhs, wrapped->name())) return false; - } else { - throw std::runtime_error("wrapped not selector is not a list"); - } - } - if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - wlhs = wrapped->selector(); - if (Selector_List_Obj list = Cast(wrapped->selector())) { - if (Compound_Selector_Obj comp = Cast(rhs)) { - if (!wrapping.empty() && wrapping != wrapped->name()) return false; - if (wrapping.empty() || wrapping != wrapped->name()) {; - if (list->is_superselector_of(comp, wrapped->name())) return true; - } - } - } - } - Simple_Selector_Ptr rhs_sel = NULL; - if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; - if (Wrapped_Selector_Ptr wrapped_r = Cast(rhs_sel)) { - if (wrapped->name() == wrapped_r->name()) { - if (wrapped->is_superselector_of(wrapped_r)) { - continue; - }} - } - } - // match from here on as strings - lset.insert(wlhs->to_string()); - } - - for (size_t n = 0, nL = rhs->length(); n < nL; ++n) - { - Selector_Obj r = (*rhs)[n]; - if (Wrapped_Selector_Obj wrapped = Cast(r)) { - if (wrapped->name() == ":not") { - if (Selector_List_Obj ls = Cast(wrapped->selector())) { - ls->remove_parent_selectors(); - if (is_superselector_of(ls, wrapped->name())) return false; - } - } - if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - if (!wrapping.empty()) { - if (wrapping != wrapped->name()) return false; - } - if (Selector_List_Obj ls = Cast(wrapped->selector())) { - ls->remove_parent_selectors(); - return (is_superselector_of(ls, wrapped->name())); - } - } - } - rset.insert(r->to_string()); - } - - //for (auto l : lset) { cerr << "l: " << l << endl; } - //for (auto r : rset) { cerr << "r: " << r << endl; } - - if (lset.empty()) return true; - // return true if rset contains all the elements of lset - return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); - - } - - // create complex selector (ancestor of) from compound selector - Complex_Selector_Obj Compound_Selector::to_complex() - { - // create an intermediate complex selector - return SASS_MEMORY_NEW(Complex_Selector, - pstate(), - Complex_Selector::ANCESTOR_OF, - this, - 0); - } - - Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other) - { - - // get last tails (on the right side) - Complex_Selector_Obj l_last = this->last(); - Complex_Selector_Obj r_last = other->last(); - - // check valid pointers (assertion) - SASS_ASSERT(l_last, "lhs is null"); - SASS_ASSERT(r_last, "rhs is null"); - - // Not sure about this check, but closest way I could check - // was to see if this is a ruby 'SimpleSequence' equivalent. - // It seems to do the job correctly as some specs react to this - if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0; - if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0; - - // get the headers for the last tails - Compound_Selector_Obj l_last_head = l_last->head(); - Compound_Selector_Obj r_last_head = r_last->head(); - - // check valid head pointers (assertion) - SASS_ASSERT(l_last_head, "lhs head is null"); - SASS_ASSERT(r_last_head, "rhs head is null"); - - // get the unification of the last compound selectors - Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); - - // abort if we could not unify heads - if (unified == 0) return 0; - - // check for universal (star: `*`) selector - bool is_universal = l_last_head->is_universal() || - r_last_head->is_universal(); - - if (is_universal) - { - // move the head - l_last->head(0); - r_last->head(unified); - } - - // create nodes from both selectors - Node lhsNode = complexSelectorToNode(this); - Node rhsNode = complexSelectorToNode(other); - - // overwrite universal base - if (!is_universal) - { - // create some temporaries to convert to node - Complex_Selector_Obj fake = unified->to_complex(); - Node unified_node = complexSelectorToNode(fake); - // add to permutate the list? - rhsNode.plus(unified_node); - } - - // do some magic we inherit from node and extend - Node node = subweave(lhsNode, rhsNode); - Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate()); - NodeDequePtr col = node.collection(); // move from collection to list - for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) - { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); } - - // only return if list has some entries - return result->length() ? result.detach() : 0; - - } - - bool Compound_Selector::operator== (const Compound_Selector& rhs) const - { - // for array access - size_t i = 0, n = 0; - size_t iL = length(); - size_t nL = rhs.length(); - // create temporary vectors and sort them - std::vector l_lst = this->elements(); - std::vector r_lst = rhs.elements(); - std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); - std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); - // process loop - while (true) - { - // first check for valid index - if (i == iL) return iL == nL; - else if (n == nL) return iL == nL; - // the access the vector items - Simple_Selector_Obj l = l_lst[i]; - Simple_Selector_Obj r = r_lst[n]; - // skip nulls - if (!l) ++i; - if (!r) ++n; - // do the check now - else if (*l != *r) - { return false; } - // advance now - ++i; ++n; - } - // there is no break?! - } - - bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) - { - return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); - } - - bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping) - { - Complex_Selector_Ptr lhs = this; - // check for selectors with leading or trailing combinators - if (!lhs->head() || !rhs->head()) - { return false; } - Complex_Selector_Obj l_innermost = lhs->innermost(); - if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF) - { return false; } - Complex_Selector_Obj r_innermost = rhs->innermost(); - if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF) - { return false; } - // more complex (i.e., longer) selectors are always more specific - size_t l_len = lhs->length(), r_len = rhs->length(); - if (l_len > r_len) - { return false; } - - if (l_len == 1) - { return lhs->head()->is_superselector_of(rhs->last()->head(), wrapping); } - - // we have to look one tail deeper, since we cary the - // combinator around for it (which is important here) - if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { - Complex_Selector_Obj lhs_tail = lhs->tail(); - Complex_Selector_Obj rhs_tail = rhs->tail(); - if (lhs_tail->combinator() != rhs_tail->combinator()) return false; - if (lhs_tail->head() && !rhs_tail->head()) return false; - if (!lhs_tail->head() && rhs_tail->head()) return false; - if (lhs_tail->head() && rhs_tail->head()) { - if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; - } - } - - bool found = false; - Complex_Selector_Obj marker = rhs; - for (size_t i = 0, L = rhs->length(); i < L; ++i) { - if (i == L-1) - { return false; } - if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping)) - { found = true; break; } - marker = marker->tail(); - } - if (!found) - { return false; } - - /* - Hmm, I hope I have the logic right: - - if lhs has a combinator: - if !(marker has a combinator) return false - if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false - return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost) - else if marker has a combinator: - if !(marker.combinator == ">") return false - return lhs.tail.is_superselector_of(marker.tail) - else - return lhs.tail.is_superselector_of(marker.tail) - */ - if (lhs->combinator() != Complex_Selector::ANCESTOR_OF) - { - if (marker->combinator() == Complex_Selector::ANCESTOR_OF) - { return false; } - if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) - { return false; } - return lhs->tail()->is_superselector_of(marker->tail()); - } - else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) - { - if (marker->combinator() != Complex_Selector::PARENT_OF) - { return false; } - return lhs->tail()->is_superselector_of(marker->tail()); - } - return lhs->tail()->is_superselector_of(marker->tail()); - } - - size_t Complex_Selector::length() const - { - // TODO: make this iterative - if (!tail()) return 1; - return 1 + tail()->length(); - } - - // append another complex selector at the end - // check if we need to append some headers - // then we need to check for the combinator - // only then we can safely set the new tail - void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces) - { - - Complex_Selector_Obj t = ss->tail(); - Combinator c = ss->combinator(); - String_Obj r = ss->reference(); - Compound_Selector_Obj h = ss->head(); - - if (ss->has_line_feed()) has_line_feed(true); - if (ss->has_line_break()) has_line_break(true); - - // append old headers - if (h && h->length()) { - if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - traces.push_back(Backtrace(pstate())); - throw Exception::InvalidParent(this, traces, ss); - } else if (last()->head_ && last()->head_->length()) { - Compound_Selector_Obj rh = last()->head(); - size_t i; - size_t L = h->length(); - if (Cast(h->first())) { - if (Class_Selector_Ptr cs = Cast(rh->last())) { - Class_Selector_Ptr sqs = SASS_MEMORY_COPY(cs); - sqs->name(sqs->name() + (*h)[0]->name()); - sqs->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = sqs; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Id_Selector_Ptr is = Cast(rh->last())) { - Id_Selector_Ptr sqs = SASS_MEMORY_COPY(is); - sqs->name(sqs->name() + (*h)[0]->name()); - sqs->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = sqs; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Element_Selector_Ptr ts = Cast(rh->last())) { - Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts); - tss->name(tss->name() + (*h)[0]->name()); - tss->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = tss; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Placeholder_Selector_Ptr ps = Cast(rh->last())) { - Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps); - pss->name(pss->name() + (*h)[0]->name()); - pss->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = pss; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else { - last()->head_->concat(h); - } - } else { - last()->head_->concat(h); - } - } else if (last()->head_) { - last()->head_->concat(h); - } - } else { - // std::cerr << "has no or empty head\n"; - } - - if (last()) { - if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate()); - inter->reference(r); - inter->combinator(c); - inter->tail(t); - last()->tail(inter); - } else { - if (last()->combinator() == ANCESTOR_OF) { - last()->combinator(c); - last()->reference(r); - } - last()->tail(t); - } - } - - } - - Selector_List_Obj Selector_List::eval(Eval& eval) - { - Selector_List_Obj list = schema() ? - eval(schema()) : eval(this); - list->schema(schema()); - return list; - } - - Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) - { - if (!this->has_parent_ref()) return this; - Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); - Selector_List_Ptr ps = pstack.back(); - for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { - for (size_t si = 0, sL = this->length(); si < sL; ++si) { - Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent); - ss->concat(rv); - } - } - return ss; - } - - Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) - { - Complex_Selector_Obj tail = this->tail(); - Compound_Selector_Obj head = this->head(); - Selector_List_Ptr parents = pstack.back(); - - if (!this->has_real_parent_ref() && !implicit_parent) { - Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate()); - retval->append(this); - return retval; - } - - // first resolve_parent_refs the tail (which may return an expanded list) - Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0; - - if (head && head->length() > 0) { - - Selector_List_Obj retval; - // we have a parent selector in a simple compound list - // mix parent complex selector into the compound list - if (Cast((*head)[0])) { - retval = SASS_MEMORY_NEW(Selector_List, pstate()); - - // it turns out that real parent references reach - // across @at-root rules, which comes unexpected - if (parents == NULL && head->has_real_parent_ref()) { - int i = pstack.size() - 1; - while (!parents && i > -1) { - parents = pstack.at(i--); - } - } - - if (parents && parents->length()) { - if (tails && tails->length() > 0) { - for (size_t n = 0, nL = tails->length(); n < nL; ++n) { - for (size_t i = 0, iL = parents->length(); i < iL; ++i) { - Complex_Selector_Obj t = (*tails)[n]; - Complex_Selector_Obj parent = (*parents)[i]; - Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); - Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL); - Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); - // remove parent selector from sequence - if (h->length()) { - h->erase(h->begin()); - ss->head(h); - } else { - ss->head(NULL); - } - // adjust for parent selector (1 char) - // if (h->length()) { - // ParserState state(h->at(0)->pstate()); - // state.offset.column += 1; - // state.column -= 1; - // (*h)[0]->pstate(state); - // } - // keep old parser state - s->pstate(pstate()); - // append new tail - s->append(ss, traces); - retval->append(s); - } - } - } - // have no tails but parents - // loop above is inside out - else { - for (size_t i = 0, iL = parents->length(); i < iL; ++i) { - Complex_Selector_Obj parent = (*parents)[i]; - Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); - Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - // this is only if valid if the parent has no trailing op - // otherwise we cannot append more simple selectors to head - if (parent->last()->combinator() != ANCESTOR_OF) { - traces.push_back(Backtrace(pstate())); - throw Exception::InvalidParent(parent, traces, ss); - } - ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); - Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); - // remove parent selector from sequence - if (h->length()) { - h->erase(h->begin()); - ss->head(h); - } else { - ss->head(NULL); - } - // \/ IMO ruby sass bug \/ - ss->has_line_feed(false); - // adjust for parent selector (1 char) - // if (h->length()) { - // ParserState state(h->at(0)->pstate()); - // state.offset.column += 1; - // state.column -= 1; - // (*h)[0]->pstate(state); - // } - // keep old parser state - s->pstate(pstate()); - // append new tail - s->append(ss, traces); - retval->append(s); - } - } - } - // have no parent but some tails - else { - if (tails && tails->length() > 0) { - for (size_t n = 0, nL = tails->length(); n < nL; ++n) { - Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); - cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); - cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); - for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append((*this->head())[i]); - if (!cpy->head()->length()) cpy->head(0); - retval->append(cpy->skip_empty_reference()); - } - } - // have no parent nor tails - else { - Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); - cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); - for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append((*this->head())[i]); - if (!cpy->head()->length()) cpy->head(0); - retval->append(cpy->skip_empty_reference()); - } - } - } - // no parent selector in head - else { - retval = this->tails(tails); - } - - for (Simple_Selector_Obj ss : head->elements()) { - if (Wrapped_Selector_Ptr ws = Cast(ss)) { - if (Selector_List_Ptr sl = Cast(ws->selector())) { - if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent)); - } - } - } - - return retval.detach(); - - } - // has no head - return this->tails(tails); - } - - Selector_List_Ptr Complex_Selector::tails(Selector_List_Ptr tails) - { - Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_); - if (tails && tails->length()) { - for (size_t i = 0, iL = tails->length(); i < iL; ++i) { - Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this); - pr->tail(tails->at(i)); - rv->append(pr); - } - } - else { - rv->append(this); - } - return rv; - } - - // return the last tail that is defined - Complex_Selector_Obj Complex_Selector::first() - { - // declare variables used in loop - Complex_Selector_Obj cur = this; - Compound_Selector_Obj head; - // processing loop - while (cur) - { - // get the head - head = cur->head_; - // abort (and return) if it is not a parent selector - if (!head || head->length() != 1 || !Cast((*head)[0])) { - break; - } - // advance to next - cur = cur->tail_; - } - // result - return cur; - } - - // return the last tail that is defined - Complex_Selector_Obj Complex_Selector::last() - { - Complex_Selector_Ptr cur = this; - Complex_Selector_Ptr nxt = cur; - // loop until last - while (nxt) { - cur = nxt; - nxt = cur->tail(); - } - return cur; - } - - Complex_Selector::Combinator Complex_Selector::clear_innermost() - { - Combinator c; - if (!tail() || tail()->tail() == 0) - { c = combinator(); combinator(ANCESTOR_OF); tail(0); } - else - { c = tail()->clear_innermost(); } - return c; - } - - void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c) - { - if (!tail()) - { tail(val); combinator(c); } - else - { tail()->set_innermost(val, c); } - } - - void Complex_Selector::cloneChildren() - { - if (head()) head(SASS_MEMORY_CLONE(head())); - if (tail()) tail(SASS_MEMORY_CLONE(tail())); - } - - void Compound_Selector::cloneChildren() - { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } - } - - void Selector_List::cloneChildren() - { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } - } - - void Wrapped_Selector::cloneChildren() - { - selector(SASS_MEMORY_CLONE(selector())); - } - - // remove parent selector references - // basically unwraps parsed selectors - void Selector_List::remove_parent_selectors() - { - // Check every rhs selector against left hand list - for(size_t i = 0, L = length(); i < L; ++i) { - if (!(*this)[i]->head()) continue; - if ((*this)[i]->head()->is_empty_reference()) { - // simply move to the next tail if we have "no" combinator - if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) { - if ((*this)[i]->tail()) { - if ((*this)[i]->has_line_feed()) { - (*this)[i]->tail()->has_line_feed(true); - } - (*this)[i] = (*this)[i]->tail(); - } - } - // otherwise remove the first item from head - else { - (*this)[i]->head()->erase((*this)[i]->head()->begin()); - } - } - } - } - - size_t Wrapped_Selector::hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (selector_) hash_combine(hash_, selector_->hash()); - } - return hash_; - } - bool Wrapped_Selector::has_parent_ref() const { - // if (has_reference()) return true; - if (!selector()) return false; - return selector()->has_parent_ref(); - } - bool Wrapped_Selector::has_real_parent_ref() const { - // if (has_reference()) return true; - if (!selector()) return false; - return selector()->has_real_parent_ref(); - } - unsigned long Wrapped_Selector::specificity() const - { - return selector_ ? selector_->specificity() : 0; - } - - - bool Selector_List::has_parent_ref() const - { - for (Complex_Selector_Obj s : elements()) { - if (s && s->has_parent_ref()) return true; - } - return false; - } - - bool Selector_List::has_real_parent_ref() const - { - for (Complex_Selector_Obj s : elements()) { - if (s && s->has_real_parent_ref()) return true; - } - return false; - } - - bool Selector_Schema::has_parent_ref() const - { - if (String_Schema_Obj schema = Cast(contents())) { - return schema->length() > 0 && Cast(schema->at(0)) != NULL; - } - return false; - } - - bool Selector_Schema::has_real_parent_ref() const - { - if (String_Schema_Obj schema = Cast(contents())) { - Parent_Selector_Obj p = Cast(schema->at(0)); - return schema->length() > 0 && p && p->is_real_parent_ref(); - } - return false; - } - - void Selector_List::adjust_after_pushing(Complex_Selector_Obj c) - { - // if (c->has_reference()) has_reference(true); - } - - // it's a superselector if every selector of the right side - // list is a superselector of the given left side selector - bool Complex_Selector::is_superselector_of(Selector_List_Obj sub, std::string wrapping) - { - // Check every rhs selector against left hand list - for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of((*sub)[i], wrapping)) return false; - } - return true; - } - - // it's a superselector if every selector of the right side - // list is a superselector of the given left side selector - bool Selector_List::is_superselector_of(Selector_List_Obj sub, std::string wrapping) - { - // Check every rhs selector against left hand list - for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of((*sub)[i], wrapping)) return false; - } - return true; - } - - // it's a superselector if every selector on the right side - // is a superselector of any one of the left side selectors - bool Selector_List::is_superselector_of(Compound_Selector_Obj sub, std::string wrapping) - { - // Check every lhs selector against right hand - for(size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_superselector_of(sub, wrapping)) return true; - } - return false; - } - - // it's a superselector if every selector on the right side - // is a superselector of any one of the left side selectors - bool Selector_List::is_superselector_of(Complex_Selector_Obj sub, std::string wrapping) - { - // Check every lhs selector against right hand - for(size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_superselector_of(sub)) return true; - } - return false; - } - - Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) { - std::vector unified_complex_selectors; - // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` - for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { - Complex_Selector_Obj seq1 = (*this)[lhs_i]; - for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { - Complex_Selector_Ptr seq2 = rhs->at(rhs_i); - - Selector_List_Obj result = seq1->unify_with(seq2); - if( result ) { - for(size_t i = 0, L = result->length(); i < L; ++i) { - unified_complex_selectors.push_back( (*result)[i] ); - } - } - } - } - - // Creates the final Selector_List by combining all the complex selectors - Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate()); - for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) { - final_result->append(*itr); - } - return final_result; - } - - void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends) - { - - Selector_List_Ptr extender = this; - for (auto complex_sel : extendee->elements()) { - Complex_Selector_Obj c = complex_sel; - - - // Ignore any parent selectors, until we find the first non Selectorerence head - Compound_Selector_Obj compound_sel = c->head(); - Complex_Selector_Obj pIter = complex_sel; - while (pIter) { - Compound_Selector_Obj pHead = pIter->head(); - if (pHead && Cast(pHead->elements()[0]) == NULL) { - compound_sel = pHead; - break; - } - - pIter = pIter->tail(); - } - - if (!pIter->head() || pIter->tail()) { - coreError("nested selectors may not be extended", c->pstate()); - } - - compound_sel->is_optional(extendee->is_optional()); - - for (size_t i = 0, L = extender->length(); i < L; ++i) { - extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel)); - } - } - }; - - void Compound_Selector::append(Simple_Selector_Ptr element) - { - Vectorized::append(element); - pstate_.offset += element->pstate().offset; - } - - Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs) - { - Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate()); - // result->has_parent_reference(has_parent_reference()); - - // not very efficient because it needs to preserve order - for (size_t i = 0, L = length(); i < L; ++i) - { - bool found = false; - std::string thisSelector((*this)[i]->to_string()); - for (size_t j = 0, M = rhs->length(); j < M; ++j) - { - if (thisSelector == (*rhs)[j]->to_string()) - { - found = true; - break; - } - } - if (!found) result->append((*this)[i]); - } - - return result; - } - - void Compound_Selector::mergeSources(ComplexSelectorSet& sources) - { - for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { - this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); - } - } - - Argument_Obj Arguments::get_rest_argument() - { - if (this->has_rest_argument()) { - for (Argument_Obj arg : this->elements()) { - if (arg->is_rest_argument()) { - return arg; - } - } - } - return NULL; - } - - Argument_Obj Arguments::get_keyword_argument() - { - if (this->has_keyword_argument()) { - for (Argument_Obj arg : this->elements()) { - if (arg->is_keyword_argument()) { - return arg; - } - } - } - return NULL; - } - - void Arguments::adjust_after_pushing(Argument_Obj a) - { - if (!a->name().empty()) { - if (has_keyword_argument()) { - coreError("named arguments must precede variable-length argument", a->pstate()); - } - has_named_arguments(true); - } - else if (a->is_rest_argument()) { - if (has_rest_argument()) { - coreError("functions and mixins may only be called with one variable-length argument", a->pstate()); - } - if (has_keyword_argument_) { - coreError("only keyword arguments may follow variable arguments", a->pstate()); - } - has_rest_argument(true); - } - else if (a->is_keyword_argument()) { - if (has_keyword_argument()) { - coreError("functions and mixins may only be called with one keyword argument", a->pstate()); - } - has_keyword_argument(true); - } - else { - if (has_rest_argument()) { - coreError("ordinal arguments must precede variable-length arguments", a->pstate()); - } - if (has_named_arguments()) { - coreError("ordinal arguments must precede named arguments", a->pstate()); - } - } - } - - bool Ruleset::is_invisible() const { - if (Selector_List_Ptr sl = Cast(selector())) { - for (size_t i = 0, L = sl->length(); i < L; ++i) - if (!(*sl)[i]->has_placeholder()) return false; - } - return true; - } - - bool Media_Block::is_invisible() const { - for (size_t i = 0, L = block()->length(); i < L; ++i) { - Statement_Obj stm = block()->at(i); - if (!stm->is_invisible()) return false; - } - return true; - } - - Number::Number(ParserState pstate, double val, std::string u, bool zero) - : Value(pstate), - Units(), - value_(val), - zero_(zero), - hash_(0) - { - size_t l = 0; - size_t r; - if (!u.empty()) { - bool nominator = true; - while (true) { - r = u.find_first_of("*/", l); - std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); - if (!unit.empty()) { - if (nominator) numerators.push_back(unit); - else denominators.push_back(unit); - } - if (r == std::string::npos) break; - // ToDo: should error for multiple slashes - // if (!nominator && u[r] == '/') error(...) - if (u[r] == '/') - nominator = false; - // strange math parsing? - // else if (u[r] == '*') - // nominator = true; - l = r + 1; - } - } - concrete_type(NUMBER); - } - - // cancel out unnecessary units - void Number::reduce() - { - // apply conversion factor - value_ *= this->Units::reduce(); - } - - void Number::normalize() - { - // apply conversion factor - value_ *= this->Units::normalize(); - } - - bool Custom_Warning::operator== (const Expression& rhs) const - { - if (Custom_Warning_Ptr_Const r = Cast(&rhs)) { - return message() == r->message(); - } - return false; - } - - bool Custom_Error::operator== (const Expression& rhs) const - { - if (Custom_Error_Ptr_Const r = Cast(&rhs)) { - return message() == r->message(); - } - return false; - } - - bool Number::operator== (const Expression& rhs) const - { - if (auto rhsnr = Cast(&rhs)) { - return *this == *rhsnr; - } - return false; - } - - bool Number::operator== (const Number& rhs) const - { - Number l(*this), r(rhs); l.reduce(); r.reduce(); - size_t lhs_units = l.numerators.size() + l.denominators.size(); - size_t rhs_units = r.numerators.size() + r.denominators.size(); - // unitless and only having one unit seems equivalent (will change in future) - if (!lhs_units || !rhs_units) { - return NEAR_EQUAL(l.value(), r.value()); - } - l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - return lhs_unit == rhs_unit && - NEAR_EQUAL(l.value(), r.value()); - } - - bool Number::operator< (const Number& rhs) const - { - Number l(*this), r(rhs); l.reduce(); r.reduce(); - size_t lhs_units = l.numerators.size() + l.denominators.size(); - size_t rhs_units = r.numerators.size() + r.denominators.size(); - // unitless and only having one unit seems equivalent (will change in future) - if (!lhs_units || !rhs_units) { - return l.value() < r.value(); - } - l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - if (!(lhs_unit == rhs_unit)) { - /* ToDo: do we always get usefull backtraces? */ - throw Exception::IncompatibleUnits(rhs, *this); - } - return lhs_unit < rhs_unit || - l.value() < r.value(); - } - - bool String_Quoted::operator== (const Expression& rhs) const - { - if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { - return (value() == qstr->value()); - } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { - return (value() == cstr->value()); - } - return false; - } - - bool String_Constant::is_invisible() const { - return value_.empty() && quote_mark_ == 0; - } - - bool String_Constant::operator== (const Expression& rhs) const - { - if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { - return (value() == qstr->value()); - } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { - return (value() == cstr->value()); - } - return false; - } - - bool String_Schema::is_left_interpolant(void) const - { - return length() && first()->is_left_interpolant(); - } - bool String_Schema::is_right_interpolant(void) const - { - return length() && last()->is_right_interpolant(); - } - - bool String_Schema::operator== (const Expression& rhs) const - { - if (String_Schema_Ptr_Const r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - Expression_Obj rv = (*r)[i]; - Expression_Obj lv = (*this)[i]; - if (!lv || !rv) return false; - if (!(*lv == *rv)) return false; - } - return true; - } - return false; - } - - bool Boolean::operator== (const Expression& rhs) const - { - if (Boolean_Ptr_Const r = Cast(&rhs)) { - return (value() == r->value()); - } - return false; - } - - bool Color::operator== (const Expression& rhs) const - { - if (Color_Ptr_Const r = Cast(&rhs)) { - return r_ == r->r() && - g_ == r->g() && - b_ == r->b() && - a_ == r->a(); - } - return false; - } - - bool List::operator== (const Expression& rhs) const - { - if (List_Ptr_Const r = Cast(&rhs)) { - if (length() != r->length()) return false; - if (separator() != r->separator()) return false; - if (is_bracketed() != r->is_bracketed()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - Expression_Obj rv = r->at(i); - Expression_Obj lv = this->at(i); - if (!lv || !rv) return false; - if (!(*lv == *rv)) return false; - } - return true; - } - return false; - } - - bool Map::operator== (const Expression& rhs) const - { - if (Map_Ptr_Const r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (auto key : keys()) { - Expression_Obj lv = at(key); - Expression_Obj rv = r->at(key); - if (!rv || !lv) return false; - if (!(*lv == *rv)) return false; - } - return true; - } - return false; - } - - bool Null::operator== (const Expression& rhs) const - { - return rhs.concrete_type() == NULL_VAL; - } - - bool Function::operator== (const Expression& rhs) const - { - if (Function_Ptr_Const r = Cast(&rhs)) { - Definition_Ptr_Const d1 = Cast(definition()); - Definition_Ptr_Const d2 = Cast(r->definition()); - return d1 && d2 && d1 == d2 && is_css() == r->is_css(); - } - return false; - } - - size_t List::size() const { - if (!is_arglist_) return length(); - // arglist expects a list of arguments - // so we need to break before keywords - for (size_t i = 0, L = length(); i < L; ++i) { - Expression_Obj obj = this->at(i); - if (Argument_Ptr arg = Cast(obj)) { - if (!arg->name().empty()) return i; - } - } - return length(); - } - - Expression_Obj Hashed::at(Expression_Obj k) const - { - if (elements_.count(k)) - { return elements_.at(k); } - else { return NULL; } - } - - bool Binary_Expression::is_left_interpolant(void) const - { - return is_interpolant() || (left() && left()->is_left_interpolant()); - } - bool Binary_Expression::is_right_interpolant(void) const - { - return is_interpolant() || (right() && right()->is_right_interpolant()); - } - - const std::string AST_Node::to_string(Sass_Inspect_Options opt) const - { - Sass_Output_Options out(opt); - Emitter emitter(out); - Inspect i(emitter); - i.in_declaration = true; - // ToDo: inspect should be const - const_cast(this)->perform(&i); - return i.get_buffer(); - } - - const std::string AST_Node::to_string() const - { - return to_string({ NESTED, 5 }); - } - - std::string String_Quoted::inspect() const - { - return quote(value_, '*'); - } - - std::string String_Constant::inspect() const - { - return quote(value_, '*'); - } - - bool Declaration::is_invisible() const - { - if (is_custom_property()) return false; - - return !(value_ && value_->concrete_type() != Expression::NULL_VAL); - } - - ////////////////////////////////////////////////////////////////////////////////////////// - // Additional method on Lists to retrieve values directly or from an encompassed Argument. - ////////////////////////////////////////////////////////////////////////////////////////// - Expression_Obj List::value_at_index(size_t i) { - Expression_Obj obj = this->at(i); - if (is_arglist_) { - if (Argument_Ptr arg = Cast(obj)) { - return arg->value(); - } else { - return obj; - } - } else { - return obj; - } - } - - ////////////////////////////////////////////////////////////////////////////////////////// - // Convert map to (key, value) list. - ////////////////////////////////////////////////////////////////////////////////////////// - List_Obj Map::to_list(ParserState& pstate) { - List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); - - for (auto key : keys()) { - List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(key); - l->append(at(key)); - ret->append(l); - } - - return ret; - } - - ////////////////////////////////////////////////////////////////////////////////////////// - // Copy implementations - ////////////////////////////////////////////////////////////////////////////////////////// - - #ifdef DEBUG_SHARED_PTR - - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass##_Ptr klass::copy(std::string file, size_t line) const { \ - klass##_Ptr cpy = new klass(this); \ - cpy->trace(file, line); \ - return cpy; \ - } \ - klass##_Ptr klass::clone(std::string file, size_t line) const { \ - klass##_Ptr cpy = copy(file, line); \ - cpy->cloneChildren(); \ - return cpy; \ - } \ - - #else - - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass##_Ptr klass::copy() const { \ - return new klass(this); \ - } \ - klass##_Ptr klass::clone() const { \ - klass##_Ptr cpy = copy(); \ - cpy->cloneChildren(); \ - return cpy; \ - } \ - - #endif - - IMPLEMENT_AST_OPERATORS(Supports_Operator); - IMPLEMENT_AST_OPERATORS(Supports_Negation); - IMPLEMENT_AST_OPERATORS(Compound_Selector); - IMPLEMENT_AST_OPERATORS(Complex_Selector); - IMPLEMENT_AST_OPERATORS(Element_Selector); - IMPLEMENT_AST_OPERATORS(Class_Selector); - IMPLEMENT_AST_OPERATORS(Id_Selector); - IMPLEMENT_AST_OPERATORS(Pseudo_Selector); - IMPLEMENT_AST_OPERATORS(Wrapped_Selector); - IMPLEMENT_AST_OPERATORS(Selector_List); - IMPLEMENT_AST_OPERATORS(Ruleset); - IMPLEMENT_AST_OPERATORS(Media_Block); - IMPLEMENT_AST_OPERATORS(Custom_Warning); - IMPLEMENT_AST_OPERATORS(Custom_Error); - IMPLEMENT_AST_OPERATORS(List); - IMPLEMENT_AST_OPERATORS(Map); - IMPLEMENT_AST_OPERATORS(Function); - IMPLEMENT_AST_OPERATORS(Number); - IMPLEMENT_AST_OPERATORS(Binary_Expression); - IMPLEMENT_AST_OPERATORS(String_Schema); - IMPLEMENT_AST_OPERATORS(String_Constant); - IMPLEMENT_AST_OPERATORS(String_Quoted); - IMPLEMENT_AST_OPERATORS(Boolean); - IMPLEMENT_AST_OPERATORS(Color); - IMPLEMENT_AST_OPERATORS(Null); - IMPLEMENT_AST_OPERATORS(Parent_Selector); - IMPLEMENT_AST_OPERATORS(Import); - IMPLEMENT_AST_OPERATORS(Import_Stub); - IMPLEMENT_AST_OPERATORS(Function_Call); - IMPLEMENT_AST_OPERATORS(Directive); - IMPLEMENT_AST_OPERATORS(At_Root_Block); - IMPLEMENT_AST_OPERATORS(Supports_Block); - IMPLEMENT_AST_OPERATORS(While); - IMPLEMENT_AST_OPERATORS(Each); - IMPLEMENT_AST_OPERATORS(For); - IMPLEMENT_AST_OPERATORS(If); - IMPLEMENT_AST_OPERATORS(Mixin_Call); - IMPLEMENT_AST_OPERATORS(Extension); - IMPLEMENT_AST_OPERATORS(Media_Query); - IMPLEMENT_AST_OPERATORS(Media_Query_Expression); - IMPLEMENT_AST_OPERATORS(Debug); - IMPLEMENT_AST_OPERATORS(Error); - IMPLEMENT_AST_OPERATORS(Warning); - IMPLEMENT_AST_OPERATORS(Assignment); - IMPLEMENT_AST_OPERATORS(Return); - IMPLEMENT_AST_OPERATORS(At_Root_Query); - IMPLEMENT_AST_OPERATORS(Variable); - IMPLEMENT_AST_OPERATORS(Comment); - IMPLEMENT_AST_OPERATORS(Attribute_Selector); - IMPLEMENT_AST_OPERATORS(Supports_Interpolation); - IMPLEMENT_AST_OPERATORS(Supports_Declaration); - IMPLEMENT_AST_OPERATORS(Supports_Condition); - IMPLEMENT_AST_OPERATORS(Parameters); - IMPLEMENT_AST_OPERATORS(Parameter); - IMPLEMENT_AST_OPERATORS(Arguments); - IMPLEMENT_AST_OPERATORS(Argument); - IMPLEMENT_AST_OPERATORS(Unary_Expression); - IMPLEMENT_AST_OPERATORS(Function_Call_Schema); - IMPLEMENT_AST_OPERATORS(Block); - IMPLEMENT_AST_OPERATORS(Content); - IMPLEMENT_AST_OPERATORS(Trace); - IMPLEMENT_AST_OPERATORS(Keyframe_Rule); - IMPLEMENT_AST_OPERATORS(Bubble); - IMPLEMENT_AST_OPERATORS(Selector_Schema); - IMPLEMENT_AST_OPERATORS(Placeholder_Selector); - IMPLEMENT_AST_OPERATORS(Definition); - IMPLEMENT_AST_OPERATORS(Declaration); -} diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp deleted file mode 100644 index a2be8685c..000000000 --- a/src/libsass/src/ast.hpp +++ /dev/null @@ -1,3049 +0,0 @@ -#ifndef SASS_AST_H -#define SASS_AST_H - -#include "sass.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#ifdef DEBUG_SHARED_PTR - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy(std::string, size_t) const = 0; \ - virtual klass##_Ptr clone(std::string, size_t) const = 0; \ - -#define ATTACH_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy(std::string, size_t) const; \ - virtual klass##_Ptr clone(std::string, size_t) const; \ - -#else - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy() const = 0; \ - virtual klass##_Ptr clone() const = 0; \ - -#define ATTACH_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy() const; \ - virtual klass##_Ptr clone() const; \ - -#endif - -#ifdef __clang__ - -/* - * There are some overloads used here that trigger the clang overload - * hiding warning. Specifically: - * - * Type type() which hides string type() from Expression - * - */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Woverloaded-virtual" - -#endif - -#include "util.hpp" -#include "units.hpp" -#include "context.hpp" -#include "position.hpp" -#include "constants.hpp" -#include "operation.hpp" -#include "position.hpp" -#include "inspect.hpp" -#include "source_map.hpp" -#include "environment.hpp" -#include "error_handling.hpp" -#include "ast_def_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "source_map.hpp" - -#include "sass.h" - -namespace Sass { - - // easier to search with name - const bool DELAYED = true; - - // ToDo: should this really be hardcoded - // Note: most methods follow precision option - const double NUMBER_EPSILON = 0.00000000000001; - - // macro to test if numbers are equal within a small error margin - #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON - - // ToDo: where does this fit best? - // We don't share this with C-API? - class Operand { - public: - Operand(Sass_OP operand, bool ws_before = false, bool ws_after = false) - : operand(operand), ws_before(ws_before), ws_after(ws_after) - { } - public: - enum Sass_OP operand; - bool ws_before; - bool ws_after; - }; - - ////////////////////////////////////////////////////////// - // `hash_combine` comes from boost (functional/hash): - // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html - // Boost Software License - Version 1.0 - // http://www.boost.org/users/license.html - template - void hash_combine (std::size_t& seed, const T& val) - { - seed ^= std::hash()(val) + 0x9e3779b9 - + (seed<<6) + (seed>>2); - } - ////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////// - // Abstract base class for all abstract syntax tree nodes. - ////////////////////////////////////////////////////////// - class AST_Node : public SharedObj { - ADD_PROPERTY(ParserState, pstate) - public: - AST_Node(ParserState pstate) - : pstate_(pstate) - { } - AST_Node(const AST_Node* ptr) - : pstate_(ptr->pstate_) - { } - - // AST_Node(AST_Node& ptr) = delete; - - virtual ~AST_Node() = 0; - virtual size_t hash() { return 0; } - ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node); - virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } - virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } - virtual const std::string to_string(Sass_Inspect_Options opt) const; - virtual const std::string to_string() const; - virtual void cloneChildren() {}; - // generic find function (not fully implemented yet) - // ToDo: add specific implementions to all children - virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; - public: - void update_pstate(const ParserState& pstate); - public: - Offset off() { return pstate(); } - Position pos() { return pstate(); } - ATTACH_OPERATIONS() - }; - inline AST_Node::~AST_Node() { } - - ////////////////////////////////////////////////////////////////////// - // define cast template now (need complete type) - ////////////////////////////////////////////////////////////////////// - - template - T* Cast(AST_Node* ptr) { - return ptr && typeid(T) == typeid(*ptr) ? - static_cast(ptr) : NULL; - }; - - template - const T* Cast(const AST_Node* ptr) { - return ptr && typeid(T) == typeid(*ptr) ? - static_cast(ptr) : NULL; - }; - - ////////////////////////////////////////////////////////////////////// - // Abstract base class for expressions. This side of the AST hierarchy - // represents elements in value contexts, which exist primarily to be - // evaluated and returned. - ////////////////////////////////////////////////////////////////////// - class Expression : public AST_Node { - public: - enum Concrete_Type { - NONE, - BOOLEAN, - NUMBER, - COLOR, - STRING, - LIST, - MAP, - SELECTOR, - NULL_VAL, - FUNCTION_VAL, - C_WARNING, - C_ERROR, - FUNCTION, - VARIABLE, - NUM_TYPES - }; - enum Simple_Type { - SIMPLE, - ATTR_SEL, - PSEUDO_SEL, - WRAPPED_SEL, - }; - private: - // expressions in some contexts shouldn't be evaluated - ADD_PROPERTY(bool, is_delayed) - ADD_PROPERTY(bool, is_expanded) - ADD_PROPERTY(bool, is_interpolant) - ADD_PROPERTY(Concrete_Type, concrete_type) - public: - Expression(ParserState pstate, - bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) - : AST_Node(pstate), - is_delayed_(d), - is_expanded_(e), - is_interpolant_(i), - concrete_type_(ct) - { } - Expression(const Expression* ptr) - : AST_Node(ptr), - is_delayed_(ptr->is_delayed_), - is_expanded_(ptr->is_expanded_), - is_interpolant_(ptr->is_interpolant_), - concrete_type_(ptr->concrete_type_) - { } - virtual operator bool() { return true; } - virtual ~Expression() { } - virtual std::string type() const { return ""; /* TODO: raise an error? */ } - virtual bool is_invisible() const { return false; } - static std::string type_name() { return ""; } - virtual bool is_false() { return false; } - // virtual bool is_true() { return !is_false(); } - virtual bool operator== (const Expression& rhs) const { return false; } - virtual bool eq(const Expression& rhs) const { return *this == rhs; }; - virtual void set_delayed(bool delayed) { is_delayed(delayed); } - virtual bool has_interpolant() const { return is_interpolant(); } - virtual bool is_left_interpolant() const { return is_interpolant(); } - virtual bool is_right_interpolant() const { return is_interpolant(); } - virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } - virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } - ATTACH_VIRTUAL_AST_OPERATIONS(Expression); - virtual size_t hash() { return 0; } - }; - - ////////////////////////////////////////////////////////////////////// - // Still just an expression, but with a to_string method - ////////////////////////////////////////////////////////////////////// - class PreValue : public Expression { - public: - PreValue(ParserState pstate, - bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) - : Expression(pstate, d, e, i, ct) - { } - PreValue(const PreValue* ptr) - : Expression(ptr) - { } - ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); - virtual ~PreValue() { } - }; - - ////////////////////////////////////////////////////////////////////// - // base class for values that support operations - ////////////////////////////////////////////////////////////////////// - class Value : public Expression { - public: - Value(ParserState pstate, - bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) - : Expression(pstate, d, e, i, ct) - { } - Value(const Value* ptr) - : Expression(ptr) - { } - ATTACH_VIRTUAL_AST_OPERATIONS(Value); - virtual bool operator== (const Expression& rhs) const = 0; - }; -} - -///////////////////////////////////////////////////////////////////////////////////// -// Hash method specializations for std::unordered_map to work with Sass::Expression -///////////////////////////////////////////////////////////////////////////////////// - -namespace std { - template<> - struct hash - { - size_t operator()(Sass::Expression_Obj s) const - { - return s->hash(); - } - }; - template<> - struct equal_to - { - bool operator()( Sass::Expression_Obj lhs, Sass::Expression_Obj rhs) const - { - return lhs->hash() == rhs->hash(); - } - }; -} - -namespace Sass { - - ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like vectors. Uses the - // "Template Method" design pattern to allow subclasses to adjust their flags - // when certain objects are pushed. - ///////////////////////////////////////////////////////////////////////////// - template - class Vectorized { - std::vector elements_; - protected: - size_t hash_; - void reset_hash() { hash_ = 0; } - virtual void adjust_after_pushing(T element) { } - public: - Vectorized(size_t s = 0) : elements_(std::vector()), hash_(0) - { elements_.reserve(s); } - virtual ~Vectorized() = 0; - size_t length() const { return elements_.size(); } - bool empty() const { return elements_.empty(); } - void clear() { return elements_.clear(); } - T last() const { return elements_.back(); } - T first() const { return elements_.front(); } - T& operator[](size_t i) { return elements_[i]; } - virtual const T& at(size_t i) const { return elements_.at(i); } - virtual T& at(size_t i) { return elements_.at(i); } - const T& operator[](size_t i) const { return elements_[i]; } - virtual void append(T element) - { - if (element) { - reset_hash(); - elements_.push_back(element); - adjust_after_pushing(element); - } - } - virtual void concat(Vectorized* v) - { - for (size_t i = 0, L = v->length(); i < L; ++i) this->append((*v)[i]); - } - Vectorized& unshift(T element) - { - elements_.insert(elements_.begin(), element); - return *this; - } - std::vector& elements() { return elements_; } - const std::vector& elements() const { return elements_; } - std::vector& elements(std::vector& e) { elements_ = e; return elements_; } - - virtual size_t hash() - { - if (hash_ == 0) { - for (T& el : elements_) { - hash_combine(hash_, el->hash()); - } - } - return hash_; - } - - typename std::vector::iterator end() { return elements_.end(); } - typename std::vector::iterator begin() { return elements_.begin(); } - typename std::vector::const_iterator end() const { return elements_.end(); } - typename std::vector::const_iterator begin() const { return elements_.begin(); } - typename std::vector::iterator erase(typename std::vector::iterator el) { return elements_.erase(el); } - typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { return elements_.erase(el); } - - }; - template - inline Vectorized::~Vectorized() { } - - ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like a hash table. Uses an - // extra internally to maintain insertion order for interation. - ///////////////////////////////////////////////////////////////////////////// - class Hashed { - private: - ExpressionMap elements_; - std::vector list_; - protected: - size_t hash_; - Expression_Obj duplicate_key_; - void reset_hash() { hash_ = 0; } - void reset_duplicate_key() { duplicate_key_ = 0; } - virtual void adjust_after_pushing(std::pair p) { } - public: - Hashed(size_t s = 0) - : elements_(ExpressionMap(s)), - list_(std::vector()), - hash_(0), duplicate_key_(NULL) - { elements_.reserve(s); list_.reserve(s); } - virtual ~Hashed(); - size_t length() const { return list_.size(); } - bool empty() const { return list_.empty(); } - bool has(Expression_Obj k) const { return elements_.count(k) == 1; } - Expression_Obj at(Expression_Obj k) const; - bool has_duplicate_key() const { return duplicate_key_ != 0; } - Expression_Obj get_duplicate_key() const { return duplicate_key_; } - const ExpressionMap elements() { return elements_; } - Hashed& operator<<(std::pair p) - { - reset_hash(); - - if (!has(p.first)) list_.push_back(p.first); - else if (!duplicate_key_) duplicate_key_ = p.first; - - elements_[p.first] = p.second; - - adjust_after_pushing(p); - return *this; - } - Hashed& operator+=(Hashed* h) - { - if (length() == 0) { - this->elements_ = h->elements_; - this->list_ = h->list_; - return *this; - } - - for (auto key : h->keys()) { - *this << std::make_pair(key, h->at(key)); - } - - reset_duplicate_key(); - return *this; - } - const ExpressionMap& pairs() const { return elements_; } - const std::vector& keys() const { return list_; } - -// std::unordered_map::iterator end() { return elements_.end(); } -// std::unordered_map::iterator begin() { return elements_.begin(); } -// std::unordered_map::const_iterator end() const { return elements_.end(); } -// std::unordered_map::const_iterator begin() const { return elements_.begin(); } - - }; - inline Hashed::~Hashed() { } - - - ///////////////////////////////////////////////////////////////////////// - // Abstract base class for statements. This side of the AST hierarchy - // represents elements in expansion contexts, which exist primarily to be - // rewritten and macro-expanded. - ///////////////////////////////////////////////////////////////////////// - class Statement : public AST_Node { - public: - enum Statement_Type { - NONE, - RULESET, - MEDIA, - DIRECTIVE, - SUPPORTS, - ATROOT, - BUBBLE, - CONTENT, - KEYFRAMERULE, - DECLARATION, - ASSIGNMENT, - IMPORT_STUB, - IMPORT, - COMMENT, - WARNING, - RETURN, - EXTEND, - ERROR, - DEBUGSTMT, - WHILE, - EACH, - FOR, - IF - }; - private: - ADD_PROPERTY(Statement_Type, statement_type) - ADD_PROPERTY(size_t, tabs) - ADD_PROPERTY(bool, group_end) - public: - Statement(ParserState pstate, Statement_Type st = NONE, size_t t = 0) - : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false) - { } - Statement(const Statement* ptr) - : AST_Node(ptr), - statement_type_(ptr->statement_type_), - tabs_(ptr->tabs_), - group_end_(ptr->group_end_) - { } - virtual ~Statement() = 0; - // needed for rearranging nested rulesets during CSS emission - virtual bool is_invisible() const { return false; } - virtual bool bubbles() { return false; } - virtual bool has_content() - { - return statement_type_ == CONTENT; - } - }; - inline Statement::~Statement() { } - - //////////////////////// - // Blocks of statements. - //////////////////////// - class Block : public Statement, public Vectorized { - ADD_PROPERTY(bool, is_root) - // needed for properly formatted CSS emission - protected: - void adjust_after_pushing(Statement_Obj s) - { - } - public: - Block(ParserState pstate, size_t s = 0, bool r = false) - : Statement(pstate), - Vectorized(s), - is_root_(r) - { } - Block(const Block* ptr) - : Statement(ptr), - Vectorized(*ptr), - is_root_(ptr->is_root_) - { } - virtual bool has_content() - { - for (size_t i = 0, L = elements().size(); i < L; ++i) { - if (elements()[i]->has_content()) return true; - } - return Statement::has_content(); - } - ATTACH_AST_OPERATIONS(Block) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////// - // Abstract base class for statements that contain blocks of statements. - //////////////////////////////////////////////////////////////////////// - class Has_Block : public Statement { - ADD_PROPERTY(Block_Obj, block) - public: - Has_Block(ParserState pstate, Block_Obj b) - : Statement(pstate), block_(b) - { } - Has_Block(const Has_Block* ptr) - : Statement(ptr), block_(ptr->block_) - { } - virtual bool has_content() - { - return (block_ && block_->has_content()) || Statement::has_content(); - } - virtual ~Has_Block() = 0; - }; - inline Has_Block::~Has_Block() { } - - ///////////////////////////////////////////////////////////////////////////// - // Rulesets (i.e., sets of styles headed by a selector and containing a block - // of style declarations. - ///////////////////////////////////////////////////////////////////////////// - class Ruleset : public Has_Block { - ADD_PROPERTY(Selector_List_Obj, selector) - ADD_PROPERTY(bool, is_root); - public: - Ruleset(ParserState pstate, Selector_List_Obj s = 0, Block_Obj b = 0) - : Has_Block(pstate, b), selector_(s), is_root_(false) - { statement_type(RULESET); } - Ruleset(const Ruleset* ptr) - : Has_Block(ptr), - selector_(ptr->selector_), - is_root_(ptr->is_root_) - { statement_type(RULESET); } - bool is_invisible() const; - ATTACH_AST_OPERATIONS(Ruleset) - ATTACH_OPERATIONS() - }; - - ///////////////// - // Bubble. - ///////////////// - class Bubble : public Statement { - ADD_PROPERTY(Statement_Obj, node) - ADD_PROPERTY(bool, group_end) - public: - Bubble(ParserState pstate, Statement_Obj n, Statement_Obj g = 0, size_t t = 0) - : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == 0) - { } - Bubble(const Bubble* ptr) - : Statement(ptr), - node_(ptr->node_), - group_end_(ptr->group_end_) - { } - bool bubbles() { return true; } - ATTACH_AST_OPERATIONS(Bubble) - ATTACH_OPERATIONS() - }; - - ///////////////// - // Trace. - ///////////////// - class Trace : public Has_Block { - ADD_CONSTREF(char, type) - ADD_CONSTREF(std::string, name) - public: - Trace(ParserState pstate, std::string n, Block_Obj b = 0, char type = 'm') - : Has_Block(pstate, b), type_(type), name_(n) - { } - Trace(const Trace* ptr) - : Has_Block(ptr), - type_(ptr->type_), - name_(ptr->name_) - { } - ATTACH_AST_OPERATIONS(Trace) - ATTACH_OPERATIONS() - }; - - ///////////////// - // Media queries. - ///////////////// - class Media_Block : public Has_Block { - ADD_PROPERTY(List_Obj, media_queries) - public: - Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b) - : Has_Block(pstate, b), media_queries_(mqs) - { statement_type(MEDIA); } - Media_Block(const Media_Block* ptr) - : Has_Block(ptr), media_queries_(ptr->media_queries_) - { statement_type(MEDIA); } - bool bubbles() { return true; } - bool is_invisible() const; - ATTACH_AST_OPERATIONS(Media_Block) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////////////////////////// - // At-rules -- arbitrary directives beginning with "@" that may have an - // optional statement block. - /////////////////////////////////////////////////////////////////////// - class Directive : public Has_Block { - ADD_CONSTREF(std::string, keyword) - ADD_PROPERTY(Selector_List_Obj, selector) - ADD_PROPERTY(Expression_Obj, value) - public: - Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0) - : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed - { statement_type(DIRECTIVE); } - Directive(const Directive* ptr) - : Has_Block(ptr), - keyword_(ptr->keyword_), - selector_(ptr->selector_), - value_(ptr->value_) // set value manually if needed - { statement_type(DIRECTIVE); } - bool bubbles() { return is_keyframes() || is_media(); } - bool is_media() { - return keyword_.compare("@-webkit-media") == 0 || - keyword_.compare("@-moz-media") == 0 || - keyword_.compare("@-o-media") == 0 || - keyword_.compare("@media") == 0; - } - bool is_keyframes() { - return keyword_.compare("@-webkit-keyframes") == 0 || - keyword_.compare("@-moz-keyframes") == 0 || - keyword_.compare("@-o-keyframes") == 0 || - keyword_.compare("@keyframes") == 0; - } - ATTACH_AST_OPERATIONS(Directive) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////////////////////////// - // Keyframe-rules -- the child blocks of "@keyframes" nodes. - /////////////////////////////////////////////////////////////////////// - class Keyframe_Rule : public Has_Block { - // according to css spec, this should be - // = | - ADD_PROPERTY(Selector_List_Obj, name) - public: - Keyframe_Rule(ParserState pstate, Block_Obj b) - : Has_Block(pstate, b), name_() - { statement_type(KEYFRAMERULE); } - Keyframe_Rule(const Keyframe_Rule* ptr) - : Has_Block(ptr), name_(ptr->name_) - { statement_type(KEYFRAMERULE); } - ATTACH_AST_OPERATIONS(Keyframe_Rule) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////// - // Declarations -- style rules consisting of a property name and values. - //////////////////////////////////////////////////////////////////////// - class Declaration : public Has_Block { - ADD_PROPERTY(String_Obj, property) - ADD_PROPERTY(Expression_Obj, value) - ADD_PROPERTY(bool, is_important) - ADD_PROPERTY(bool, is_custom_property) - ADD_PROPERTY(bool, is_indented) - public: - Declaration(ParserState pstate, - String_Obj prop, Expression_Obj val, bool i = false, bool c = false, Block_Obj b = 0) - : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) - { statement_type(DECLARATION); } - Declaration(const Declaration* ptr) - : Has_Block(ptr), - property_(ptr->property_), - value_(ptr->value_), - is_important_(ptr->is_important_), - is_custom_property_(ptr->is_custom_property_), - is_indented_(ptr->is_indented_) - { statement_type(DECLARATION); } - virtual bool is_invisible() const; - ATTACH_AST_OPERATIONS(Declaration) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////// - // Assignments -- variable and value. - ///////////////////////////////////// - class Assignment : public Statement { - ADD_CONSTREF(std::string, variable) - ADD_PROPERTY(Expression_Obj, value) - ADD_PROPERTY(bool, is_default) - ADD_PROPERTY(bool, is_global) - public: - Assignment(ParserState pstate, - std::string var, Expression_Obj val, - bool is_default = false, - bool is_global = false) - : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global) - { statement_type(ASSIGNMENT); } - Assignment(const Assignment* ptr) - : Statement(ptr), - variable_(ptr->variable_), - value_(ptr->value_), - is_default_(ptr->is_default_), - is_global_(ptr->is_global_) - { statement_type(ASSIGNMENT); } - ATTACH_AST_OPERATIONS(Assignment) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Import directives. CSS and Sass import lists can be intermingled, so it's - // necessary to store a list of each in an Import node. - //////////////////////////////////////////////////////////////////////////// - class Import : public Statement { - std::vector urls_; - std::vector incs_; - ADD_PROPERTY(List_Obj, import_queries); - public: - Import(ParserState pstate) - : Statement(pstate), - urls_(std::vector()), - incs_(std::vector()), - import_queries_() - { statement_type(IMPORT); } - Import(const Import* ptr) - : Statement(ptr), - urls_(ptr->urls_), - incs_(ptr->incs_), - import_queries_(ptr->import_queries_) - { statement_type(IMPORT); } - std::vector& urls() { return urls_; } - std::vector& incs() { return incs_; } - ATTACH_AST_OPERATIONS(Import) - ATTACH_OPERATIONS() - }; - - // not yet resolved single import - // so far we only know requested name - class Import_Stub : public Statement { - Include resource_; - public: - std::string abs_path() { return resource_.abs_path; }; - std::string imp_path() { return resource_.imp_path; }; - Include resource() { return resource_; }; - - Import_Stub(ParserState pstate, Include res) - : Statement(pstate), resource_(res) - { statement_type(IMPORT_STUB); } - Import_Stub(const Import_Stub* ptr) - : Statement(ptr), resource_(ptr->resource_) - { statement_type(IMPORT_STUB); } - ATTACH_AST_OPERATIONS(Import_Stub) - ATTACH_OPERATIONS() - }; - - ////////////////////////////// - // The Sass `@warn` directive. - ////////////////////////////// - class Warning : public Statement { - ADD_PROPERTY(Expression_Obj, message) - public: - Warning(ParserState pstate, Expression_Obj msg) - : Statement(pstate), message_(msg) - { statement_type(WARNING); } - Warning(const Warning* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(WARNING); } - ATTACH_AST_OPERATIONS(Warning) - ATTACH_OPERATIONS() - }; - - /////////////////////////////// - // The Sass `@error` directive. - /////////////////////////////// - class Error : public Statement { - ADD_PROPERTY(Expression_Obj, message) - public: - Error(ParserState pstate, Expression_Obj msg) - : Statement(pstate), message_(msg) - { statement_type(ERROR); } - Error(const Error* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(ERROR); } - ATTACH_AST_OPERATIONS(Error) - ATTACH_OPERATIONS() - }; - - /////////////////////////////// - // The Sass `@debug` directive. - /////////////////////////////// - class Debug : public Statement { - ADD_PROPERTY(Expression_Obj, value) - public: - Debug(ParserState pstate, Expression_Obj val) - : Statement(pstate), value_(val) - { statement_type(DEBUGSTMT); } - Debug(const Debug* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(DEBUGSTMT); } - ATTACH_AST_OPERATIONS(Debug) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////// - // CSS comments. These may be interpolated. - /////////////////////////////////////////// - class Comment : public Statement { - ADD_PROPERTY(String_Obj, text) - ADD_PROPERTY(bool, is_important) - public: - Comment(ParserState pstate, String_Obj txt, bool is_important) - : Statement(pstate), text_(txt), is_important_(is_important) - { statement_type(COMMENT); } - Comment(const Comment* ptr) - : Statement(ptr), - text_(ptr->text_), - is_important_(ptr->is_important_) - { statement_type(COMMENT); } - virtual bool is_invisible() const - { return /* is_important() == */ false; } - ATTACH_AST_OPERATIONS(Comment) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////// - // The Sass `@if` control directive. - //////////////////////////////////// - class If : public Has_Block { - ADD_PROPERTY(Expression_Obj, predicate) - ADD_PROPERTY(Block_Obj, alternative) - public: - If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0) - : Has_Block(pstate, con), predicate_(pred), alternative_(alt) - { statement_type(IF); } - If(const If* ptr) - : Has_Block(ptr), - predicate_(ptr->predicate_), - alternative_(ptr->alternative_) - { statement_type(IF); } - virtual bool has_content() - { - return Has_Block::has_content() || (alternative_ && alternative_->has_content()); - } - ATTACH_AST_OPERATIONS(If) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////// - // The Sass `@for` control directive. - ///////////////////////////////////// - class For : public Has_Block { - ADD_CONSTREF(std::string, variable) - ADD_PROPERTY(Expression_Obj, lower_bound) - ADD_PROPERTY(Expression_Obj, upper_bound) - ADD_PROPERTY(bool, is_inclusive) - public: - For(ParserState pstate, - std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc) - : Has_Block(pstate, b), - variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) - { statement_type(FOR); } - For(const For* ptr) - : Has_Block(ptr), - variable_(ptr->variable_), - lower_bound_(ptr->lower_bound_), - upper_bound_(ptr->upper_bound_), - is_inclusive_(ptr->is_inclusive_) - { statement_type(FOR); } - ATTACH_AST_OPERATIONS(For) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////// - // The Sass `@each` control directive. - ////////////////////////////////////// - class Each : public Has_Block { - ADD_PROPERTY(std::vector, variables) - ADD_PROPERTY(Expression_Obj, list) - public: - Each(ParserState pstate, std::vector vars, Expression_Obj lst, Block_Obj b) - : Has_Block(pstate, b), variables_(vars), list_(lst) - { statement_type(EACH); } - Each(const Each* ptr) - : Has_Block(ptr), variables_(ptr->variables_), list_(ptr->list_) - { statement_type(EACH); } - ATTACH_AST_OPERATIONS(Each) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////// - // The Sass `@while` control directive. - /////////////////////////////////////// - class While : public Has_Block { - ADD_PROPERTY(Expression_Obj, predicate) - public: - While(ParserState pstate, Expression_Obj pred, Block_Obj b) - : Has_Block(pstate, b), predicate_(pred) - { statement_type(WHILE); } - While(const While* ptr) - : Has_Block(ptr), predicate_(ptr->predicate_) - { statement_type(WHILE); } - ATTACH_AST_OPERATIONS(While) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////// - // The @return directive for use inside SassScript functions. - ///////////////////////////////////////////////////////////// - class Return : public Statement { - ADD_PROPERTY(Expression_Obj, value) - public: - Return(ParserState pstate, Expression_Obj val) - : Statement(pstate), value_(val) - { statement_type(RETURN); } - Return(const Return* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(RETURN); } - ATTACH_AST_OPERATIONS(Return) - ATTACH_OPERATIONS() - }; - - //////////////////////////////// - // The Sass `@extend` directive. - //////////////////////////////// - class Extension : public Statement { - ADD_PROPERTY(Selector_List_Obj, selector) - public: - Extension(ParserState pstate, Selector_List_Obj s) - : Statement(pstate), selector_(s) - { statement_type(EXTEND); } - Extension(const Extension* ptr) - : Statement(ptr), selector_(ptr->selector_) - { statement_type(EXTEND); } - ATTACH_AST_OPERATIONS(Extension) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////////////////////// - // Definitions for both mixins and functions. The two cases are distinguished - // by a type tag. - ///////////////////////////////////////////////////////////////////////////// - struct Backtrace; - typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); - class Definition : public Has_Block { - public: - enum Type { MIXIN, FUNCTION }; - ADD_CONSTREF(std::string, name) - ADD_PROPERTY(Parameters_Obj, parameters) - ADD_PROPERTY(Env*, environment) - ADD_PROPERTY(Type, type) - ADD_PROPERTY(Native_Function, native_function) - ADD_PROPERTY(Sass_Function_Entry, c_function) - ADD_PROPERTY(void*, cookie) - ADD_PROPERTY(bool, is_overload_stub) - ADD_PROPERTY(Signature, signature) - public: - Definition(const Definition* ptr) - : Has_Block(ptr), - name_(ptr->name_), - parameters_(ptr->parameters_), - environment_(ptr->environment_), - type_(ptr->type_), - native_function_(ptr->native_function_), - c_function_(ptr->c_function_), - cookie_(ptr->cookie_), - is_overload_stub_(ptr->is_overload_stub_), - signature_(ptr->signature_) - { } - - Definition(ParserState pstate, - std::string n, - Parameters_Obj params, - Block_Obj b, - Type t) - : Has_Block(pstate, b), - name_(n), - parameters_(params), - environment_(0), - type_(t), - native_function_(0), - c_function_(0), - cookie_(0), - is_overload_stub_(false), - signature_(0) - { } - Definition(ParserState pstate, - Signature sig, - std::string n, - Parameters_Obj params, - Native_Function func_ptr, - bool overload_stub = false) - : Has_Block(pstate, 0), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(func_ptr), - c_function_(0), - cookie_(0), - is_overload_stub_(overload_stub), - signature_(sig) - { } - Definition(ParserState pstate, - Signature sig, - std::string n, - Parameters_Obj params, - Sass_Function_Entry c_func, - bool whatever, - bool whatever2) - : Has_Block(pstate, 0), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(0), - c_function_(c_func), - cookie_(sass_function_get_cookie(c_func)), - is_overload_stub_(false), - signature_(sig) - { } - ATTACH_AST_OPERATIONS(Definition) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////// - // Mixin calls (i.e., `@include ...`). - ////////////////////////////////////// - class Mixin_Call : public Has_Block { - ADD_CONSTREF(std::string, name) - ADD_PROPERTY(Arguments_Obj, arguments) - public: - Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0) - : Has_Block(pstate, b), name_(n), arguments_(args) - { } - Mixin_Call(const Mixin_Call* ptr) - : Has_Block(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_) - { } - ATTACH_AST_OPERATIONS(Mixin_Call) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////// - // The @content directive for mixin content blocks. - /////////////////////////////////////////////////// - class Content : public Statement { - ADD_PROPERTY(Media_Block_Ptr, media_block) - public: - Content(ParserState pstate) - : Statement(pstate), - media_block_(NULL) - { statement_type(CONTENT); } - Content(const Content* ptr) - : Statement(ptr), - media_block_(ptr->media_block_) - { statement_type(CONTENT); } - ATTACH_AST_OPERATIONS(Content) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////////////////////////// - // Lists of values, both comma- and space-separated (distinguished by a - // type-tag.) Also used to represent variable-length argument lists. - /////////////////////////////////////////////////////////////////////// - class List : public Value, public Vectorized { - void adjust_after_pushing(Expression_Obj e) { is_expanded(false); } - private: - ADD_PROPERTY(enum Sass_Separator, separator) - ADD_PROPERTY(bool, is_arglist) - ADD_PROPERTY(bool, is_bracketed) - ADD_PROPERTY(bool, from_selector) - public: - List(ParserState pstate, - size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false) - : Value(pstate), - Vectorized(size), - separator_(sep), - is_arglist_(argl), - is_bracketed_(bracket), - from_selector_(false) - { concrete_type(LIST); } - List(const List* ptr) - : Value(ptr), - Vectorized(*ptr), - separator_(ptr->separator_), - is_arglist_(ptr->is_arglist_), - is_bracketed_(ptr->is_bracketed_), - from_selector_(ptr->from_selector_) - { concrete_type(LIST); } - std::string type() const { return is_arglist_ ? "arglist" : "list"; } - static std::string type_name() { return "list"; } - const char* sep_string(bool compressed = false) const { - return separator() == SASS_SPACE ? - " " : (compressed ? "," : ", "); - } - bool is_invisible() const { return empty() && !is_bracketed(); } - Expression_Obj value_at_index(size_t i); - - virtual size_t size() const; - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(sep_string()); - hash_combine(hash_, std::hash()(is_bracketed())); - for (size_t i = 0, L = length(); i < L; ++i) - hash_combine(hash_, (elements()[i])->hash()); - } - return hash_; - } - - virtual void set_delayed(bool delayed) - { - is_delayed(delayed); - // don't set children - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(List) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////////////////////////// - // Key value paris. - /////////////////////////////////////////////////////////////////////// - class Map : public Value, public Hashed { - void adjust_after_pushing(std::pair p) { is_expanded(false); } - public: - Map(ParserState pstate, - size_t size = 0) - : Value(pstate), - Hashed(size) - { concrete_type(MAP); } - Map(const Map* ptr) - : Value(ptr), - Hashed(*ptr) - { concrete_type(MAP); } - std::string type() const { return "map"; } - static std::string type_name() { return "map"; } - bool is_invisible() const { return empty(); } - List_Obj to_list(ParserState& pstate); - - virtual size_t hash() - { - if (hash_ == 0) { - for (auto key : keys()) { - hash_combine(hash_, key->hash()); - hash_combine(hash_, at(key)->hash()); - } - } - - return hash_; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Map) - ATTACH_OPERATIONS() - }; - - inline static const std::string sass_op_to_name(enum Sass_OP op) { - switch (op) { - case AND: return "and"; - case OR: return "or"; - case EQ: return "eq"; - case NEQ: return "neq"; - case GT: return "gt"; - case GTE: return "gte"; - case LT: return "lt"; - case LTE: return "lte"; - case ADD: return "plus"; - case SUB: return "sub"; - case MUL: return "times"; - case DIV: return "div"; - case MOD: return "mod"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } - } - - inline static const std::string sass_op_separator(enum Sass_OP op) { - switch (op) { - case AND: return "&&"; - case OR: return "||"; - case EQ: return "=="; - case NEQ: return "!="; - case GT: return ">"; - case GTE: return ">="; - case LT: return "<"; - case LTE: return "<="; - case ADD: return "+"; - case SUB: return "-"; - case MUL: return "*"; - case DIV: return "/"; - case MOD: return "%"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } - } - - ////////////////////////////////////////////////////////////////////////// - // Binary expressions. Represents logical, relational, and arithmetic - // operations. Templatized to avoid large switch statements and repetitive - // subclassing. - ////////////////////////////////////////////////////////////////////////// - class Binary_Expression : public PreValue { - private: - HASH_PROPERTY(Operand, op) - HASH_PROPERTY(Expression_Obj, left) - HASH_PROPERTY(Expression_Obj, right) - size_t hash_; - public: - Binary_Expression(ParserState pstate, - Operand op, Expression_Obj lhs, Expression_Obj rhs) - : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) - { } - Binary_Expression(const Binary_Expression* ptr) - : PreValue(ptr), - op_(ptr->op_), - left_(ptr->left_), - right_(ptr->right_), - hash_(ptr->hash_) - { } - const std::string type_name() { - return sass_op_to_name(optype()); - } - const std::string separator() { - return sass_op_separator(optype()); - } - bool is_left_interpolant(void) const; - bool is_right_interpolant(void) const; - bool has_interpolant() const - { - return is_left_interpolant() || - is_right_interpolant(); - } - virtual void set_delayed(bool delayed) - { - right()->set_delayed(delayed); - left()->set_delayed(delayed); - is_delayed(delayed); - } - virtual bool operator==(const Expression& rhs) const - { - try - { - Binary_Expression_Ptr_Const m = Cast(&rhs); - if (m == 0) return false; - return type() == m->type() && - *left() == *m->left() && - *right() == *m->right(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(optype()); - hash_combine(hash_, left()->hash()); - hash_combine(hash_, right()->hash()); - } - return hash_; - } - enum Sass_OP optype() const { return op_.operand; } - ATTACH_AST_OPERATIONS(Binary_Expression) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Arithmetic negation (logical negation is just an ordinary function call). - //////////////////////////////////////////////////////////////////////////// - class Unary_Expression : public Expression { - public: - enum Type { PLUS, MINUS, NOT, SLASH }; - private: - HASH_PROPERTY(Type, optype) - HASH_PROPERTY(Expression_Obj, operand) - size_t hash_; - public: - Unary_Expression(ParserState pstate, Type t, Expression_Obj o) - : Expression(pstate), optype_(t), operand_(o), hash_(0) - { } - Unary_Expression(const Unary_Expression* ptr) - : Expression(ptr), - optype_(ptr->optype_), - operand_(ptr->operand_), - hash_(ptr->hash_) - { } - const std::string type_name() { - switch (optype_) { - case PLUS: return "plus"; - case MINUS: return "minus"; - case SLASH: return "slash"; - case NOT: return "not"; - default: return "invalid"; - } - } - virtual bool operator==(const Expression& rhs) const - { - try - { - Unary_Expression_Ptr_Const m = Cast(&rhs); - if (m == 0) return false; - return type() == m->type() && - *operand() == *m->operand(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(optype_); - hash_combine(hash_, operand()->hash()); - }; - return hash_; - } - ATTACH_AST_OPERATIONS(Unary_Expression) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////// - // Individual argument objects for mixin and function calls. - //////////////////////////////////////////////////////////// - class Argument : public Expression { - HASH_PROPERTY(Expression_Obj, value) - HASH_CONSTREF(std::string, name) - ADD_PROPERTY(bool, is_rest_argument) - ADD_PROPERTY(bool, is_keyword_argument) - size_t hash_; - public: - Argument(ParserState pstate, Expression_Obj val, std::string n = "", bool rest = false, bool keyword = false) - : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) - { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } - } - Argument(const Argument* ptr) - : Expression(ptr), - value_(ptr->value_), - name_(ptr->name_), - is_rest_argument_(ptr->is_rest_argument_), - is_keyword_argument_(ptr->is_keyword_argument_), - hash_(ptr->hash_) - { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } - } - - virtual void set_delayed(bool delayed); - virtual bool operator==(const Expression& rhs) const - { - try - { - Argument_Ptr_Const m = Cast(&rhs); - if (!(m && name() == m->name())) return false; - return *value() == *m->value(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(name()); - hash_combine(hash_, value()->hash()); - } - return hash_; - } - - ATTACH_AST_OPERATIONS(Argument) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////// - // Argument lists -- in their own class to facilitate context-sensitive - // error checking (e.g., ensuring that all ordinal arguments precede all - // named arguments). - //////////////////////////////////////////////////////////////////////// - class Arguments : public Expression, public Vectorized { - ADD_PROPERTY(bool, has_named_arguments) - ADD_PROPERTY(bool, has_rest_argument) - ADD_PROPERTY(bool, has_keyword_argument) - protected: - void adjust_after_pushing(Argument_Obj a); - public: - Arguments(ParserState pstate) - : Expression(pstate), - Vectorized(), - has_named_arguments_(false), - has_rest_argument_(false), - has_keyword_argument_(false) - { } - Arguments(const Arguments* ptr) - : Expression(ptr), - Vectorized(*ptr), - has_named_arguments_(ptr->has_named_arguments_), - has_rest_argument_(ptr->has_rest_argument_), - has_keyword_argument_(ptr->has_keyword_argument_) - { } - - virtual void set_delayed(bool delayed); - - Argument_Obj get_rest_argument(); - Argument_Obj get_keyword_argument(); - - ATTACH_AST_OPERATIONS(Arguments) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////// - // Function reference. - //////////////////////////////////////////////////// - class Function : public Value { - public: - ADD_PROPERTY(Definition_Obj, definition) - ADD_PROPERTY(bool, is_css) - public: - Function(ParserState pstate, Definition_Obj def, bool css) - : Value(pstate), definition_(def), is_css_(css) - { concrete_type(FUNCTION_VAL); } - Function(const Function* ptr) - : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) - { concrete_type(FUNCTION_VAL); } - - std::string type() const { return "function"; } - static std::string type_name() { return "function"; } - bool is_invisible() const { return true; } - - std::string name() { - if (definition_) { - return definition_->name(); - } - return ""; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Function) - ATTACH_OPERATIONS() - }; - - ////////////////// - // Function calls. - ////////////////// - class Function_Call : public PreValue { - HASH_CONSTREF(std::string, name) - HASH_PROPERTY(Arguments_Obj, arguments) - HASH_PROPERTY(Function_Obj, func) - ADD_PROPERTY(bool, via_call) - ADD_PROPERTY(void*, cookie) - size_t hash_; - public: - Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie) - : PreValue(pstate), name_(n), arguments_(args), func_(0), via_call_(false), cookie_(cookie), hash_(0) - { concrete_type(FUNCTION); } - Function_Call(ParserState pstate, std::string n, Arguments_Obj args, Function_Obj func) - : PreValue(pstate), name_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call(ParserState pstate, std::string n, Arguments_Obj args) - : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call(const Function_Call* ptr) - : PreValue(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_), - func_(ptr->func_), - via_call_(ptr->via_call_), - cookie_(ptr->cookie_), - hash_(ptr->hash_) - { concrete_type(FUNCTION); } - - bool is_css() { - if (func_) return func_->is_css(); - return false; - } - - virtual bool operator==(const Expression& rhs) const - { - try - { - Function_Call_Ptr_Const m = Cast(&rhs); - if (!(m && name() == m->name())) return false; - if (!(m && arguments()->length() == m->arguments()->length())) return false; - for (size_t i =0, L = arguments()->length(); i < L; ++i) - if (!(*(*arguments())[i] == *(*m->arguments())[i])) return false; - return true; - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(name()); - for (auto argument : arguments()->elements()) - hash_combine(hash_, argument->hash()); - } - return hash_; - } - ATTACH_AST_OPERATIONS(Function_Call) - ATTACH_OPERATIONS() - }; - - ///////////////////////// - // Function call schemas. - ///////////////////////// - class Function_Call_Schema : public Expression { - ADD_PROPERTY(String_Obj, name) - ADD_PROPERTY(Arguments_Obj, arguments) - public: - Function_Call_Schema(ParserState pstate, String_Obj n, Arguments_Obj args) - : Expression(pstate), name_(n), arguments_(args) - { concrete_type(STRING); } - Function_Call_Schema(const Function_Call_Schema* ptr) - : Expression(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_) - { concrete_type(STRING); } - ATTACH_AST_OPERATIONS(Function_Call_Schema) - ATTACH_OPERATIONS() - }; - - /////////////////////// - // Variable references. - /////////////////////// - class Variable : public PreValue { - ADD_CONSTREF(std::string, name) - public: - Variable(ParserState pstate, std::string n) - : PreValue(pstate), name_(n) - { concrete_type(VARIABLE); } - Variable(const Variable* ptr) - : PreValue(ptr), name_(ptr->name_) - { concrete_type(VARIABLE); } - - virtual bool operator==(const Expression& rhs) const - { - try - { - Variable_Ptr_Const e = Cast(&rhs); - return e && name() == e->name(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - - virtual size_t hash() - { - return std::hash()(name()); - } - - ATTACH_AST_OPERATIONS(Variable) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////// - // Numbers, percentages, dimensions, and colors. - //////////////////////////////////////////////// - class Number : public Value, public Units { - HASH_PROPERTY(double, value) - ADD_PROPERTY(bool, zero) - size_t hash_; - public: - Number(ParserState pstate, double val, std::string u = "", bool zero = true); - - Number(const Number* ptr) - : Value(ptr), - Units(ptr), - value_(ptr->value_), zero_(ptr->zero_), - hash_(ptr->hash_) - { concrete_type(NUMBER); } - - bool zero() { return zero_; } - std::string type() const { return "number"; } - static std::string type_name() { return "number"; } - - void reduce(); - void normalize(); - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(value_); - for (const auto numerator : numerators) - hash_combine(hash_, std::hash()(numerator)); - for (const auto denominator : denominators) - hash_combine(hash_, std::hash()(denominator)); - } - return hash_; - } - - virtual bool operator< (const Number& rhs) const; - virtual bool operator== (const Number& rhs) const; - virtual bool operator== (const Expression& rhs) const; - ATTACH_AST_OPERATIONS(Number) - ATTACH_OPERATIONS() - }; - - ////////// - // Colors. - ////////// - class Color : public Value { - HASH_PROPERTY(double, r) - HASH_PROPERTY(double, g) - HASH_PROPERTY(double, b) - HASH_PROPERTY(double, a) - ADD_CONSTREF(std::string, disp) - size_t hash_; - public: - Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "") - : Value(pstate), r_(r), g_(g), b_(b), a_(a), disp_(disp), - hash_(0) - { concrete_type(COLOR); } - Color(const Color* ptr) - : Value(ptr), - r_(ptr->r_), - g_(ptr->g_), - b_(ptr->b_), - a_(ptr->a_), - disp_(ptr->disp_), - hash_(ptr->hash_) - { concrete_type(COLOR); } - std::string type() const { return "color"; } - static std::string type_name() { return "color"; } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(a_); - hash_combine(hash_, std::hash()(r_)); - hash_combine(hash_, std::hash()(g_)); - hash_combine(hash_, std::hash()(b_)); - } - return hash_; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Color) - ATTACH_OPERATIONS() - }; - - ////////////////////////////// - // Errors from Sass_Values. - ////////////////////////////// - class Custom_Error : public Value { - ADD_CONSTREF(std::string, message) - public: - Custom_Error(ParserState pstate, std::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_ERROR); } - Custom_Error(const Custom_Error* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_ERROR); } - virtual bool operator== (const Expression& rhs) const; - ATTACH_AST_OPERATIONS(Custom_Error) - ATTACH_OPERATIONS() - }; - - ////////////////////////////// - // Warnings from Sass_Values. - ////////////////////////////// - class Custom_Warning : public Value { - ADD_CONSTREF(std::string, message) - public: - Custom_Warning(ParserState pstate, std::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_WARNING); } - Custom_Warning(const Custom_Warning* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_WARNING); } - virtual bool operator== (const Expression& rhs) const; - ATTACH_AST_OPERATIONS(Custom_Warning) - ATTACH_OPERATIONS() - }; - - //////////// - // Booleans. - //////////// - class Boolean : public Value { - HASH_PROPERTY(bool, value) - size_t hash_; - public: - Boolean(ParserState pstate, bool val) - : Value(pstate), value_(val), - hash_(0) - { concrete_type(BOOLEAN); } - Boolean(const Boolean* ptr) - : Value(ptr), - value_(ptr->value_), - hash_(ptr->hash_) - { concrete_type(BOOLEAN); } - virtual operator bool() { return value_; } - std::string type() const { return "bool"; } - static std::string type_name() { return "bool"; } - virtual bool is_false() { return !value_; } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(value_); - } - return hash_; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Boolean) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////// - // Abstract base class for Sass string values. Includes interpolated and - // "flat" strings. - //////////////////////////////////////////////////////////////////////// - class String : public Value { - public: - String(ParserState pstate, bool delayed = false) - : Value(pstate, delayed) - { concrete_type(STRING); } - String(const String* ptr) - : Value(ptr) - { concrete_type(STRING); } - static std::string type_name() { return "string"; } - virtual ~String() = 0; - virtual void rtrim() = 0; - virtual bool operator==(const Expression& rhs) const = 0; - virtual bool operator<(const Expression& rhs) const { - return this->to_string() < rhs.to_string(); - }; - ATTACH_VIRTUAL_AST_OPERATIONS(String); - ATTACH_OPERATIONS() - }; - inline String::~String() { }; - - /////////////////////////////////////////////////////////////////////// - // Interpolated strings. Meant to be reduced to flat strings during the - // evaluation phase. - /////////////////////////////////////////////////////////////////////// - class String_Schema : public String, public Vectorized { - ADD_PROPERTY(bool, css) - size_t hash_; - public: - String_Schema(ParserState pstate, size_t size = 0, bool css = true) - : String(pstate), Vectorized(size), css_(css), hash_(0) - { concrete_type(STRING); } - String_Schema(const String_Schema* ptr) - : String(ptr), - Vectorized(*ptr), - css_(ptr->css_), - hash_(ptr->hash_) - { concrete_type(STRING); } - - std::string type() const { return "string"; } - static std::string type_name() { return "string"; } - - bool is_left_interpolant(void) const; - bool is_right_interpolant(void) const; - // void has_interpolants(bool tc) { } - bool has_interpolants() { - for (auto el : elements()) { - if (el->is_interpolant()) return true; - } - return false; - } - virtual void rtrim(); - - virtual size_t hash() - { - if (hash_ == 0) { - for (auto string : elements()) - hash_combine(hash_, string->hash()); - } - return hash_; - } - - virtual void set_delayed(bool delayed) { - is_delayed(delayed); - } - - virtual bool operator==(const Expression& rhs) const; - ATTACH_AST_OPERATIONS(String_Schema) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////// - // Flat strings -- the lowest level of raw textual data. - //////////////////////////////////////////////////////// - class String_Constant : public String { - ADD_PROPERTY(char, quote_mark) - ADD_PROPERTY(bool, can_compress_whitespace) - HASH_CONSTREF(std::string, value) - protected: - size_t hash_; - public: - String_Constant(const String_Constant* ptr) - : String(ptr), - quote_mark_(ptr->quote_mark_), - can_compress_whitespace_(ptr->can_compress_whitespace_), - value_(ptr->value_), - hash_(ptr->hash_) - { } - String_Constant(ParserState pstate, std::string val, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0) - { } - String_Constant(ParserState pstate, const char* beg, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0) - { } - String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) - { } - String_Constant(ParserState pstate, const Token& tok, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) - { } - std::string type() const { return "string"; } - static std::string type_name() { return "string"; } - virtual bool is_invisible() const; - virtual void rtrim(); - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(value_); - } - return hash_; - } - - virtual bool operator==(const Expression& rhs) const; - virtual std::string inspect() const; // quotes are forced on inspection - - // static char auto_quote() { return '*'; } - static char double_quote() { return '"'; } - static char single_quote() { return '\''; } - - ATTACH_AST_OPERATIONS(String_Constant) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////// - // Possibly quoted string (unquote on instantiation) - //////////////////////////////////////////////////////// - class String_Quoted : public String_Constant { - public: - String_Quoted(ParserState pstate, std::string val, char q = 0, - bool keep_utf8_escapes = false, bool skip_unquoting = false, - bool strict_unquoting = true, bool css = true) - : String_Constant(pstate, val, css) - { - if (skip_unquoting == false) { - value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); - } - if (q && quote_mark_) quote_mark_ = q; - } - String_Quoted(const String_Quoted* ptr) - : String_Constant(ptr) - { } - virtual bool operator==(const Expression& rhs) const; - virtual std::string inspect() const; // quotes are forced on inspection - ATTACH_AST_OPERATIONS(String_Quoted) - ATTACH_OPERATIONS() - }; - - ///////////////// - // Media queries. - ///////////////// - class Media_Query : public Expression, - public Vectorized { - ADD_PROPERTY(String_Obj, media_type) - ADD_PROPERTY(bool, is_negated) - ADD_PROPERTY(bool, is_restricted) - public: - Media_Query(ParserState pstate, - String_Obj t = 0, size_t s = 0, bool n = false, bool r = false) - : Expression(pstate), Vectorized(s), - media_type_(t), is_negated_(n), is_restricted_(r) - { } - Media_Query(const Media_Query* ptr) - : Expression(ptr), - Vectorized(*ptr), - media_type_(ptr->media_type_), - is_negated_(ptr->is_negated_), - is_restricted_(ptr->is_restricted_) - { } - ATTACH_AST_OPERATIONS(Media_Query) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////// - // Media expressions (for use inside media queries). - //////////////////////////////////////////////////// - class Media_Query_Expression : public Expression { - ADD_PROPERTY(Expression_Obj, feature) - ADD_PROPERTY(Expression_Obj, value) - ADD_PROPERTY(bool, is_interpolated) - public: - Media_Query_Expression(ParserState pstate, - Expression_Obj f, Expression_Obj v, bool i = false) - : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) - { } - Media_Query_Expression(const Media_Query_Expression* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_), - is_interpolated_(ptr->is_interpolated_) - { } - ATTACH_AST_OPERATIONS(Media_Query_Expression) - ATTACH_OPERATIONS() - }; - - //////////////////// - // `@supports` rule. - //////////////////// - class Supports_Block : public Has_Block { - ADD_PROPERTY(Supports_Condition_Obj, condition) - public: - Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block = 0) - : Has_Block(pstate, block), condition_(condition) - { statement_type(SUPPORTS); } - Supports_Block(const Supports_Block* ptr) - : Has_Block(ptr), condition_(ptr->condition_) - { statement_type(SUPPORTS); } - bool bubbles() { return true; } - ATTACH_AST_OPERATIONS(Supports_Block) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////////////////////// - // The abstract superclass of all Supports conditions. - ////////////////////////////////////////////////////// - class Supports_Condition : public Expression { - public: - Supports_Condition(ParserState pstate) - : Expression(pstate) - { } - Supports_Condition(const Supports_Condition* ptr) - : Expression(ptr) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } - ATTACH_AST_OPERATIONS(Supports_Condition) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////// - // An operator condition (e.g. `CONDITION1 and CONDITION2`). - //////////////////////////////////////////////////////////// - class Supports_Operator : public Supports_Condition { - public: - enum Operand { AND, OR }; - private: - ADD_PROPERTY(Supports_Condition_Obj, left); - ADD_PROPERTY(Supports_Condition_Obj, right); - ADD_PROPERTY(Operand, operand); - public: - Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o) - : Supports_Condition(pstate), left_(l), right_(r), operand_(o) - { } - Supports_Operator(const Supports_Operator* ptr) - : Supports_Condition(ptr), - left_(ptr->left_), - right_(ptr->right_), - operand_(ptr->operand_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const; - ATTACH_AST_OPERATIONS(Supports_Operator) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////////// - // A negation condition (`not CONDITION`). - ////////////////////////////////////////// - class Supports_Negation : public Supports_Condition { - private: - ADD_PROPERTY(Supports_Condition_Obj, condition); - public: - Supports_Negation(ParserState pstate, Supports_Condition_Obj c) - : Supports_Condition(pstate), condition_(c) - { } - Supports_Negation(const Supports_Negation* ptr) - : Supports_Condition(ptr), condition_(ptr->condition_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const; - ATTACH_AST_OPERATIONS(Supports_Negation) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////// - // A declaration condition (e.g. `(feature: value)`). - ///////////////////////////////////////////////////// - class Supports_Declaration : public Supports_Condition { - private: - ADD_PROPERTY(Expression_Obj, feature); - ADD_PROPERTY(Expression_Obj, value); - public: - Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v) - : Supports_Condition(pstate), feature_(f), value_(v) - { } - Supports_Declaration(const Supports_Declaration* ptr) - : Supports_Condition(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } - ATTACH_AST_OPERATIONS(Supports_Declaration) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////// - // An interpolation condition (e.g. `#{$var}`). - /////////////////////////////////////////////// - class Supports_Interpolation : public Supports_Condition { - private: - ADD_PROPERTY(Expression_Obj, value); - public: - Supports_Interpolation(ParserState pstate, Expression_Obj v) - : Supports_Condition(pstate), value_(v) - { } - Supports_Interpolation(const Supports_Interpolation* ptr) - : Supports_Condition(ptr), - value_(ptr->value_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } - ATTACH_AST_OPERATIONS(Supports_Interpolation) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////// - // At root expressions (for use inside @at-root). - ///////////////////////////////////////////////// - class At_Root_Query : public Expression { - private: - ADD_PROPERTY(Expression_Obj, feature) - ADD_PROPERTY(Expression_Obj, value) - public: - At_Root_Query(ParserState pstate, Expression_Obj f = 0, Expression_Obj v = 0, bool i = false) - : Expression(pstate), feature_(f), value_(v) - { } - At_Root_Query(const At_Root_Query* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - bool exclude(std::string str); - ATTACH_AST_OPERATIONS(At_Root_Query) - ATTACH_OPERATIONS() - }; - - /////////// - // At-root. - /////////// - class At_Root_Block : public Has_Block { - ADD_PROPERTY(At_Root_Query_Obj, expression) - public: - At_Root_Block(ParserState pstate, Block_Obj b = 0, At_Root_Query_Obj e = 0) - : Has_Block(pstate, b), expression_(e) - { statement_type(ATROOT); } - At_Root_Block(const At_Root_Block* ptr) - : Has_Block(ptr), expression_(ptr->expression_) - { statement_type(ATROOT); } - bool bubbles() { return true; } - bool exclude_node(Statement_Obj s) { - if (expression() == 0) - { - return s->statement_type() == Statement::RULESET; - } - - if (s->statement_type() == Statement::DIRECTIVE) - { - if (Directive_Obj dir = Cast(s)) - { - std::string keyword(dir->keyword()); - if (keyword.length() > 0) keyword.erase(0, 1); - return expression()->exclude(keyword); - } - } - if (s->statement_type() == Statement::MEDIA) - { - return expression()->exclude("media"); - } - if (s->statement_type() == Statement::RULESET) - { - return expression()->exclude("rule"); - } - if (s->statement_type() == Statement::SUPPORTS) - { - return expression()->exclude("supports"); - } - if (Directive_Obj dir = Cast(s)) - { - if (dir->is_keyframes()) return expression()->exclude("keyframes"); - } - return false; - } - ATTACH_AST_OPERATIONS(At_Root_Block) - ATTACH_OPERATIONS() - }; - - ////////////////// - // The null value. - ////////////////// - class Null : public Value { - public: - Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); } - Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); } - std::string type() const { return "null"; } - static std::string type_name() { return "null"; } - bool is_invisible() const { return true; } - operator bool() { return false; } - bool is_false() { return true; } - - virtual size_t hash() - { - return -1; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Null) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////// - // Thunks for delayed evaluation. - ///////////////////////////////// - class Thunk : public Expression { - ADD_PROPERTY(Expression_Obj, expression) - ADD_PROPERTY(Env*, environment) - public: - Thunk(ParserState pstate, Expression_Obj exp, Env* env = 0) - : Expression(pstate), expression_(exp), environment_(env) - { } - }; - - ///////////////////////////////////////////////////////// - // Individual parameter objects for mixins and functions. - ///////////////////////////////////////////////////////// - class Parameter : public AST_Node { - ADD_CONSTREF(std::string, name) - ADD_PROPERTY(Expression_Obj, default_value) - ADD_PROPERTY(bool, is_rest_parameter) - public: - Parameter(ParserState pstate, - std::string n, Expression_Obj def = 0, bool rest = false) - : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) - { - // tried to come up with a spec test for this, but it does no longer - // get past the parser (it error out earlier). A spec test was added! - // if (default_value_ && is_rest_parameter_) { - // error("variable-length parameter may not have a default value", pstate_); - // } - } - Parameter(const Parameter* ptr) - : AST_Node(ptr), - name_(ptr->name_), - default_value_(ptr->default_value_), - is_rest_parameter_(ptr->is_rest_parameter_) - { - // tried to come up with a spec test for this, but it does no longer - // get past the parser (it error out earlier). A spec test was added! - // if (default_value_ && is_rest_parameter_) { - // error("variable-length parameter may not have a default value", pstate_); - // } - } - ATTACH_AST_OPERATIONS(Parameter) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////////////////// - // Parameter lists -- in their own class to facilitate context-sensitive - // error checking (e.g., ensuring that all optional parameters follow all - // required parameters). - ///////////////////////////////////////////////////////////////////////// - class Parameters : public AST_Node, public Vectorized { - ADD_PROPERTY(bool, has_optional_parameters) - ADD_PROPERTY(bool, has_rest_parameter) - protected: - void adjust_after_pushing(Parameter_Obj p) - { - if (p->default_value()) { - if (has_rest_parameter()) { - coreError("optional parameters may not be combined with variable-length parameters", p->pstate()); - } - has_optional_parameters(true); - } - else if (p->is_rest_parameter()) { - if (has_rest_parameter()) { - coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate()); - } - has_rest_parameter(true); - } - else { - if (has_rest_parameter()) { - coreError("required parameters must precede variable-length parameters", p->pstate()); - } - if (has_optional_parameters()) { - coreError("required parameters must precede optional parameters", p->pstate()); - } - } - } - public: - Parameters(ParserState pstate) - : AST_Node(pstate), - Vectorized(), - has_optional_parameters_(false), - has_rest_parameter_(false) - { } - Parameters(const Parameters* ptr) - : AST_Node(ptr), - Vectorized(*ptr), - has_optional_parameters_(ptr->has_optional_parameters_), - has_rest_parameter_(ptr->has_rest_parameter_) - { } - ATTACH_AST_OPERATIONS(Parameters) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////// - // Abstract base class for CSS selectors. - ///////////////////////////////////////// - class Selector : public Expression { - // ADD_PROPERTY(bool, has_reference) - // line break before list separator - ADD_PROPERTY(bool, has_line_feed) - // line break after list separator - ADD_PROPERTY(bool, has_line_break) - // maybe we have optional flag - ADD_PROPERTY(bool, is_optional) - // parent block pointers - - // must not be a reference counted object - // otherwise we create circular references - ADD_PROPERTY(Media_Block_Ptr, media_block) - protected: - size_t hash_; - public: - Selector(ParserState pstate) - : Expression(pstate), - has_line_feed_(false), - has_line_break_(false), - is_optional_(false), - media_block_(0), - hash_(0) - { concrete_type(SELECTOR); } - Selector(const Selector* ptr) - : Expression(ptr), - // has_reference_(ptr->has_reference_), - has_line_feed_(ptr->has_line_feed_), - has_line_break_(ptr->has_line_break_), - is_optional_(ptr->is_optional_), - media_block_(ptr->media_block_), - hash_(ptr->hash_) - { concrete_type(SELECTOR); } - virtual ~Selector() = 0; - virtual size_t hash() = 0; - virtual unsigned long specificity() const = 0; - virtual void set_media_block(Media_Block_Ptr mb) { - media_block(mb); - } - virtual bool has_parent_ref() const { - return false; - } - virtual bool has_real_parent_ref() const { - return false; - } - // dispatch to correct handlers - virtual bool operator<(const Selector& rhs) const = 0; - virtual bool operator==(const Selector& rhs) const = 0; - ATTACH_VIRTUAL_AST_OPERATIONS(Selector); - }; - inline Selector::~Selector() { } - - ///////////////////////////////////////////////////////////////////////// - // Interpolated selectors -- the interpolated String will be expanded and - // re-parsed into a normal selector class. - ///////////////////////////////////////////////////////////////////////// - class Selector_Schema : public AST_Node { - ADD_PROPERTY(String_Obj, contents) - ADD_PROPERTY(bool, connect_parent); - // must not be a reference counted object - // otherwise we create circular references - ADD_PROPERTY(Media_Block_Ptr, media_block) - // store computed hash - size_t hash_; - public: - Selector_Schema(ParserState pstate, String_Obj c) - : AST_Node(pstate), - contents_(c), - connect_parent_(true), - media_block_(NULL), - hash_(0) - { } - Selector_Schema(const Selector_Schema* ptr) - : AST_Node(ptr), - contents_(ptr->contents_), - connect_parent_(ptr->connect_parent_), - media_block_(ptr->media_block_), - hash_(ptr->hash_) - { } - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - // selector schema is not yet a final selector, so we do not - // have a specificity for it yet. We need to - virtual unsigned long specificity() const { return 0; } - virtual size_t hash() { - if (hash_ == 0) { - hash_combine(hash_, contents_->hash()); - } - return hash_; - } - ATTACH_AST_OPERATIONS(Selector_Schema) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////// - // Abstract base class for simple selectors. - //////////////////////////////////////////// - class Simple_Selector : public Selector { - ADD_CONSTREF(std::string, ns) - ADD_CONSTREF(std::string, name) - ADD_PROPERTY(Simple_Type, simple_type) - ADD_PROPERTY(bool, has_ns) - public: - Simple_Selector(ParserState pstate, std::string n = "") - : Selector(pstate), ns_(""), name_(n), has_ns_(false) - { - simple_type(SIMPLE); - size_t pos = n.find('|'); - // found some namespace - if (pos != std::string::npos) { - has_ns_ = true; - ns_ = n.substr(0, pos); - name_ = n.substr(pos + 1); - } - } - Simple_Selector(const Simple_Selector* ptr) - : Selector(ptr), - ns_(ptr->ns_), - name_(ptr->name_), - has_ns_(ptr->has_ns_) - { simple_type(SIMPLE); } - virtual std::string ns_name() const - { - std::string name(""); - if (has_ns_) - name += ns_ + "|"; - return name + name_; - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, std::hash()(SELECTOR)); - hash_combine(hash_, std::hash()(ns())); - hash_combine(hash_, std::hash()(name())); - } - return hash_; - } - // namespace compare functions - bool is_ns_eq(const Simple_Selector& r) const; - // namespace query functions - bool is_universal_ns() const - { - return has_ns_ && ns_ == "*"; - } - bool has_universal_ns() const - { - return !has_ns_ || ns_ == "*"; - } - bool is_empty_ns() const - { - return !has_ns_ || ns_ == ""; - } - bool has_empty_ns() const - { - return has_ns_ && ns_ == ""; - } - bool has_qualified_ns() const - { - return has_ns_ && ns_ != "" && ns_ != "*"; - } - // name query functions - bool is_universal() const - { - return name_ == "*"; - } - - virtual bool has_placeholder() { - return false; - } - - virtual ~Simple_Selector() = 0; - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - virtual bool has_parent_ref() const { return false; }; - virtual bool has_real_parent_ref() const { return false; }; - virtual bool is_pseudo_element() const { return false; } - - virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; } - - virtual bool operator==(const Selector& rhs) const; - virtual bool operator==(const Simple_Selector& rhs) const; - inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); } - - bool operator<(const Selector& rhs) const; - bool operator<(const Simple_Selector& rhs) const; - // default implementation should work for most of the simple selectors (otherwise overload) - ATTACH_VIRTUAL_AST_OPERATIONS(Simple_Selector); - ATTACH_OPERATIONS(); - }; - inline Simple_Selector::~Simple_Selector() { } - - - ////////////////////////////////// - // The Parent Selector Expression. - ////////////////////////////////// - // parent selectors can occur in selectors but also - // inside strings in declarations (Compound_Selector). - // only one simple parent selector means the first case. - class Parent_Selector : public Simple_Selector { - ADD_PROPERTY(bool, real) - public: - Parent_Selector(ParserState pstate, bool r = true) - : Simple_Selector(pstate, "&"), real_(r) - { /* has_reference(true); */ } - Parent_Selector(const Parent_Selector* ptr) - : Simple_Selector(ptr), real_(ptr->real_) - { /* has_reference(true); */ } - bool is_real_parent_ref() const { return real(); }; - virtual bool has_parent_ref() const { return true; }; - virtual bool has_real_parent_ref() const { return is_real_parent_ref(); }; - virtual unsigned long specificity() const - { - return 0; - } - std::string type() const { return "selector"; } - static std::string type_name() { return "selector"; } - ATTACH_AST_OPERATIONS(Parent_Selector) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////////////////// - // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. - ///////////////////////////////////////////////////////////////////////// - class Placeholder_Selector : public Simple_Selector { - public: - Placeholder_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Placeholder_Selector(const Placeholder_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - return Constants::Specificity_Base; - } - virtual bool has_placeholder() { - return true; - } - virtual ~Placeholder_Selector() {}; - ATTACH_AST_OPERATIONS(Placeholder_Selector) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////////////// - // Element selectors (and the universal selector) -- e.g., div, span, *. - ///////////////////////////////////////////////////////////////////// - class Element_Selector : public Simple_Selector { - public: - Element_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Element_Selector(const Element_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - if (name() == "*") return 0; - else return Constants::Specificity_Element; - } - virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr); - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Element_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Element_Selector& rhs) const; - ATTACH_AST_OPERATIONS(Element_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////// - // Class selectors -- i.e., .foo. - //////////////////////////////////////////////// - class Class_Selector : public Simple_Selector { - public: - Class_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Class_Selector(const Class_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - return Constants::Specificity_Class; - } - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - ATTACH_AST_OPERATIONS(Class_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////// - // ID selectors -- i.e., #foo. - //////////////////////////////////////////////// - class Id_Selector : public Simple_Selector { - public: - Id_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Id_Selector(const Id_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - return Constants::Specificity_ID; - } - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - ATTACH_AST_OPERATIONS(Id_Selector) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////// - // Attribute selectors -- e.g., [src*=".jpg"], etc. - /////////////////////////////////////////////////// - class Attribute_Selector : public Simple_Selector { - ADD_CONSTREF(std::string, matcher) - // this cannot be changed to obj atm!!!!!!????!!!!!!! - ADD_PROPERTY(String_Obj, value) // might be interpolated - ADD_PROPERTY(char, modifier); - public: - Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0) - : Simple_Selector(pstate, n), matcher_(m), value_(v), modifier_(o) - { simple_type(ATTR_SEL); } - Attribute_Selector(const Attribute_Selector* ptr) - : Simple_Selector(ptr), - matcher_(ptr->matcher_), - value_(ptr->value_), - modifier_(ptr->modifier_) - { simple_type(ATTR_SEL); } - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - hash_combine(hash_, std::hash()(matcher())); - if (value_) hash_combine(hash_, value_->hash()); - } - return hash_; - } - virtual unsigned long specificity() const - { - return Constants::Specificity_Attr; - } - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Attribute_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Attribute_Selector& rhs) const; - ATTACH_AST_OPERATIONS(Attribute_Selector) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////////////////////////////////// - // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. - ////////////////////////////////////////////////////////////////// - /* '::' starts a pseudo-element, ':' a pseudo-class */ - /* Except :first-line, :first-letter, :before and :after */ - /* Note that pseudo-elements are restricted to one per selector */ - /* and occur only in the last simple_selector_sequence. */ - inline bool is_pseudo_class_element(const std::string& name) - { - return name == ":before" || - name == ":after" || - name == ":first-line" || - name == ":first-letter"; - } - - // Pseudo Selector cannot have any namespace? - class Pseudo_Selector : public Simple_Selector { - ADD_PROPERTY(String_Obj, expression) - public: - Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr = 0) - : Simple_Selector(pstate, n), expression_(expr) - { simple_type(PSEUDO_SEL); } - Pseudo_Selector(const Pseudo_Selector* ptr) - : Simple_Selector(ptr), expression_(ptr->expression_) - { simple_type(PSEUDO_SEL); } - - // A pseudo-element is made of two colons (::) followed by the name. - // The `::` notation is introduced by the current document in order to - // establish a discrimination between pseudo-classes and pseudo-elements. - // For compatibility with existing style sheets, user agents must also - // accept the previous one-colon notation for pseudo-elements introduced - // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and - // :after). This compatibility is not allowed for the new pseudo-elements - // introduced in this specification. - virtual bool is_pseudo_element() const - { - return (name_[0] == ':' && name_[1] == ':') - || is_pseudo_class_element(name_); - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (expression_) hash_combine(hash_, expression_->hash()); - } - return hash_; - } - virtual unsigned long specificity() const - { - if (is_pseudo_element()) - return Constants::Specificity_Element; - return Constants::Specificity_Pseudo; - } - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Pseudo_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Pseudo_Selector& rhs) const; - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - ATTACH_AST_OPERATIONS(Pseudo_Selector) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////// - // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir) - ///////////////////////////////////////////////// - class Wrapped_Selector : public Simple_Selector { - ADD_PROPERTY(Selector_List_Obj, selector) - public: - Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel) - : Simple_Selector(pstate, n), selector_(sel) - { simple_type(WRAPPED_SEL); } - Wrapped_Selector(const Wrapped_Selector* ptr) - : Simple_Selector(ptr), selector_(ptr->selector_) - { simple_type(WRAPPED_SEL); } - virtual bool is_superselector_of(Wrapped_Selector_Obj sub); - // Selectors inside the negation pseudo-class are counted like any - // other, but the negation itself does not count as a pseudo-class. - virtual size_t hash(); - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - virtual unsigned long specificity() const; - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Wrapped_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Wrapped_Selector& rhs) const; - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Wrapped_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Simple selector sequences. Maintains flags indicating whether it contains - // any parent references or placeholders, to simplify expansion. - //////////////////////////////////////////////////////////////////////////// - class Compound_Selector : public Selector, public Vectorized { - private: - ComplexSelectorSet sources_; - ADD_PROPERTY(bool, extended); - ADD_PROPERTY(bool, has_parent_reference); - protected: - void adjust_after_pushing(Simple_Selector_Obj s) - { - // if (s->has_reference()) has_reference(true); - // if (s->has_placeholder()) has_placeholder(true); - } - public: - Compound_Selector(ParserState pstate, size_t s = 0) - : Selector(pstate), - Vectorized(s), - extended_(false), - has_parent_reference_(false) - { } - Compound_Selector(const Compound_Selector* ptr) - : Selector(ptr), - Vectorized(*ptr), - extended_(ptr->extended_), - has_parent_reference_(ptr->has_parent_reference_) - { } - bool contains_placeholder() { - for (size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->has_placeholder()) return true; - } - return false; - }; - - void append(Simple_Selector_Ptr element); - - bool is_universal() const - { - return length() == 1 && (*this)[0]->is_universal(); - } - - Complex_Selector_Obj to_complex(); - Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs); - // virtual Placeholder_Selector_Ptr find_placeholder(); - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - Simple_Selector_Ptr base() const { - if (length() == 0) return 0; - // ToDo: why is this needed? - if (Cast((*this)[0])) - return (*this)[0]; - return 0; - } - virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = ""); - virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapped = ""); - virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapped = ""); - virtual size_t hash() - { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - if (length()) hash_combine(Selector::hash_, Vectorized::hash()); - } - return Selector::hash_; - } - virtual unsigned long specificity() const - { - int sum = 0; - for (size_t i = 0, L = length(); i < L; ++i) - { sum += (*this)[i]->specificity(); } - return sum; - } - - virtual bool has_placeholder() - { - if (length() == 0) return false; - if (Simple_Selector_Obj ss = elements().front()) { - if (ss->has_placeholder()) return true; - } - return false; - } - - bool is_empty_reference() - { - return length() == 1 && - Cast((*this)[0]); - } - - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - virtual bool operator<(const Compound_Selector& rhs) const; - virtual bool operator==(const Compound_Selector& rhs) const; - inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); } - - ComplexSelectorSet& sources() { return sources_; } - void clearSources() { sources_.clear(); } - void mergeSources(ComplexSelectorSet& sources); - - Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs); - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Compound_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////////// - // General selectors -- i.e., simple sequences combined with one of the four - // CSS selector combinators (">", "+", "~", and whitespace). Essentially a - // linked list. - //////////////////////////////////////////////////////////////////////////// - class Complex_Selector : public Selector { - public: - enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE }; - private: - HASH_CONSTREF(Combinator, combinator) - HASH_PROPERTY(Compound_Selector_Obj, head) - HASH_PROPERTY(Complex_Selector_Obj, tail) - HASH_PROPERTY(String_Obj, reference); - public: - bool contains_placeholder() { - if (head() && head()->contains_placeholder()) return true; - if (tail() && tail()->contains_placeholder()) return true; - return false; - }; - Complex_Selector(ParserState pstate, - Combinator c = ANCESTOR_OF, - Compound_Selector_Obj h = 0, - Complex_Selector_Obj t = 0, - String_Obj r = 0) - : Selector(pstate), - combinator_(c), - head_(h), tail_(t), - reference_(r) - {} - Complex_Selector(const Complex_Selector* ptr) - : Selector(ptr), - combinator_(ptr->combinator_), - head_(ptr->head_), tail_(ptr->tail_), - reference_(ptr->reference_) - {}; - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - - Complex_Selector_Obj skip_empty_reference() - { - if ((!head_ || !head_->length() || head_->is_empty_reference()) && - combinator() == Combinator::ANCESTOR_OF) - { - if (!tail_) return 0; - tail_->has_line_feed_ = this->has_line_feed_; - // tail_->has_line_break_ = this->has_line_break_; - return tail_->skip_empty_reference(); - } - return this; - } - - // can still have a tail - bool is_empty_ancestor() const - { - return (!head() || head()->length() == 0) && - combinator() == Combinator::ANCESTOR_OF; - } - - Selector_List_Ptr tails(Selector_List_Ptr tails); - - // front returns the first real tail - // skips over parent and empty ones - Complex_Selector_Obj first(); - // last returns the last real tail - Complex_Selector_Obj last(); - - // some shortcuts that should be removed - Complex_Selector_Obj innermost() { return last(); }; - - size_t length() const; - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); - virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); - Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs); - Combinator clear_innermost(); - void append(Complex_Selector_Obj, Backtraces& traces); - void set_innermost(Complex_Selector_Obj, Combinator); - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, std::hash()(SELECTOR)); - hash_combine(hash_, std::hash()(combinator_)); - if (head_) hash_combine(hash_, head_->hash()); - if (tail_) hash_combine(hash_, tail_->hash()); - } - return hash_; - } - virtual unsigned long specificity() const - { - int sum = 0; - if (head()) sum += head()->specificity(); - if (tail()) sum += tail()->specificity(); - return sum; - } - virtual void set_media_block(Media_Block_Ptr mb) { - media_block(mb); - if (tail_) tail_->set_media_block(mb); - if (head_) head_->set_media_block(mb); - } - virtual bool has_placeholder() { - if (head_ && head_->has_placeholder()) return true; - if (tail_ && tail_->has_placeholder()) return true; - return false; - } - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - virtual bool operator<(const Complex_Selector& rhs) const; - virtual bool operator==(const Complex_Selector& rhs) const; - inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); } - const ComplexSelectorSet sources() - { - //s = Set.new - //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} - //s - - ComplexSelectorSet srcs; - - Compound_Selector_Obj pHead = head(); - Complex_Selector_Obj pTail = tail(); - - if (pHead) { - const ComplexSelectorSet& headSources = pHead->sources(); - srcs.insert(headSources.begin(), headSources.end()); - } - - if (pTail) { - const ComplexSelectorSet& tailSources = pTail->sources(); - srcs.insert(tailSources.begin(), tailSources.end()); - } - - return srcs; - } - void addSources(ComplexSelectorSet& sources) { - // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} - Complex_Selector_Ptr pIter = this; - while (pIter) { - Compound_Selector_Ptr pHead = pIter->head(); - - if (pHead) { - pHead->mergeSources(sources); - } - - pIter = pIter->tail(); - } - } - void clearSources() { - Complex_Selector_Ptr pIter = this; - while (pIter) { - Compound_Selector_Ptr pHead = pIter->head(); - - if (pHead) { - pHead->clearSources(); - } - - pIter = pIter->tail(); - } - } - - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Complex_Selector) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////// - // Comma-separated selector groups. - /////////////////////////////////// - class Selector_List : public Selector, public Vectorized { - ADD_PROPERTY(Selector_Schema_Obj, schema) - ADD_CONSTREF(std::vector, wspace) - protected: - void adjust_after_pushing(Complex_Selector_Obj c); - public: - Selector_List(ParserState pstate, size_t s = 0) - : Selector(pstate), - Vectorized(s), - schema_(NULL), - wspace_(0) - { } - Selector_List(const Selector_List* ptr) - : Selector(ptr), - Vectorized(*ptr), - schema_(ptr->schema_), - wspace_(ptr->wspace_) - { } - std::string type() const { return "list"; } - // remove parent selector references - // basically unwraps parsed selectors - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - void remove_parent_selectors(); - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); - virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); - Selector_List_Ptr unify_with(Selector_List_Ptr); - void populate_extends(Selector_List_Obj, Subset_Map&); - Selector_List_Obj eval(Eval& eval); - virtual size_t hash() - { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - hash_combine(Selector::hash_, Vectorized::hash()); - } - return Selector::hash_; - } - virtual unsigned long specificity() const - { - unsigned long sum = 0; - unsigned long specificity; - for (size_t i = 0, L = length(); i < L; ++i) - { - specificity = (*this)[i]->specificity(); - if (sum < specificity) sum = specificity; - } - return sum; - } - virtual void set_media_block(Media_Block_Ptr mb) { - media_block(mb); - for (Complex_Selector_Obj cs : elements()) { - cs->set_media_block(mb); - } - } - virtual bool has_placeholder() { - for (Complex_Selector_Obj cs : elements()) { - if (cs->has_placeholder()) return true; - } - return false; - } - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - virtual bool operator<(const Selector_List& rhs) const; - virtual bool operator==(const Selector_List& rhs) const; - // Selector Lists can be compared to comma lists - virtual bool operator==(const Expression& rhs) const; - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Selector_List) - ATTACH_OPERATIONS() - }; - - // compare function for sorting and probably other other uses - struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } }; - struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } }; - struct cmp_simple_selector { inline bool operator() (const Simple_Selector_Obj l, const Simple_Selector_Obj r) { return (*l < *r); } }; - -} - -#ifdef __clang__ - -#pragma clang diagnostic pop - -#endif - -#endif diff --git a/src/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp deleted file mode 100644 index b3a7f8d16..000000000 --- a/src/libsass/src/ast_def_macros.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef SASS_AST_DEF_MACROS_H -#define SASS_AST_DEF_MACROS_H - -// Helper class to switch a flag and revert once we go out of scope -template -class LocalOption { - private: - T* var; // pointer to original variable - T orig; // copy of the original option - public: - LocalOption(T& var) - { - this->var = &var; - this->orig = var; - } - LocalOption(T& var, T orig) - { - this->var = &var; - this->orig = var; - *(this->var) = orig; - } - void reset() - { - *(this->var) = this->orig; - } - ~LocalOption() { - *(this->var) = this->orig; - } -}; - -#define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) -#define LOCAL_COUNT(name,opt) LocalOption cnt_##name(name, opt) - -#define NESTING_GUARD(name) \ - LocalOption cnt_##name(name, name + 1); \ - if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ - -#define ATTACH_OPERATIONS()\ -virtual void perform(Operation* op) { (*op)(this); }\ -virtual AST_Node_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual Statement_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual Expression_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual Selector_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual std::string perform(Operation* op) { return (*op)(this); }\ -virtual union Sass_Value* perform(Operation* op) { return (*op)(this); }\ -virtual Value_Ptr perform(Operation* op) { return (*op)(this); } - -#define ADD_PROPERTY(type, name)\ -protected:\ - type name##_;\ -public:\ - type name() const { return name##_; }\ - type name(type name##__) { return name##_ = name##__; }\ -private: - -#define HASH_PROPERTY(type, name)\ -protected:\ - type name##_;\ -public:\ - type name() const { return name##_; }\ - type name(type name##__) { hash_ = 0; return name##_ = name##__; }\ -private: - -#define ADD_CONSTREF(type, name) \ -protected: \ - type name##_; \ -public: \ - const type& name() const { return name##_; } \ - void name(type name##__) { name##_ = name##__; } \ -private: - -#define HASH_CONSTREF(type, name) \ -protected: \ - type name##_; \ -public: \ - const type& name() const { return name##_; } \ - void name(type name##__) { hash_ = 0; name##_ = name##__; } \ -private: - -#endif diff --git a/src/libsass/src/ast_fwd_decl.cpp b/src/libsass/src/ast_fwd_decl.cpp deleted file mode 100644 index c9c76727a..000000000 --- a/src/libsass/src/ast_fwd_decl.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "ast.hpp" - -namespace Sass { - - #define IMPLEMENT_BASE_CAST(T) \ - template<> \ - T* Cast(AST_Node* ptr) { \ - return dynamic_cast(ptr); \ - }; \ - \ - template<> \ - const T* Cast(const AST_Node* ptr) { \ - return dynamic_cast(ptr); \ - }; \ - - IMPLEMENT_BASE_CAST(AST_Node) - IMPLEMENT_BASE_CAST(Expression) - IMPLEMENT_BASE_CAST(Statement) - IMPLEMENT_BASE_CAST(Has_Block) - IMPLEMENT_BASE_CAST(PreValue) - IMPLEMENT_BASE_CAST(Value) - IMPLEMENT_BASE_CAST(List) - IMPLEMENT_BASE_CAST(String) - IMPLEMENT_BASE_CAST(String_Constant) - IMPLEMENT_BASE_CAST(Supports_Condition) - IMPLEMENT_BASE_CAST(Selector) - IMPLEMENT_BASE_CAST(Simple_Selector) - -} diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp deleted file mode 100644 index 5145a092b..000000000 --- a/src/libsass/src/ast_fwd_decl.hpp +++ /dev/null @@ -1,463 +0,0 @@ -#ifndef SASS_AST_FWD_DECL_H -#define SASS_AST_FWD_DECL_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "memory/SharedPtr.hpp" -#include "sass/functions.h" - -///////////////////////////////////////////// -// Forward declarations for the AST visitors. -///////////////////////////////////////////// -namespace Sass { - - class AST_Node; - typedef AST_Node* AST_Node_Ptr; - typedef AST_Node const* AST_Node_Ptr_Const; - - class Has_Block; - typedef Has_Block* Has_Block_Ptr; - typedef Has_Block const* Has_Block_Ptr_Const; - - class Simple_Selector; - typedef Simple_Selector* Simple_Selector_Ptr; - typedef Simple_Selector const* Simple_Selector_Ptr_Const; - - class PreValue; - typedef PreValue* PreValue_Ptr; - typedef PreValue const* PreValue_Ptr_Const; - class Thunk; - typedef Thunk* Thunk_Ptr; - typedef Thunk const* Thunk_Ptr_Const; - class Block; - typedef Block* Block_Ptr; - typedef Block const* Block_Ptr_Const; - class Expression; - typedef Expression* Expression_Ptr; - typedef Expression const* Expression_Ptr_Const; - class Statement; - typedef Statement* Statement_Ptr; - typedef Statement const* Statement_Ptr_Const; - class Value; - typedef Value* Value_Ptr; - typedef Value const* Value_Ptr_Const; - class Declaration; - typedef Declaration* Declaration_Ptr; - typedef Declaration const* Declaration_Ptr_Const; - class Ruleset; - typedef Ruleset* Ruleset_Ptr; - typedef Ruleset const* Ruleset_Ptr_Const; - class Bubble; - typedef Bubble* Bubble_Ptr; - typedef Bubble const* Bubble_Ptr_Const; - class Trace; - typedef Trace* Trace_Ptr; - typedef Trace const* Trace_Ptr_Const; - - class Media_Block; - typedef Media_Block* Media_Block_Ptr; - typedef Media_Block const* Media_Block_Ptr_Const; - class Supports_Block; - typedef Supports_Block* Supports_Block_Ptr; - typedef Supports_Block const* Supports_Block_Ptr_Const; - class Directive; - typedef Directive* Directive_Ptr; - typedef Directive const* Directive_Ptr_Const; - - - class Keyframe_Rule; - typedef Keyframe_Rule* Keyframe_Rule_Ptr; - typedef Keyframe_Rule const* Keyframe_Rule_Ptr_Const; - class At_Root_Block; - typedef At_Root_Block* At_Root_Block_Ptr; - typedef At_Root_Block const* At_Root_Block_Ptr_Const; - class Assignment; - typedef Assignment* Assignment_Ptr; - typedef Assignment const* Assignment_Ptr_Const; - - class Import; - typedef Import* Import_Ptr; - typedef Import const* Import_Ptr_Const; - class Import_Stub; - typedef Import_Stub* Import_Stub_Ptr; - typedef Import_Stub const* Import_Stub_Ptr_Const; - class Warning; - typedef Warning* Warning_Ptr; - typedef Warning const* Warning_Ptr_Const; - - class Error; - typedef Error* Error_Ptr; - typedef Error const* Error_Ptr_Const; - class Debug; - typedef Debug* Debug_Ptr; - typedef Debug const* Debug_Ptr_Const; - class Comment; - typedef Comment* Comment_Ptr; - typedef Comment const* Comment_Ptr_Const; - - class If; - typedef If* If_Ptr; - typedef If const* If_Ptr_Const; - class For; - typedef For* For_Ptr; - typedef For const* For_Ptr_Const; - class Each; - typedef Each* Each_Ptr; - typedef Each const* Each_Ptr_Const; - class While; - typedef While* While_Ptr; - typedef While const* While_Ptr_Const; - class Return; - typedef Return* Return_Ptr; - typedef Return const* Return_Ptr_Const; - class Content; - typedef Content* Content_Ptr; - typedef Content const* Content_Ptr_Const; - class Extension; - typedef Extension* Extension_Ptr; - typedef Extension const* Extension_Ptr_Const; - class Definition; - typedef Definition* Definition_Ptr; - typedef Definition const* Definition_Ptr_Const; - - class List; - typedef List* List_Ptr; - typedef List const* List_Ptr_Const; - class Map; - typedef Map* Map_Ptr; - typedef Map const* Map_Ptr_Const; - class Function; - typedef Function* Function_Ptr; - typedef Function const* Function_Ptr_Const; - - class Mixin_Call; - typedef Mixin_Call* Mixin_Call_Ptr; - typedef Mixin_Call const* Mixin_Call_Ptr_Const; - class Binary_Expression; - typedef Binary_Expression* Binary_Expression_Ptr; - typedef Binary_Expression const* Binary_Expression_Ptr_Const; - class Unary_Expression; - typedef Unary_Expression* Unary_Expression_Ptr; - typedef Unary_Expression const* Unary_Expression_Ptr_Const; - class Function_Call; - typedef Function_Call* Function_Call_Ptr; - typedef Function_Call const* Function_Call_Ptr_Const; - class Function_Call_Schema; - typedef Function_Call_Schema* Function_Call_Schema_Ptr; - typedef Function_Call_Schema const* Function_Call_Schema_Ptr_Const; - class Custom_Warning; - typedef Custom_Warning* Custom_Warning_Ptr; - typedef Custom_Warning const* Custom_Warning_Ptr_Const; - class Custom_Error; - typedef Custom_Error* Custom_Error_Ptr; - typedef Custom_Error const* Custom_Error_Ptr_Const; - - class Variable; - typedef Variable* Variable_Ptr; - typedef Variable const* Variable_Ptr_Const; - class Number; - typedef Number* Number_Ptr; - typedef Number const* Number_Ptr_Const; - class Color; - typedef Color* Color_Ptr; - typedef Color const* Color_Ptr_Const; - class Boolean; - typedef Boolean* Boolean_Ptr; - typedef Boolean const* Boolean_Ptr_Const; - class String; - typedef String* String_Ptr; - typedef String const* String_Ptr_Const; - - class String_Schema; - typedef String_Schema* String_Schema_Ptr; - typedef String_Schema const* String_Schema_Ptr_Const; - class String_Constant; - typedef String_Constant* String_Constant_Ptr; - typedef String_Constant const* String_Constant_Ptr_Const; - class String_Quoted; - typedef String_Quoted* String_Quoted_Ptr; - typedef String_Quoted const* String_Quoted_Ptr_Const; - - class Media_Query; - typedef Media_Query* Media_Query_Ptr; - typedef Media_Query const* Media_Query_Ptr_Const; - class Media_Query_Expression; - typedef Media_Query_Expression* Media_Query_Expression_Ptr; - typedef Media_Query_Expression const* Media_Query_Expression_Ptr_Const; - class Supports_Condition; - typedef Supports_Condition* Supports_Condition_Ptr; - typedef Supports_Condition const* Supports_Condition_Ptr_Const; - class Supports_Operator; - typedef Supports_Operator* Supports_Operator_Ptr; - typedef Supports_Operator const* Supports_Operator_Ptr_Const; - class Supports_Negation; - typedef Supports_Negation* Supports_Negation_Ptr; - typedef Supports_Negation const* Supports_Negation_Ptr_Const; - class Supports_Declaration; - typedef Supports_Declaration* Supports_Declaration_Ptr; - typedef Supports_Declaration const* Supports_Declaration_Ptr_Const; - class Supports_Interpolation; - typedef Supports_Interpolation* Supports_Interpolation_Ptr; - typedef Supports_Interpolation const* Supports_Interpolation_Ptr_Const; - - - class Null; - typedef Null* Null_Ptr; - typedef Null const* Null_Ptr_Const; - - class At_Root_Query; - typedef At_Root_Query* At_Root_Query_Ptr; - typedef At_Root_Query const* At_Root_Query_Ptr_Const; - class Parent_Selector; - typedef Parent_Selector* Parent_Selector_Ptr; - typedef Parent_Selector const* Parent_Selector_Ptr_Const; - class Parameter; - typedef Parameter* Parameter_Ptr; - typedef Parameter const* Parameter_Ptr_Const; - class Parameters; - typedef Parameters* Parameters_Ptr; - typedef Parameters const* Parameters_Ptr_Const; - class Argument; - typedef Argument* Argument_Ptr; - typedef Argument const* Argument_Ptr_Const; - class Arguments; - typedef Arguments* Arguments_Ptr; - typedef Arguments const* Arguments_Ptr_Const; - class Selector; - typedef Selector* Selector_Ptr; - typedef Selector const* Selector_Ptr_Const; - - - class Selector_Schema; - typedef Selector_Schema* Selector_Schema_Ptr; - typedef Selector_Schema const* Selector_Schema_Ptr_Const; - class Placeholder_Selector; - typedef Placeholder_Selector* Placeholder_Selector_Ptr; - typedef Placeholder_Selector const* Placeholder_Selector_Ptr_Const; - class Element_Selector; - typedef Element_Selector* Element_Selector_Ptr; - typedef Element_Selector const* Element_Selector_Ptr_Const; - class Class_Selector; - typedef Class_Selector* Class_Selector_Ptr; - typedef Class_Selector const* Class_Selector_Ptr_Const; - class Id_Selector; - typedef Id_Selector* Id_Selector_Ptr; - typedef Id_Selector const* Id_Selector_Ptr_Const; - class Attribute_Selector; - typedef Attribute_Selector* Attribute_Selector_Ptr; - typedef Attribute_Selector const* Attribute_Selector_Ptr_Const; - - class Pseudo_Selector; - typedef Pseudo_Selector* Pseudo_Selector_Ptr; - typedef Pseudo_Selector const * Pseudo_Selector_Ptr_Const; - class Wrapped_Selector; - typedef Wrapped_Selector* Wrapped_Selector_Ptr; - typedef Wrapped_Selector const * Wrapped_Selector_Ptr_Const; - class Compound_Selector; - typedef Compound_Selector* Compound_Selector_Ptr; - typedef Compound_Selector const * Compound_Selector_Ptr_Const; - class Complex_Selector; - typedef Complex_Selector* Complex_Selector_Ptr; - typedef Complex_Selector const * Complex_Selector_Ptr_Const; - class Selector_List; - typedef Selector_List* Selector_List_Ptr; - typedef Selector_List const * Selector_List_Ptr_Const; - - - // common classes - class Context; - class Expand; - class Eval; - - // declare classes that are instances of memory nodes - // #define IMPL_MEM_OBJ(type) using type##_Obj = SharedImpl - #define IMPL_MEM_OBJ(type) typedef SharedImpl type##_Obj - - IMPL_MEM_OBJ(AST_Node); - IMPL_MEM_OBJ(Statement); - IMPL_MEM_OBJ(Block); - IMPL_MEM_OBJ(Ruleset); - IMPL_MEM_OBJ(Bubble); - IMPL_MEM_OBJ(Trace); - IMPL_MEM_OBJ(Media_Block); - IMPL_MEM_OBJ(Supports_Block); - IMPL_MEM_OBJ(Directive); - IMPL_MEM_OBJ(Keyframe_Rule); - IMPL_MEM_OBJ(At_Root_Block); - IMPL_MEM_OBJ(Declaration); - IMPL_MEM_OBJ(Assignment); - IMPL_MEM_OBJ(Import); - IMPL_MEM_OBJ(Import_Stub); - IMPL_MEM_OBJ(Warning); - IMPL_MEM_OBJ(Error); - IMPL_MEM_OBJ(Debug); - IMPL_MEM_OBJ(Comment); - IMPL_MEM_OBJ(PreValue); - IMPL_MEM_OBJ(Has_Block); - IMPL_MEM_OBJ(Thunk); - IMPL_MEM_OBJ(If); - IMPL_MEM_OBJ(For); - IMPL_MEM_OBJ(Each); - IMPL_MEM_OBJ(While); - IMPL_MEM_OBJ(Return); - IMPL_MEM_OBJ(Content); - IMPL_MEM_OBJ(Extension); - IMPL_MEM_OBJ(Definition); - IMPL_MEM_OBJ(Mixin_Call); - IMPL_MEM_OBJ(Value); - IMPL_MEM_OBJ(Expression); - IMPL_MEM_OBJ(List); - IMPL_MEM_OBJ(Map); - IMPL_MEM_OBJ(Function); - IMPL_MEM_OBJ(Binary_Expression); - IMPL_MEM_OBJ(Unary_Expression); - IMPL_MEM_OBJ(Function_Call); - IMPL_MEM_OBJ(Function_Call_Schema); - IMPL_MEM_OBJ(Custom_Warning); - IMPL_MEM_OBJ(Custom_Error); - IMPL_MEM_OBJ(Variable); - IMPL_MEM_OBJ(Number); - IMPL_MEM_OBJ(Color); - IMPL_MEM_OBJ(Boolean); - IMPL_MEM_OBJ(String_Schema); - IMPL_MEM_OBJ(String); - IMPL_MEM_OBJ(String_Constant); - IMPL_MEM_OBJ(String_Quoted); - IMPL_MEM_OBJ(Media_Query); - IMPL_MEM_OBJ(Media_Query_Expression); - IMPL_MEM_OBJ(Supports_Condition); - IMPL_MEM_OBJ(Supports_Operator); - IMPL_MEM_OBJ(Supports_Negation); - IMPL_MEM_OBJ(Supports_Declaration); - IMPL_MEM_OBJ(Supports_Interpolation); - IMPL_MEM_OBJ(At_Root_Query); - IMPL_MEM_OBJ(Null); - IMPL_MEM_OBJ(Parent_Selector); - IMPL_MEM_OBJ(Parameter); - IMPL_MEM_OBJ(Parameters); - IMPL_MEM_OBJ(Argument); - IMPL_MEM_OBJ(Arguments); - IMPL_MEM_OBJ(Selector); - IMPL_MEM_OBJ(Selector_Schema); - IMPL_MEM_OBJ(Simple_Selector); - IMPL_MEM_OBJ(Placeholder_Selector); - IMPL_MEM_OBJ(Element_Selector); - IMPL_MEM_OBJ(Class_Selector); - IMPL_MEM_OBJ(Id_Selector); - IMPL_MEM_OBJ(Attribute_Selector); - IMPL_MEM_OBJ(Pseudo_Selector); - IMPL_MEM_OBJ(Wrapped_Selector); - IMPL_MEM_OBJ(Compound_Selector); - IMPL_MEM_OBJ(Complex_Selector); - IMPL_MEM_OBJ(Selector_List); - - // ########################################################################### - // Implement compare, order and hashing operations for AST Nodes - // ########################################################################### - - struct HashNodes { - template - size_t operator() (const T& ex) const { - return ex.isNull() ? 0 : ex->hash(); - } - }; - struct OrderNodes { - template - bool operator() (const T& lhs, const T& rhs) const { - return !lhs.isNull() && !rhs.isNull() && *lhs < *rhs; - } - }; - struct CompareNodes { - template - bool operator() (const T& lhs, const T& rhs) const { - // code around sass logic issue. 1px == 1 is true - // but both items are still different keys in maps - if (dynamic_cast(lhs.ptr())) - if (dynamic_cast(rhs.ptr())) - return lhs->hash() == rhs->hash(); - return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs; - } - }; - - // ########################################################################### - // some often used typedefs - // ########################################################################### - - typedef std::unordered_map< - Expression_Obj, // key - Expression_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > ExpressionMap; - typedef std::unordered_set< - Expression_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > ExpressionSet; - - typedef std::string SubSetMapKey; - typedef std::vector SubSetMapKeys; - - typedef std::pair SubSetMapPair; - typedef std::pair SubSetMapLookup; - typedef std::vector SubSetMapPairs; - typedef std::vector SubSetMapLookups; - - typedef std::pair SubSetMapResult; - typedef std::vector SubSetMapResults; - - typedef std::deque ComplexSelectorDeque; - typedef std::set SimpleSelectorSet; - typedef std::set ComplexSelectorSet; - typedef std::set CompoundSelectorSet; - typedef std::unordered_set SimpleSelectorDict; - - typedef std::vector* ImporterStack; - - // only to switch implementations for testing - #define environment_map std::map - - // ########################################################################### - // explicit type conversion functions - // ########################################################################### - - template - T* Cast(AST_Node* ptr); - - template - const T* Cast(const AST_Node* ptr); - - // sometimes you know the class you want to cast to is final - // in this case a simple typeid check is faster and safe to use - - #define DECLARE_BASE_CAST(T) \ - template<> T* Cast(AST_Node* ptr); \ - template<> const T* Cast(const AST_Node* ptr); \ - - // ########################################################################### - // implement specialization for final classes - // ########################################################################### - - DECLARE_BASE_CAST(AST_Node) - DECLARE_BASE_CAST(Expression) - DECLARE_BASE_CAST(Statement) - DECLARE_BASE_CAST(Has_Block) - DECLARE_BASE_CAST(PreValue) - DECLARE_BASE_CAST(Value) - DECLARE_BASE_CAST(List) - DECLARE_BASE_CAST(String) - DECLARE_BASE_CAST(String_Constant) - DECLARE_BASE_CAST(Supports_Condition) - DECLARE_BASE_CAST(Selector) - DECLARE_BASE_CAST(Simple_Selector) - -} - -#endif diff --git a/src/libsass/src/b64/cencode.h b/src/libsass/src/b64/cencode.h deleted file mode 100644 index 1d71e83fd..000000000 --- a/src/libsass/src/b64/cencode.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -cencode.h - c header for a base64 encoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifndef BASE64_CENCODE_H -#define BASE64_CENCODE_H - -typedef enum -{ - step_A, step_B, step_C -} base64_encodestep; - -typedef struct -{ - base64_encodestep step; - char result; - int stepcount; -} base64_encodestate; - -void base64_init_encodestate(base64_encodestate* state_in); - -char base64_encode_value(char value_in); - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in); - -#endif /* BASE64_CENCODE_H */ - diff --git a/src/libsass/src/b64/encode.h b/src/libsass/src/b64/encode.h deleted file mode 100644 index 92df8ec70..000000000 --- a/src/libsass/src/b64/encode.h +++ /dev/null @@ -1,79 +0,0 @@ -// :mode=c++: -/* -encode.h - c++ wrapper for a base64 encoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ -#ifndef BASE64_ENCODE_H -#define BASE64_ENCODE_H - -#include - -namespace base64 -{ - extern "C" - { - #include "cencode.h" - } - - struct encoder - { - base64_encodestate _state; - int _buffersize; - - encoder(int buffersize_in = BUFFERSIZE) - : _buffersize(buffersize_in) - { - base64_init_encodestate(&_state); - } - - int encode(char value_in) - { - return base64_encode_value(value_in); - } - - int encode(const char* code_in, const int length_in, char* plaintext_out) - { - return base64_encode_block(code_in, length_in, plaintext_out, &_state); - } - - int encode_end(char* plaintext_out) - { - return base64_encode_blockend(plaintext_out, &_state); - } - - void encode(std::istream& istream_in, std::ostream& ostream_in) - { - base64_init_encodestate(&_state); - // - const int N = _buffersize; - char* plaintext = new char[N]; - char* code = new char[2*N]; - int plainlength; - int codelength; - - do - { - istream_in.read(plaintext, N); - plainlength = static_cast(istream_in.gcount()); - // - codelength = encode(plaintext, plainlength, code); - ostream_in.write(code, codelength); - } - while (istream_in.good() && plainlength > 0); - - codelength = encode_end(code); - ostream_in.write(code, codelength); - // - base64_init_encodestate(&_state); - - delete [] code; - delete [] plaintext; - } - }; - -} // namespace base64 - -#endif // BASE64_ENCODE_H - diff --git a/src/libsass/src/backtrace.cpp b/src/libsass/src/backtrace.cpp deleted file mode 100644 index 8da963a72..000000000 --- a/src/libsass/src/backtrace.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "backtrace.hpp" - -namespace Sass { - - const std::string traces_to_string(Backtraces traces, std::string indent) { - - std::stringstream ss; - std::string cwd(File::get_cwd()); - - bool first = true; - size_t i_beg = traces.size() - 1; - size_t i_end = std::string::npos; - for (size_t i = i_beg; i != i_end; i --) { - - const Backtrace& trace = traces[i]; - - // make path relative to the current directory - std::string rel_path(File::abs2rel(trace.pstate.path, cwd, cwd)); - - // skip functions on error cases (unsure why ruby sass does this) - // if (trace.caller.substr(0, 6) == ", in f") continue; - - if (first) { - ss << indent; - ss << "on line "; - ss << trace.pstate.line + 1; - ss << " of " << rel_path; - // ss << trace.caller; - first = false; - } else { - ss << trace.caller; - ss << std::endl; - ss << indent; - ss << "from line "; - ss << trace.pstate.line + 1; - ss << " of " << rel_path; - } - - } - - ss << std::endl; - return ss.str(); - - } - -}; diff --git a/src/libsass/src/backtrace.hpp b/src/libsass/src/backtrace.hpp deleted file mode 100644 index 72d5fe517..000000000 --- a/src/libsass/src/backtrace.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SASS_BACKTRACE_H -#define SASS_BACKTRACE_H - -#include -#include -#include "file.hpp" -#include "position.hpp" - -namespace Sass { - - struct Backtrace { - - ParserState pstate; - std::string caller; - - Backtrace(ParserState pstate, std::string c = "") - : pstate(pstate), - caller(c) - { } - - }; - - typedef std::vector Backtraces; - - const std::string traces_to_string(Backtraces traces, std::string indent = "\t"); - -} - -#endif diff --git a/src/libsass/src/base64vlq.cpp b/src/libsass/src/base64vlq.cpp deleted file mode 100644 index be2fb4926..000000000 --- a/src/libsass/src/base64vlq.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "sass.hpp" -#include "base64vlq.hpp" - -namespace Sass { - - std::string Base64VLQ::encode(const int number) const - { - std::string encoded = ""; - - int vlq = to_vlq_signed(number); - - do { - int digit = vlq & VLQ_BASE_MASK; - vlq >>= VLQ_BASE_SHIFT; - if (vlq > 0) { - digit |= VLQ_CONTINUATION_BIT; - } - encoded += base64_encode(digit); - } while (vlq > 0); - - return encoded; - } - - char Base64VLQ::base64_encode(const int number) const - { - int index = number; - if (index < 0) index = 0; - if (index > 63) index = 63; - return CHARACTERS[index]; - } - - int Base64VLQ::to_vlq_signed(const int number) const - { - return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; - } - - const char* Base64VLQ::CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - const int Base64VLQ::VLQ_BASE_SHIFT = 5; - const int Base64VLQ::VLQ_BASE = 1 << VLQ_BASE_SHIFT; - const int Base64VLQ::VLQ_BASE_MASK = VLQ_BASE - 1; - const int Base64VLQ::VLQ_CONTINUATION_BIT = VLQ_BASE; - -} diff --git a/src/libsass/src/base64vlq.hpp b/src/libsass/src/base64vlq.hpp deleted file mode 100644 index aca315a21..000000000 --- a/src/libsass/src/base64vlq.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SASS_BASE64VLQ_H -#define SASS_BASE64VLQ_H - -#include - -namespace Sass { - - class Base64VLQ { - - public: - - std::string encode(const int number) const; - - private: - - char base64_encode(const int number) const; - - int to_vlq_signed(const int number) const; - - static const char* CHARACTERS; - - static const int VLQ_BASE_SHIFT; - static const int VLQ_BASE; - static const int VLQ_BASE_MASK; - static const int VLQ_CONTINUATION_BIT; - }; - -} - -#endif diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp deleted file mode 100644 index ec20ac838..000000000 --- a/src/libsass/src/bind.cpp +++ /dev/null @@ -1,311 +0,0 @@ -#include "sass.hpp" -#include "bind.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include -#include -#include - -namespace Sass { - - void bind(std::string type, std::string name, Parameters_Obj ps, Arguments_Obj as, Context* ctx, Env* env, Eval* eval) - { - std::string callee(type + " " + name); - - std::map param_map; - List_Obj varargs = SASS_MEMORY_NEW(List, as->pstate()); - varargs->is_arglist(true); // enable keyword size handling - - for (size_t i = 0, L = as->length(); i < L; ++i) { - if (auto str = Cast((*as)[i]->value())) { - // force optional quotes (only if needed) - if (str->quote_mark()) { - str->quote_mark('*'); - } - } - } - - // Set up a map to ensure named arguments refer to actual parameters. Also - // eval each default value left-to-right, wrt env, populating env as we go. - for (size_t i = 0, L = ps->length(); i < L; ++i) { - Parameter_Obj p = ps->at(i); - param_map[p->name()] = p; - // if (p->default_value()) { - // env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env)); - // } - } - - // plug in all args; if we have leftover params, deal with it later - size_t ip = 0, LP = ps->length(); - size_t ia = 0, LA = as->length(); - while (ia < LA) { - Argument_Obj a = as->at(ia); - if (ip >= LP) { - // skip empty rest arguments - if (a->is_rest_argument()) { - if (List_Obj l = Cast(a->value())) { - if (l->length() == 0) { - ++ ia; continue; - } - } - } - std::stringstream msg; - msg << "wrong number of arguments (" << LA << " for " << LP << ")"; - msg << " for `" << name << "'"; - return error(msg.str(), as->pstate(), eval->exp.traces); - } - Parameter_Obj p = ps->at(ip); - - // If the current parameter is the rest parameter, process and break the loop - if (p->is_rest_parameter()) { - // The next argument by coincidence provides a rest argument - if (a->is_rest_argument()) { - - // We should always get a list for rest arguments - if (List_Obj rest = Cast(a->value())) { - // create a new list object for wrapped items - List_Ptr arglist = SASS_MEMORY_NEW(List, - p->pstate(), - 0, - rest->separator(), - true); - // wrap each item from list as an argument - for (Expression_Obj item : rest->elements()) { - if (Argument_Obj arg = Cast(item)) { - arglist->append(SASS_MEMORY_COPY(arg)); // copy - } else { - arglist->append(SASS_MEMORY_NEW(Argument, - item->pstate(), - item, - "", - false, - false)); - } - } - // assign new arglist to environment - env->local_frame()[p->name()] = arglist; - } - // invalid state - else { - throw std::runtime_error("invalid state"); - } - } else if (a->is_keyword_argument()) { - - // expand keyword arguments into their parameters - List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); - env->local_frame()[p->name()] = arglist; - Map_Obj argmap = Cast(a->value()); - for (auto key : argmap->keys()) { - if (String_Constant_Obj str = Cast(key)) { - std::string param = unquote(str->value()); - arglist->append(SASS_MEMORY_NEW(Argument, - key->pstate(), - argmap->at(key), - "$" + param, - false, - false)); - } else { - eval->exp.traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); - } - } - - } else { - - // create a new list object for wrapped items - List_Obj arglist = SASS_MEMORY_NEW(List, - p->pstate(), - 0, - SASS_COMMA, - true); - // consume the next args - while (ia < LA) { - // get and post inc - a = (*as)[ia++]; - // maybe we have another list as argument - List_Obj ls = Cast(a->value()); - // skip any list completely if empty - if (ls && ls->empty() && a->is_rest_argument()) continue; - - Expression_Obj value = a->value(); - if (Argument_Obj arg = Cast(value)) { - arglist->append(arg); - } - // check if we have rest argument - else if (a->is_rest_argument()) { - // preserve the list separator from rest args - if (List_Obj rest = Cast(a->value())) { - arglist->separator(rest->separator()); - - for (size_t i = 0, L = rest->length(); i < L; ++i) { - Expression_Obj obj = rest->value_at_index(i); - arglist->append(SASS_MEMORY_NEW(Argument, - obj->pstate(), - obj, - "", - false, - false)); - } - } - // no more arguments - break; - } - // wrap all other value types into Argument - else { - arglist->append(SASS_MEMORY_NEW(Argument, - a->pstate(), - a->value(), - a->name(), - false, - false)); - } - } - // assign new arglist to environment - env->local_frame()[p->name()] = arglist; - } - // consumed parameter - ++ip; - // no more paramaters - break; - } - - // If the current argument is the rest argument, extract a value for processing - else if (a->is_rest_argument()) { - // normal param and rest arg - List_Obj arglist = Cast(a->value()); - if (!arglist) { - if (Expression_Obj arg = Cast(a->value())) { - arglist = SASS_MEMORY_NEW(List, a->pstate(), 1); - arglist->append(arg); - } - } - - // empty rest arg - treat all args as default values - if (!arglist || !arglist->length()) { - break; - } else { - if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { - size_t arg_count = (arglist->length() + LA - 1); - std::stringstream msg; - msg << callee << " takes " << LP; - msg << (LP == 1 ? " argument" : " arguments"); - msg << " but " << arg_count; - msg << (arg_count == 1 ? " was passed" : " were passed."); - deprecated_bind(msg.str(), as->pstate()); - - while (arglist->length() > LP - ip) { - arglist->elements().erase(arglist->elements().end() - 1); - } - } - } - // otherwise move one of the rest args into the param, converting to argument if necessary - Expression_Obj obj = arglist->at(0); - if (!(a = Cast(obj))) { - Expression_Ptr a_to_convert = obj; - a = SASS_MEMORY_NEW(Argument, - a_to_convert->pstate(), - a_to_convert, - "", - false, - false); - } - arglist->elements().erase(arglist->elements().begin()); - if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) { - ++ia; - } - - } else if (a->is_keyword_argument()) { - Map_Obj argmap = Cast(a->value()); - - for (auto key : argmap->keys()) { - String_Constant_Ptr val = Cast(key); - if (val == NULL) { - eval->exp.traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); - } - std::string param = "$" + unquote(val->value()); - - if (!param_map.count(param)) { - std::stringstream msg; - msg << callee << " has no parameter named " << param; - error(msg.str(), a->pstate(), eval->exp.traces); - } - env->local_frame()[param] = argmap->at(key); - } - ++ia; - continue; - } else { - ++ia; - } - - if (a->name().empty()) { - if (env->has_local(p->name())) { - std::stringstream msg; - msg << "parameter " << p->name() - << " provided more than once in call to " << callee; - error(msg.str(), a->pstate(), eval->exp.traces); - } - // ordinal arg -- bind it to the next param - env->local_frame()[p->name()] = a->value(); - ++ip; - } - else { - // named arg -- bind it to the appropriately named param - if (!param_map.count(a->name())) { - if (ps->has_rest_parameter()) { - varargs->append(a); - } else { - std::stringstream msg; - msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate(), eval->exp.traces); - } - } - if (param_map[a->name()]) { - if (param_map[a->name()]->is_rest_parameter()) { - std::stringstream msg; - msg << "argument " << a->name() << " of " << callee - << "cannot be used as named argument"; - error(msg.str(), a->pstate(), eval->exp.traces); - } - } - if (env->has_local(a->name())) { - std::stringstream msg; - msg << "parameter " << p->name() - << "provided more than once in call to " << callee; - error(msg.str(), a->pstate(), eval->exp.traces); - } - env->local_frame()[a->name()] = a->value(); - } - } - // EO while ia - - // If we make it here, we're out of args but may have leftover params. - // That's only okay if they have default values, or were already bound by - // named arguments, or if it's a single rest-param. - for (size_t i = ip; i < LP; ++i) { - Parameter_Obj leftover = ps->at(i); - // cerr << "env for default params:" << endl; - // env->print(); - // cerr << "********" << endl; - if (!env->has_local(leftover->name())) { - if (leftover->is_rest_parameter()) { - env->local_frame()[leftover->name()] = varargs; - } - else if (leftover->default_value()) { - Expression_Ptr dv = leftover->default_value()->perform(eval); - env->local_frame()[leftover->name()] = dv; - } - else { - // param is unbound and has no default value -- error - throw Exception::MissingArgument(as->pstate(), eval->exp.traces, name, leftover->name(), type); - } - } - } - - return; - } - - -} diff --git a/src/libsass/src/bind.hpp b/src/libsass/src/bind.hpp deleted file mode 100644 index 93a503aa6..000000000 --- a/src/libsass/src/bind.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef SASS_BIND_H -#define SASS_BIND_H - -#include -#include "environment.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Context*, Env*, Eval*); -} - -#endif diff --git a/src/libsass/src/c99func.c b/src/libsass/src/c99func.c deleted file mode 100644 index f846eee80..000000000 --- a/src/libsass/src/c99func.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#if defined(_MSC_VER) && _MSC_VER < 1900 - -#include -#include -#include - -static int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) -{ - int count = -1; - - if (size != 0) - count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); - if (count == -1) - count = _vscprintf(format, ap); - - return count; -} - -int snprintf(char* str, size_t size, const char* format, ...) -{ - int count; - va_list ap; - - va_start(ap, format); - count = c99_vsnprintf(str, size, format, ap); - va_end(ap); - - return count; -} - -#endif diff --git a/src/libsass/src/cencode.c b/src/libsass/src/cencode.c deleted file mode 100644 index 00ac24e28..000000000 --- a/src/libsass/src/cencode.c +++ /dev/null @@ -1,108 +0,0 @@ -/* -cencoder.c - c source to a base64 encoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#include "b64/cencode.h" - -void base64_init_encodestate(base64_encodestate* state_in) -{ - state_in->step = step_A; - state_in->result = 0; - state_in->stepcount = 0; -} - -char base64_encode_value(char value_in) -{ - static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - if (value_in > 63) return '='; - return encoding[(int)value_in]; -} - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) -{ - const char* plainchar = plaintext_in; - const char* const plaintextend = plaintext_in + length_in; - char* codechar = code_out; - char result; - char fragment; - - result = state_in->result; - - switch (state_in->step) - { - while (1) - { - case step_A: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_A; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result = (fragment & 0x0fc) >> 2; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x003) << 4; - #ifndef _MSC_VER - __attribute__ ((fallthrough)); - #endif - case step_B: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_B; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result |= (fragment & 0x0f0) >> 4; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x00f) << 2; - #ifndef _MSC_VER - __attribute__ ((fallthrough)); - #endif - case step_C: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_C; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result |= (fragment & 0x0c0) >> 6; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x03f) >> 0; - *codechar++ = base64_encode_value(result); - - ++(state_in->stepcount); - } - } - /* control should not reach here */ - return (int)(codechar - code_out); -} - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in) -{ - char* codechar = code_out; - - switch (state_in->step) - { - case step_B: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - *codechar++ = '='; - break; - case step_C: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - break; - case step_A: - break; - } - *codechar++ = '\n'; - - return (int)(codechar - code_out); -} - diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp deleted file mode 100644 index 880bcca37..000000000 --- a/src/libsass/src/check_nesting.cpp +++ /dev/null @@ -1,398 +0,0 @@ -#include "sass.hpp" -#include - -#include "check_nesting.hpp" - -namespace Sass { - - CheckNesting::CheckNesting() - : parents(std::vector()), - traces(std::vector()), - parent(0), current_mixin_definition(0) - { } - - void error(AST_Node_Ptr node, Backtraces traces, std::string msg) { - traces.push_back(Backtrace(node->pstate())); - throw Exception::InvalidSass(node->pstate(), traces, msg); - } - - Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) - { - Statement_Ptr old_parent = this->parent; - - if (At_Root_Block_Ptr root = Cast(parent)) { - std::vector old_parents = this->parents; - std::vector new_parents; - - for (size_t i = 0, L = this->parents.size(); i < L; i++) { - Statement_Ptr p = this->parents.at(i); - if (!root->exclude_node(p)) { - new_parents.push_back(p); - } - } - this->parents = new_parents; - - for (size_t i = this->parents.size(); i > 0; i--) { - Statement_Ptr p = 0; - Statement_Ptr gp = 0; - if (i > 0) p = this->parents.at(i - 1); - if (i > 1) gp = this->parents.at(i - 2); - - if (!this->is_transparent_parent(p, gp)) { - this->parent = p; - break; - } - } - - At_Root_Block_Ptr ar = Cast(parent); - Block_Ptr ret = ar->block(); - - if (ret != NULL) { - for (auto n : ret->elements()) { - n->perform(this); - } - } - - this->parent = old_parent; - this->parents = old_parents; - - return ret; - } - - if (!this->is_transparent_parent(parent, old_parent)) { - this->parent = parent; - } - - this->parents.push_back(parent); - - Block_Ptr b = Cast(parent); - - if (Trace_Ptr trace = Cast(parent)) { - if (trace->type() == 'i') { - this->traces.push_back(Backtrace(trace->pstate())); - } - } - - if (!b) { - if (Has_Block_Ptr bb = Cast(parent)) { - b = bb->block(); - } - } - - if (b) { - for (auto n : b->elements()) { - n->perform(this); - } - } - - this->parent = old_parent; - this->parents.pop_back(); - - if (Trace_Ptr trace = Cast(parent)) { - if (trace->type() == 'i') { - this->traces.pop_back(); - } - } - - return b; - } - - - Statement_Ptr CheckNesting::operator()(Block_Ptr b) - { - return this->visit_children(b); - } - - Statement_Ptr CheckNesting::operator()(Definition_Ptr n) - { - if (!this->should_visit(n)) return NULL; - if (!is_mixin(n)) { - visit_children(n); - return n; - } - - Definition_Ptr old_mixin_definition = this->current_mixin_definition; - this->current_mixin_definition = n; - - visit_children(n); - - this->current_mixin_definition = old_mixin_definition; - - return n; - } - - Statement_Ptr CheckNesting::operator()(If_Ptr i) - { - this->visit_children(i); - - if (Block_Ptr b = Cast(i->alternative())) { - for (auto n : b->elements()) n->perform(this); - } - - return i; - } - - Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) - { - Block_Ptr b1 = Cast(s); - Has_Block_Ptr b2 = Cast(s); - return b1 || b2 ? visit_children(s) : s; - } - - bool CheckNesting::should_visit(Statement_Ptr node) - { - if (!this->parent) return true; - - if (Cast(node)) - { this->invalid_content_parent(this->parent, node); } - - if (is_charset(node)) - { this->invalid_charset_parent(this->parent, node); } - - if (Cast(node)) - { this->invalid_extend_parent(this->parent, node); } - - // if (Cast(node)) - // { this->invalid_import_parent(this->parent); } - - if (this->is_mixin(node)) - { this->invalid_mixin_definition_parent(this->parent, node); } - - if (this->is_function(node)) - { this->invalid_function_parent(this->parent, node); } - - if (this->is_function(this->parent)) - { this->invalid_function_child(node); } - - if (Declaration_Ptr d = Cast(node)) - { - this->invalid_prop_parent(this->parent, node); - this->invalid_value_child(d->value()); - } - - if (Cast(this->parent)) - { this->invalid_prop_child(node); } - - if (Cast(node)) - { this->invalid_return_parent(this->parent, node); } - - return true; - } - - void CheckNesting::invalid_content_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - if (!this->current_mixin_definition) { - error(node, traces, "@content may only be used within a mixin."); - } - } - - void CheckNesting::invalid_charset_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - if (!( - is_root_node(parent) - )) { - error(node, traces, "@charset may only be used at the root of a document."); - } - } - - void CheckNesting::invalid_extend_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - if (!( - Cast(parent) || - Cast(parent) || - is_mixin(parent) - )) { - error(node, traces, "Extend directives may only be used within rules."); - } - } - - // void CheckNesting::invalid_import_parent(Statement_Ptr parent, AST_Node_Ptr node) - // { - // for (auto pp : this->parents) { - // if ( - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // is_mixin(pp) - // ) { - // error(node, traces, "Import directives may not be defined within control directives or other mixins."); - // } - // } - - // if (this->is_root_node(parent)) { - // return; - // } - - // if (false/*n.css_import?*/) { - // error(node, traces, "CSS import directives may only be used at the root of a document."); - // } - // } - - void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - for (Statement_Ptr pp : this->parents) { - if ( - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - is_mixin(pp) - ) { - error(node, traces, "Mixins may not be defined within control directives or other mixins."); - } - } - } - - void CheckNesting::invalid_function_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - for (Statement_Ptr pp : this->parents) { - if ( - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - is_mixin(pp) - ) { - error(node, traces, "Functions may not be defined within control directives or other mixins."); - } - } - } - - void CheckNesting::invalid_function_child(Statement_Ptr child) - { - if (!( - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - // Ruby Sass doesn't distinguish variables and assignments - Cast(child) || - Cast(child) || - Cast(child) - )) { - error(child, traces, "Functions can only contain variable declarations and control directives."); - } - } - - void CheckNesting::invalid_prop_child(Statement_Ptr child) - { - if (!( - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) - )) { - error(child, traces, "Illegal nesting: Only properties may be nested beneath properties."); - } - } - - void CheckNesting::invalid_prop_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - if (!( - is_mixin(parent) || - is_directive_node(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) - )) { - error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties."); - } - } - - void CheckNesting::invalid_value_child(AST_Node_Ptr d) - { - if (Map_Ptr m = Cast(d)) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::InvalidValue(traces, *m); - } - if (Number_Ptr n = Cast(d)) { - if (!n->is_valid_css_unit()) { - traces.push_back(Backtrace(n->pstate())); - throw Exception::InvalidValue(traces, *n); - } - } - - // error(dbg + " isn't a valid CSS value.", m->pstate(),); - - } - - void CheckNesting::invalid_return_parent(Statement_Ptr parent, AST_Node_Ptr node) - { - if (!this->is_function(parent)) { - error(node, traces, "@return may only be used within a function."); - } - } - - bool CheckNesting::is_transparent_parent(Statement_Ptr parent, Statement_Ptr grandparent) - { - bool parent_bubbles = parent && parent->bubbles(); - - bool valid_bubble_node = parent_bubbles && - !is_root_node(grandparent) && - !is_at_root_node(grandparent); - - return Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - valid_bubble_node; - } - - bool CheckNesting::is_charset(Statement_Ptr n) - { - Directive_Ptr d = Cast(n); - return d && d->keyword() == "charset"; - } - - bool CheckNesting::is_mixin(Statement_Ptr n) - { - Definition_Ptr def = Cast(n); - return def && def->type() == Definition::MIXIN; - } - - bool CheckNesting::is_function(Statement_Ptr n) - { - Definition_Ptr def = Cast(n); - return def && def->type() == Definition::FUNCTION; - } - - bool CheckNesting::is_root_node(Statement_Ptr n) - { - if (Cast(n)) return false; - - Block_Ptr b = Cast(n); - return b && b->is_root(); - } - - bool CheckNesting::is_at_root_node(Statement_Ptr n) - { - return Cast(n) != NULL; - } - - bool CheckNesting::is_directive_node(Statement_Ptr n) - { - return Cast(n) || - Cast(n) || - Cast(n) || - Cast(n); - } -} diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp deleted file mode 100644 index 62c38d9dc..000000000 --- a/src/libsass/src/check_nesting.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef SASS_CHECK_NESTING_H -#define SASS_CHECK_NESTING_H - -#include "ast.hpp" -#include "operation.hpp" - -namespace Sass { - - class CheckNesting : public Operation_CRTP { - - std::vector parents; - Backtraces traces; - Statement_Ptr parent; - Definition_Ptr current_mixin_definition; - - Statement_Ptr fallback_impl(Statement_Ptr); - Statement_Ptr before(Statement_Ptr); - Statement_Ptr visit_children(Statement_Ptr); - - public: - CheckNesting(); - ~CheckNesting() { } - - Statement_Ptr operator()(Block_Ptr); - Statement_Ptr operator()(Definition_Ptr); - Statement_Ptr operator()(If_Ptr); - - template - Statement_Ptr fallback(U x) { - Statement_Ptr n = Cast(x); - if (this->should_visit(n)) { - return fallback_impl(n); - } - return NULL; - } - - private: - void invalid_content_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_charset_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_extend_parent(Statement_Ptr, AST_Node_Ptr); - // void invalid_import_parent(Statement_Ptr); - void invalid_mixin_definition_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_function_parent(Statement_Ptr, AST_Node_Ptr); - - void invalid_function_child(Statement_Ptr); - void invalid_prop_child(Statement_Ptr); - void invalid_prop_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_return_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_value_child(AST_Node_Ptr); - - bool is_transparent_parent(Statement_Ptr, Statement_Ptr); - - bool should_visit(Statement_Ptr); - - bool is_charset(Statement_Ptr); - bool is_mixin(Statement_Ptr); - bool is_function(Statement_Ptr); - bool is_root_node(Statement_Ptr); - bool is_at_root_node(Statement_Ptr); - bool is_directive_node(Statement_Ptr); - }; - -} - -#endif diff --git a/src/libsass/src/color_maps.cpp b/src/libsass/src/color_maps.cpp deleted file mode 100644 index 129e47c5a..000000000 --- a/src/libsass/src/color_maps.cpp +++ /dev/null @@ -1,648 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "color_maps.hpp" - -namespace Sass { - - namespace ColorNames - { - const char aliceblue [] = "aliceblue"; - const char antiquewhite [] = "antiquewhite"; - const char cyan [] = "cyan"; - const char aqua [] = "aqua"; - const char aquamarine [] = "aquamarine"; - const char azure [] = "azure"; - const char beige [] = "beige"; - const char bisque [] = "bisque"; - const char black [] = "black"; - const char blanchedalmond [] = "blanchedalmond"; - const char blue [] = "blue"; - const char blueviolet [] = "blueviolet"; - const char brown [] = "brown"; - const char burlywood [] = "burlywood"; - const char cadetblue [] = "cadetblue"; - const char chartreuse [] = "chartreuse"; - const char chocolate [] = "chocolate"; - const char coral [] = "coral"; - const char cornflowerblue [] = "cornflowerblue"; - const char cornsilk [] = "cornsilk"; - const char crimson [] = "crimson"; - const char darkblue [] = "darkblue"; - const char darkcyan [] = "darkcyan"; - const char darkgoldenrod [] = "darkgoldenrod"; - const char darkgray [] = "darkgray"; - const char darkgrey [] = "darkgrey"; - const char darkgreen [] = "darkgreen"; - const char darkkhaki [] = "darkkhaki"; - const char darkmagenta [] = "darkmagenta"; - const char darkolivegreen [] = "darkolivegreen"; - const char darkorange [] = "darkorange"; - const char darkorchid [] = "darkorchid"; - const char darkred [] = "darkred"; - const char darksalmon [] = "darksalmon"; - const char darkseagreen [] = "darkseagreen"; - const char darkslateblue [] = "darkslateblue"; - const char darkslategray [] = "darkslategray"; - const char darkslategrey [] = "darkslategrey"; - const char darkturquoise [] = "darkturquoise"; - const char darkviolet [] = "darkviolet"; - const char deeppink [] = "deeppink"; - const char deepskyblue [] = "deepskyblue"; - const char dimgray [] = "dimgray"; - const char dimgrey [] = "dimgrey"; - const char dodgerblue [] = "dodgerblue"; - const char firebrick [] = "firebrick"; - const char floralwhite [] = "floralwhite"; - const char forestgreen [] = "forestgreen"; - const char magenta [] = "magenta"; - const char fuchsia [] = "fuchsia"; - const char gainsboro [] = "gainsboro"; - const char ghostwhite [] = "ghostwhite"; - const char gold [] = "gold"; - const char goldenrod [] = "goldenrod"; - const char gray [] = "gray"; - const char grey [] = "grey"; - const char green [] = "green"; - const char greenyellow [] = "greenyellow"; - const char honeydew [] = "honeydew"; - const char hotpink [] = "hotpink"; - const char indianred [] = "indianred"; - const char indigo [] = "indigo"; - const char ivory [] = "ivory"; - const char khaki [] = "khaki"; - const char lavender [] = "lavender"; - const char lavenderblush [] = "lavenderblush"; - const char lawngreen [] = "lawngreen"; - const char lemonchiffon [] = "lemonchiffon"; - const char lightblue [] = "lightblue"; - const char lightcoral [] = "lightcoral"; - const char lightcyan [] = "lightcyan"; - const char lightgoldenrodyellow [] = "lightgoldenrodyellow"; - const char lightgray [] = "lightgray"; - const char lightgrey [] = "lightgrey"; - const char lightgreen [] = "lightgreen"; - const char lightpink [] = "lightpink"; - const char lightsalmon [] = "lightsalmon"; - const char lightseagreen [] = "lightseagreen"; - const char lightskyblue [] = "lightskyblue"; - const char lightslategray [] = "lightslategray"; - const char lightslategrey [] = "lightslategrey"; - const char lightsteelblue [] = "lightsteelblue"; - const char lightyellow [] = "lightyellow"; - const char lime [] = "lime"; - const char limegreen [] = "limegreen"; - const char linen [] = "linen"; - const char maroon [] = "maroon"; - const char mediumaquamarine [] = "mediumaquamarine"; - const char mediumblue [] = "mediumblue"; - const char mediumorchid [] = "mediumorchid"; - const char mediumpurple [] = "mediumpurple"; - const char mediumseagreen [] = "mediumseagreen"; - const char mediumslateblue [] = "mediumslateblue"; - const char mediumspringgreen [] = "mediumspringgreen"; - const char mediumturquoise [] = "mediumturquoise"; - const char mediumvioletred [] = "mediumvioletred"; - const char midnightblue [] = "midnightblue"; - const char mintcream [] = "mintcream"; - const char mistyrose [] = "mistyrose"; - const char moccasin [] = "moccasin"; - const char navajowhite [] = "navajowhite"; - const char navy [] = "navy"; - const char oldlace [] = "oldlace"; - const char olive [] = "olive"; - const char olivedrab [] = "olivedrab"; - const char orange [] = "orange"; - const char orangered [] = "orangered"; - const char orchid [] = "orchid"; - const char palegoldenrod [] = "palegoldenrod"; - const char palegreen [] = "palegreen"; - const char paleturquoise [] = "paleturquoise"; - const char palevioletred [] = "palevioletred"; - const char papayawhip [] = "papayawhip"; - const char peachpuff [] = "peachpuff"; - const char peru [] = "peru"; - const char pink [] = "pink"; - const char plum [] = "plum"; - const char powderblue [] = "powderblue"; - const char purple [] = "purple"; - const char red [] = "red"; - const char rosybrown [] = "rosybrown"; - const char royalblue [] = "royalblue"; - const char saddlebrown [] = "saddlebrown"; - const char salmon [] = "salmon"; - const char sandybrown [] = "sandybrown"; - const char seagreen [] = "seagreen"; - const char seashell [] = "seashell"; - const char sienna [] = "sienna"; - const char silver [] = "silver"; - const char skyblue [] = "skyblue"; - const char slateblue [] = "slateblue"; - const char slategray [] = "slategray"; - const char slategrey [] = "slategrey"; - const char snow [] = "snow"; - const char springgreen [] = "springgreen"; - const char steelblue [] = "steelblue"; - const char tan [] = "tan"; - const char teal [] = "teal"; - const char thistle [] = "thistle"; - const char tomato [] = "tomato"; - const char turquoise [] = "turquoise"; - const char violet [] = "violet"; - const char wheat [] = "wheat"; - const char white [] = "white"; - const char whitesmoke [] = "whitesmoke"; - const char yellow [] = "yellow"; - const char yellowgreen [] = "yellowgreen"; - const char rebeccapurple [] = "rebeccapurple"; - const char transparent [] = "transparent"; - } - - namespace Colors { - const ParserState color_table("[COLOR TABLE]"); - const Color aliceblue(color_table, 240, 248, 255, 1); - const Color antiquewhite(color_table, 250, 235, 215, 1); - const Color cyan(color_table, 0, 255, 255, 1); - const Color aqua(color_table, 0, 255, 255, 1); - const Color aquamarine(color_table, 127, 255, 212, 1); - const Color azure(color_table, 240, 255, 255, 1); - const Color beige(color_table, 245, 245, 220, 1); - const Color bisque(color_table, 255, 228, 196, 1); - const Color black(color_table, 0, 0, 0, 1); - const Color blanchedalmond(color_table, 255, 235, 205, 1); - const Color blue(color_table, 0, 0, 255, 1); - const Color blueviolet(color_table, 138, 43, 226, 1); - const Color brown(color_table, 165, 42, 42, 1); - const Color burlywood(color_table, 222, 184, 135, 1); - const Color cadetblue(color_table, 95, 158, 160, 1); - const Color chartreuse(color_table, 127, 255, 0, 1); - const Color chocolate(color_table, 210, 105, 30, 1); - const Color coral(color_table, 255, 127, 80, 1); - const Color cornflowerblue(color_table, 100, 149, 237, 1); - const Color cornsilk(color_table, 255, 248, 220, 1); - const Color crimson(color_table, 220, 20, 60, 1); - const Color darkblue(color_table, 0, 0, 139, 1); - const Color darkcyan(color_table, 0, 139, 139, 1); - const Color darkgoldenrod(color_table, 184, 134, 11, 1); - const Color darkgray(color_table, 169, 169, 169, 1); - const Color darkgrey(color_table, 169, 169, 169, 1); - const Color darkgreen(color_table, 0, 100, 0, 1); - const Color darkkhaki(color_table, 189, 183, 107, 1); - const Color darkmagenta(color_table, 139, 0, 139, 1); - const Color darkolivegreen(color_table, 85, 107, 47, 1); - const Color darkorange(color_table, 255, 140, 0, 1); - const Color darkorchid(color_table, 153, 50, 204, 1); - const Color darkred(color_table, 139, 0, 0, 1); - const Color darksalmon(color_table, 233, 150, 122, 1); - const Color darkseagreen(color_table, 143, 188, 143, 1); - const Color darkslateblue(color_table, 72, 61, 139, 1); - const Color darkslategray(color_table, 47, 79, 79, 1); - const Color darkslategrey(color_table, 47, 79, 79, 1); - const Color darkturquoise(color_table, 0, 206, 209, 1); - const Color darkviolet(color_table, 148, 0, 211, 1); - const Color deeppink(color_table, 255, 20, 147, 1); - const Color deepskyblue(color_table, 0, 191, 255, 1); - const Color dimgray(color_table, 105, 105, 105, 1); - const Color dimgrey(color_table, 105, 105, 105, 1); - const Color dodgerblue(color_table, 30, 144, 255, 1); - const Color firebrick(color_table, 178, 34, 34, 1); - const Color floralwhite(color_table, 255, 250, 240, 1); - const Color forestgreen(color_table, 34, 139, 34, 1); - const Color magenta(color_table, 255, 0, 255, 1); - const Color fuchsia(color_table, 255, 0, 255, 1); - const Color gainsboro(color_table, 220, 220, 220, 1); - const Color ghostwhite(color_table, 248, 248, 255, 1); - const Color gold(color_table, 255, 215, 0, 1); - const Color goldenrod(color_table, 218, 165, 32, 1); - const Color gray(color_table, 128, 128, 128, 1); - const Color grey(color_table, 128, 128, 128, 1); - const Color green(color_table, 0, 128, 0, 1); - const Color greenyellow(color_table, 173, 255, 47, 1); - const Color honeydew(color_table, 240, 255, 240, 1); - const Color hotpink(color_table, 255, 105, 180, 1); - const Color indianred(color_table, 205, 92, 92, 1); - const Color indigo(color_table, 75, 0, 130, 1); - const Color ivory(color_table, 255, 255, 240, 1); - const Color khaki(color_table, 240, 230, 140, 1); - const Color lavender(color_table, 230, 230, 250, 1); - const Color lavenderblush(color_table, 255, 240, 245, 1); - const Color lawngreen(color_table, 124, 252, 0, 1); - const Color lemonchiffon(color_table, 255, 250, 205, 1); - const Color lightblue(color_table, 173, 216, 230, 1); - const Color lightcoral(color_table, 240, 128, 128, 1); - const Color lightcyan(color_table, 224, 255, 255, 1); - const Color lightgoldenrodyellow(color_table, 250, 250, 210, 1); - const Color lightgray(color_table, 211, 211, 211, 1); - const Color lightgrey(color_table, 211, 211, 211, 1); - const Color lightgreen(color_table, 144, 238, 144, 1); - const Color lightpink(color_table, 255, 182, 193, 1); - const Color lightsalmon(color_table, 255, 160, 122, 1); - const Color lightseagreen(color_table, 32, 178, 170, 1); - const Color lightskyblue(color_table, 135, 206, 250, 1); - const Color lightslategray(color_table, 119, 136, 153, 1); - const Color lightslategrey(color_table, 119, 136, 153, 1); - const Color lightsteelblue(color_table, 176, 196, 222, 1); - const Color lightyellow(color_table, 255, 255, 224, 1); - const Color lime(color_table, 0, 255, 0, 1); - const Color limegreen(color_table, 50, 205, 50, 1); - const Color linen(color_table, 250, 240, 230, 1); - const Color maroon(color_table, 128, 0, 0, 1); - const Color mediumaquamarine(color_table, 102, 205, 170, 1); - const Color mediumblue(color_table, 0, 0, 205, 1); - const Color mediumorchid(color_table, 186, 85, 211, 1); - const Color mediumpurple(color_table, 147, 112, 219, 1); - const Color mediumseagreen(color_table, 60, 179, 113, 1); - const Color mediumslateblue(color_table, 123, 104, 238, 1); - const Color mediumspringgreen(color_table, 0, 250, 154, 1); - const Color mediumturquoise(color_table, 72, 209, 204, 1); - const Color mediumvioletred(color_table, 199, 21, 133, 1); - const Color midnightblue(color_table, 25, 25, 112, 1); - const Color mintcream(color_table, 245, 255, 250, 1); - const Color mistyrose(color_table, 255, 228, 225, 1); - const Color moccasin(color_table, 255, 228, 181, 1); - const Color navajowhite(color_table, 255, 222, 173, 1); - const Color navy(color_table, 0, 0, 128, 1); - const Color oldlace(color_table, 253, 245, 230, 1); - const Color olive(color_table, 128, 128, 0, 1); - const Color olivedrab(color_table, 107, 142, 35, 1); - const Color orange(color_table, 255, 165, 0, 1); - const Color orangered(color_table, 255, 69, 0, 1); - const Color orchid(color_table, 218, 112, 214, 1); - const Color palegoldenrod(color_table, 238, 232, 170, 1); - const Color palegreen(color_table, 152, 251, 152, 1); - const Color paleturquoise(color_table, 175, 238, 238, 1); - const Color palevioletred(color_table, 219, 112, 147, 1); - const Color papayawhip(color_table, 255, 239, 213, 1); - const Color peachpuff(color_table, 255, 218, 185, 1); - const Color peru(color_table, 205, 133, 63, 1); - const Color pink(color_table, 255, 192, 203, 1); - const Color plum(color_table, 221, 160, 221, 1); - const Color powderblue(color_table, 176, 224, 230, 1); - const Color purple(color_table, 128, 0, 128, 1); - const Color red(color_table, 255, 0, 0, 1); - const Color rosybrown(color_table, 188, 143, 143, 1); - const Color royalblue(color_table, 65, 105, 225, 1); - const Color saddlebrown(color_table, 139, 69, 19, 1); - const Color salmon(color_table, 250, 128, 114, 1); - const Color sandybrown(color_table, 244, 164, 96, 1); - const Color seagreen(color_table, 46, 139, 87, 1); - const Color seashell(color_table, 255, 245, 238, 1); - const Color sienna(color_table, 160, 82, 45, 1); - const Color silver(color_table, 192, 192, 192, 1); - const Color skyblue(color_table, 135, 206, 235, 1); - const Color slateblue(color_table, 106, 90, 205, 1); - const Color slategray(color_table, 112, 128, 144, 1); - const Color slategrey(color_table, 112, 128, 144, 1); - const Color snow(color_table, 255, 250, 250, 1); - const Color springgreen(color_table, 0, 255, 127, 1); - const Color steelblue(color_table, 70, 130, 180, 1); - const Color tan(color_table, 210, 180, 140, 1); - const Color teal(color_table, 0, 128, 128, 1); - const Color thistle(color_table, 216, 191, 216, 1); - const Color tomato(color_table, 255, 99, 71, 1); - const Color turquoise(color_table, 64, 224, 208, 1); - const Color violet(color_table, 238, 130, 238, 1); - const Color wheat(color_table, 245, 222, 179, 1); - const Color white(color_table, 255, 255, 255, 1); - const Color whitesmoke(color_table, 245, 245, 245, 1); - const Color yellow(color_table, 255, 255, 0, 1); - const Color yellowgreen(color_table, 154, 205, 50, 1); - const Color rebeccapurple(color_table, 102, 51, 153, 1); - const Color transparent(color_table, 0, 0, 0, 0); - } - - const std::map colors_to_names { - { 240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue }, - { 250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite }, - { 0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan }, - { 127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine }, - { 240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure }, - { 245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige }, - { 255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque }, - { 0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black }, - { 255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond }, - { 0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue }, - { 138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet }, - { 165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown }, - { 222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood }, - { 95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue }, - { 127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse }, - { 210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate }, - { 255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral }, - { 100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue }, - { 255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk }, - { 220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson }, - { 0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue }, - { 0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan }, - { 184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod }, - { 169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray }, - { 0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen }, - { 189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki }, - { 139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta }, - { 85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen }, - { 255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange }, - { 153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid }, - { 139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred }, - { 233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon }, - { 143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen }, - { 72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue }, - { 47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray }, - { 0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise }, - { 148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet }, - { 255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink }, - { 0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue }, - { 105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray }, - { 30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue }, - { 178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick }, - { 255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite }, - { 34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen }, - { 255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta }, - { 220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro }, - { 248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite }, - { 255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold }, - { 218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod }, - { 128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray }, - { 0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green }, - { 173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow }, - { 240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew }, - { 255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink }, - { 205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred }, - { 75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo }, - { 255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory }, - { 240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki }, - { 230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender }, - { 255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush }, - { 124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen }, - { 255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon }, - { 173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue }, - { 240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral }, - { 224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan }, - { 250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow }, - { 211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray }, - { 144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen }, - { 255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink }, - { 255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon }, - { 32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen }, - { 135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue }, - { 119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray }, - { 176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue }, - { 255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow }, - { 0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime }, - { 50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen }, - { 250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen }, - { 128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon }, - { 102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine }, - { 0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue }, - { 186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid }, - { 147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple }, - { 60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen }, - { 123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue }, - { 0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen }, - { 72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise }, - { 199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred }, - { 25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue }, - { 245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream }, - { 255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose }, - { 255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin }, - { 255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite }, - { 0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy }, - { 253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace }, - { 128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive }, - { 107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab }, - { 255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange }, - { 255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered }, - { 218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid }, - { 238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod }, - { 152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen }, - { 175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise }, - { 219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred }, - { 255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip }, - { 255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff }, - { 205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru }, - { 255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink }, - { 221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum }, - { 176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue }, - { 128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple }, - { 255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red }, - { 188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown }, - { 65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue }, - { 139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown }, - { 250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon }, - { 244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown }, - { 46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen }, - { 255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell }, - { 160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna }, - { 192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver }, - { 135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue }, - { 106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue }, - { 112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray }, - { 255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow }, - { 0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen }, - { 70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue }, - { 210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan }, - { 0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal }, - { 216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle }, - { 255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato }, - { 64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise }, - { 238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet }, - { 245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat }, - { 255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white }, - { 245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke }, - { 255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow }, - { 154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen }, - { 102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple } - }; - - const std::map names_to_colors - { - { ColorNames::aliceblue, &Colors::aliceblue }, - { ColorNames::antiquewhite, &Colors::antiquewhite }, - { ColorNames::cyan, &Colors::cyan }, - { ColorNames::aqua, &Colors::aqua }, - { ColorNames::aquamarine, &Colors::aquamarine }, - { ColorNames::azure, &Colors::azure }, - { ColorNames::beige, &Colors::beige }, - { ColorNames::bisque, &Colors::bisque }, - { ColorNames::black, &Colors::black }, - { ColorNames::blanchedalmond, &Colors::blanchedalmond }, - { ColorNames::blue, &Colors::blue }, - { ColorNames::blueviolet, &Colors::blueviolet }, - { ColorNames::brown, &Colors::brown }, - { ColorNames::burlywood, &Colors::burlywood }, - { ColorNames::cadetblue, &Colors::cadetblue }, - { ColorNames::chartreuse, &Colors::chartreuse }, - { ColorNames::chocolate, &Colors::chocolate }, - { ColorNames::coral, &Colors::coral }, - { ColorNames::cornflowerblue, &Colors::cornflowerblue }, - { ColorNames::cornsilk, &Colors::cornsilk }, - { ColorNames::crimson, &Colors::crimson }, - { ColorNames::darkblue, &Colors::darkblue }, - { ColorNames::darkcyan, &Colors::darkcyan }, - { ColorNames::darkgoldenrod, &Colors::darkgoldenrod }, - { ColorNames::darkgray, &Colors::darkgray }, - { ColorNames::darkgrey, &Colors::darkgrey }, - { ColorNames::darkgreen, &Colors::darkgreen }, - { ColorNames::darkkhaki, &Colors::darkkhaki }, - { ColorNames::darkmagenta, &Colors::darkmagenta }, - { ColorNames::darkolivegreen, &Colors::darkolivegreen }, - { ColorNames::darkorange, &Colors::darkorange }, - { ColorNames::darkorchid, &Colors::darkorchid }, - { ColorNames::darkred, &Colors::darkred }, - { ColorNames::darksalmon, &Colors::darksalmon }, - { ColorNames::darkseagreen, &Colors::darkseagreen }, - { ColorNames::darkslateblue, &Colors::darkslateblue }, - { ColorNames::darkslategray, &Colors::darkslategray }, - { ColorNames::darkslategrey, &Colors::darkslategrey }, - { ColorNames::darkturquoise, &Colors::darkturquoise }, - { ColorNames::darkviolet, &Colors::darkviolet }, - { ColorNames::deeppink, &Colors::deeppink }, - { ColorNames::deepskyblue, &Colors::deepskyblue }, - { ColorNames::dimgray, &Colors::dimgray }, - { ColorNames::dimgrey, &Colors::dimgrey }, - { ColorNames::dodgerblue, &Colors::dodgerblue }, - { ColorNames::firebrick, &Colors::firebrick }, - { ColorNames::floralwhite, &Colors::floralwhite }, - { ColorNames::forestgreen, &Colors::forestgreen }, - { ColorNames::magenta, &Colors::magenta }, - { ColorNames::fuchsia, &Colors::fuchsia }, - { ColorNames::gainsboro, &Colors::gainsboro }, - { ColorNames::ghostwhite, &Colors::ghostwhite }, - { ColorNames::gold, &Colors::gold }, - { ColorNames::goldenrod, &Colors::goldenrod }, - { ColorNames::gray, &Colors::gray }, - { ColorNames::grey, &Colors::grey }, - { ColorNames::green, &Colors::green }, - { ColorNames::greenyellow, &Colors::greenyellow }, - { ColorNames::honeydew, &Colors::honeydew }, - { ColorNames::hotpink, &Colors::hotpink }, - { ColorNames::indianred, &Colors::indianred }, - { ColorNames::indigo, &Colors::indigo }, - { ColorNames::ivory, &Colors::ivory }, - { ColorNames::khaki, &Colors::khaki }, - { ColorNames::lavender, &Colors::lavender }, - { ColorNames::lavenderblush, &Colors::lavenderblush }, - { ColorNames::lawngreen, &Colors::lawngreen }, - { ColorNames::lemonchiffon, &Colors::lemonchiffon }, - { ColorNames::lightblue, &Colors::lightblue }, - { ColorNames::lightcoral, &Colors::lightcoral }, - { ColorNames::lightcyan, &Colors::lightcyan }, - { ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow }, - { ColorNames::lightgray, &Colors::lightgray }, - { ColorNames::lightgrey, &Colors::lightgrey }, - { ColorNames::lightgreen, &Colors::lightgreen }, - { ColorNames::lightpink, &Colors::lightpink }, - { ColorNames::lightsalmon, &Colors::lightsalmon }, - { ColorNames::lightseagreen, &Colors::lightseagreen }, - { ColorNames::lightskyblue, &Colors::lightskyblue }, - { ColorNames::lightslategray, &Colors::lightslategray }, - { ColorNames::lightslategrey, &Colors::lightslategrey }, - { ColorNames::lightsteelblue, &Colors::lightsteelblue }, - { ColorNames::lightyellow, &Colors::lightyellow }, - { ColorNames::lime, &Colors::lime }, - { ColorNames::limegreen, &Colors::limegreen }, - { ColorNames::linen, &Colors::linen }, - { ColorNames::maroon, &Colors::maroon }, - { ColorNames::mediumaquamarine, &Colors::mediumaquamarine }, - { ColorNames::mediumblue, &Colors::mediumblue }, - { ColorNames::mediumorchid, &Colors::mediumorchid }, - { ColorNames::mediumpurple, &Colors::mediumpurple }, - { ColorNames::mediumseagreen, &Colors::mediumseagreen }, - { ColorNames::mediumslateblue, &Colors::mediumslateblue }, - { ColorNames::mediumspringgreen, &Colors::mediumspringgreen }, - { ColorNames::mediumturquoise, &Colors::mediumturquoise }, - { ColorNames::mediumvioletred, &Colors::mediumvioletred }, - { ColorNames::midnightblue, &Colors::midnightblue }, - { ColorNames::mintcream, &Colors::mintcream }, - { ColorNames::mistyrose, &Colors::mistyrose }, - { ColorNames::moccasin, &Colors::moccasin }, - { ColorNames::navajowhite, &Colors::navajowhite }, - { ColorNames::navy, &Colors::navy }, - { ColorNames::oldlace, &Colors::oldlace }, - { ColorNames::olive, &Colors::olive }, - { ColorNames::olivedrab, &Colors::olivedrab }, - { ColorNames::orange, &Colors::orange }, - { ColorNames::orangered, &Colors::orangered }, - { ColorNames::orchid, &Colors::orchid }, - { ColorNames::palegoldenrod, &Colors::palegoldenrod }, - { ColorNames::palegreen, &Colors::palegreen }, - { ColorNames::paleturquoise, &Colors::paleturquoise }, - { ColorNames::palevioletred, &Colors::palevioletred }, - { ColorNames::papayawhip, &Colors::papayawhip }, - { ColorNames::peachpuff, &Colors::peachpuff }, - { ColorNames::peru, &Colors::peru }, - { ColorNames::pink, &Colors::pink }, - { ColorNames::plum, &Colors::plum }, - { ColorNames::powderblue, &Colors::powderblue }, - { ColorNames::purple, &Colors::purple }, - { ColorNames::red, &Colors::red }, - { ColorNames::rosybrown, &Colors::rosybrown }, - { ColorNames::royalblue, &Colors::royalblue }, - { ColorNames::saddlebrown, &Colors::saddlebrown }, - { ColorNames::salmon, &Colors::salmon }, - { ColorNames::sandybrown, &Colors::sandybrown }, - { ColorNames::seagreen, &Colors::seagreen }, - { ColorNames::seashell, &Colors::seashell }, - { ColorNames::sienna, &Colors::sienna }, - { ColorNames::silver, &Colors::silver }, - { ColorNames::skyblue, &Colors::skyblue }, - { ColorNames::slateblue, &Colors::slateblue }, - { ColorNames::slategray, &Colors::slategray }, - { ColorNames::slategrey, &Colors::slategrey }, - { ColorNames::snow, &Colors::snow }, - { ColorNames::springgreen, &Colors::springgreen }, - { ColorNames::steelblue, &Colors::steelblue }, - { ColorNames::tan, &Colors::tan }, - { ColorNames::teal, &Colors::teal }, - { ColorNames::thistle, &Colors::thistle }, - { ColorNames::tomato, &Colors::tomato }, - { ColorNames::turquoise, &Colors::turquoise }, - { ColorNames::violet, &Colors::violet }, - { ColorNames::wheat, &Colors::wheat }, - { ColorNames::white, &Colors::white }, - { ColorNames::whitesmoke, &Colors::whitesmoke }, - { ColorNames::yellow, &Colors::yellow }, - { ColorNames::yellowgreen, &Colors::yellowgreen }, - { ColorNames::rebeccapurple, &Colors::rebeccapurple }, - { ColorNames::transparent, &Colors::transparent } - }; - - Color_Ptr_Const name_to_color(const char* key) - { - return name_to_color(std::string(key)); - } - - Color_Ptr_Const name_to_color(const std::string& key) - { - // case insensitive lookup. See #2462 - std::string lower{key}; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - - auto p = names_to_colors.find(lower.c_str()); - if (p != names_to_colors.end()) { - return p->second; - } - return 0; - } - - const char* color_to_name(const int key) - { - auto p = colors_to_names.find(key); - if (p != colors_to_names.end()) { - return p->second; - } - return 0; - } - - const char* color_to_name(const double key) - { - return color_to_name((int)key); - } - - const char* color_to_name(const Color& c) - { - double key = c.r() * 0x10000 - + c.g() * 0x100 - + c.b(); - return color_to_name(key); - } - -} diff --git a/src/libsass/src/color_maps.hpp b/src/libsass/src/color_maps.hpp deleted file mode 100644 index d4fd41607..000000000 --- a/src/libsass/src/color_maps.hpp +++ /dev/null @@ -1,331 +0,0 @@ - -#ifndef SASS_COLOR_MAPS_H -#define SASS_COLOR_MAPS_H - -#include -#include "ast.hpp" - -namespace Sass { - - struct map_cmp_str - { - bool operator()(char const *a, char const *b) const - { - return std::strcmp(a, b) < 0; - } - }; - - namespace ColorNames - { - extern const char aliceblue[]; - extern const char antiquewhite[]; - extern const char cyan[]; - extern const char aqua[]; - extern const char aquamarine[]; - extern const char azure[]; - extern const char beige[]; - extern const char bisque[]; - extern const char black[]; - extern const char blanchedalmond[]; - extern const char blue[]; - extern const char blueviolet[]; - extern const char brown[]; - extern const char burlywood[]; - extern const char cadetblue[]; - extern const char chartreuse[]; - extern const char chocolate[]; - extern const char coral[]; - extern const char cornflowerblue[]; - extern const char cornsilk[]; - extern const char crimson[]; - extern const char darkblue[]; - extern const char darkcyan[]; - extern const char darkgoldenrod[]; - extern const char darkgray[]; - extern const char darkgrey[]; - extern const char darkgreen[]; - extern const char darkkhaki[]; - extern const char darkmagenta[]; - extern const char darkolivegreen[]; - extern const char darkorange[]; - extern const char darkorchid[]; - extern const char darkred[]; - extern const char darksalmon[]; - extern const char darkseagreen[]; - extern const char darkslateblue[]; - extern const char darkslategray[]; - extern const char darkslategrey[]; - extern const char darkturquoise[]; - extern const char darkviolet[]; - extern const char deeppink[]; - extern const char deepskyblue[]; - extern const char dimgray[]; - extern const char dimgrey[]; - extern const char dodgerblue[]; - extern const char firebrick[]; - extern const char floralwhite[]; - extern const char forestgreen[]; - extern const char magenta[]; - extern const char fuchsia[]; - extern const char gainsboro[]; - extern const char ghostwhite[]; - extern const char gold[]; - extern const char goldenrod[]; - extern const char gray[]; - extern const char grey[]; - extern const char green[]; - extern const char greenyellow[]; - extern const char honeydew[]; - extern const char hotpink[]; - extern const char indianred[]; - extern const char indigo[]; - extern const char ivory[]; - extern const char khaki[]; - extern const char lavender[]; - extern const char lavenderblush[]; - extern const char lawngreen[]; - extern const char lemonchiffon[]; - extern const char lightblue[]; - extern const char lightcoral[]; - extern const char lightcyan[]; - extern const char lightgoldenrodyellow[]; - extern const char lightgray[]; - extern const char lightgrey[]; - extern const char lightgreen[]; - extern const char lightpink[]; - extern const char lightsalmon[]; - extern const char lightseagreen[]; - extern const char lightskyblue[]; - extern const char lightslategray[]; - extern const char lightslategrey[]; - extern const char lightsteelblue[]; - extern const char lightyellow[]; - extern const char lime[]; - extern const char limegreen[]; - extern const char linen[]; - extern const char maroon[]; - extern const char mediumaquamarine[]; - extern const char mediumblue[]; - extern const char mediumorchid[]; - extern const char mediumpurple[]; - extern const char mediumseagreen[]; - extern const char mediumslateblue[]; - extern const char mediumspringgreen[]; - extern const char mediumturquoise[]; - extern const char mediumvioletred[]; - extern const char midnightblue[]; - extern const char mintcream[]; - extern const char mistyrose[]; - extern const char moccasin[]; - extern const char navajowhite[]; - extern const char navy[]; - extern const char oldlace[]; - extern const char olive[]; - extern const char olivedrab[]; - extern const char orange[]; - extern const char orangered[]; - extern const char orchid[]; - extern const char palegoldenrod[]; - extern const char palegreen[]; - extern const char paleturquoise[]; - extern const char palevioletred[]; - extern const char papayawhip[]; - extern const char peachpuff[]; - extern const char peru[]; - extern const char pink[]; - extern const char plum[]; - extern const char powderblue[]; - extern const char purple[]; - extern const char red[]; - extern const char rosybrown[]; - extern const char royalblue[]; - extern const char saddlebrown[]; - extern const char salmon[]; - extern const char sandybrown[]; - extern const char seagreen[]; - extern const char seashell[]; - extern const char sienna[]; - extern const char silver[]; - extern const char skyblue[]; - extern const char slateblue[]; - extern const char slategray[]; - extern const char slategrey[]; - extern const char snow[]; - extern const char springgreen[]; - extern const char steelblue[]; - extern const char tan[]; - extern const char teal[]; - extern const char thistle[]; - extern const char tomato[]; - extern const char turquoise[]; - extern const char violet[]; - extern const char wheat[]; - extern const char white[]; - extern const char whitesmoke[]; - extern const char yellow[]; - extern const char yellowgreen[]; - extern const char rebeccapurple[]; - extern const char transparent[]; - } - - namespace Colors { - extern const Color aliceblue; - extern const Color antiquewhite; - extern const Color cyan; - extern const Color aqua; - extern const Color aquamarine; - extern const Color azure; - extern const Color beige; - extern const Color bisque; - extern const Color black; - extern const Color blanchedalmond; - extern const Color blue; - extern const Color blueviolet; - extern const Color brown; - extern const Color burlywood; - extern const Color cadetblue; - extern const Color chartreuse; - extern const Color chocolate; - extern const Color coral; - extern const Color cornflowerblue; - extern const Color cornsilk; - extern const Color crimson; - extern const Color darkblue; - extern const Color darkcyan; - extern const Color darkgoldenrod; - extern const Color darkgray; - extern const Color darkgrey; - extern const Color darkgreen; - extern const Color darkkhaki; - extern const Color darkmagenta; - extern const Color darkolivegreen; - extern const Color darkorange; - extern const Color darkorchid; - extern const Color darkred; - extern const Color darksalmon; - extern const Color darkseagreen; - extern const Color darkslateblue; - extern const Color darkslategray; - extern const Color darkslategrey; - extern const Color darkturquoise; - extern const Color darkviolet; - extern const Color deeppink; - extern const Color deepskyblue; - extern const Color dimgray; - extern const Color dimgrey; - extern const Color dodgerblue; - extern const Color firebrick; - extern const Color floralwhite; - extern const Color forestgreen; - extern const Color magenta; - extern const Color fuchsia; - extern const Color gainsboro; - extern const Color ghostwhite; - extern const Color gold; - extern const Color goldenrod; - extern const Color gray; - extern const Color grey; - extern const Color green; - extern const Color greenyellow; - extern const Color honeydew; - extern const Color hotpink; - extern const Color indianred; - extern const Color indigo; - extern const Color ivory; - extern const Color khaki; - extern const Color lavender; - extern const Color lavenderblush; - extern const Color lawngreen; - extern const Color lemonchiffon; - extern const Color lightblue; - extern const Color lightcoral; - extern const Color lightcyan; - extern const Color lightgoldenrodyellow; - extern const Color lightgray; - extern const Color lightgrey; - extern const Color lightgreen; - extern const Color lightpink; - extern const Color lightsalmon; - extern const Color lightseagreen; - extern const Color lightskyblue; - extern const Color lightslategray; - extern const Color lightslategrey; - extern const Color lightsteelblue; - extern const Color lightyellow; - extern const Color lime; - extern const Color limegreen; - extern const Color linen; - extern const Color maroon; - extern const Color mediumaquamarine; - extern const Color mediumblue; - extern const Color mediumorchid; - extern const Color mediumpurple; - extern const Color mediumseagreen; - extern const Color mediumslateblue; - extern const Color mediumspringgreen; - extern const Color mediumturquoise; - extern const Color mediumvioletred; - extern const Color midnightblue; - extern const Color mintcream; - extern const Color mistyrose; - extern const Color moccasin; - extern const Color navajowhite; - extern const Color navy; - extern const Color oldlace; - extern const Color olive; - extern const Color olivedrab; - extern const Color orange; - extern const Color orangered; - extern const Color orchid; - extern const Color palegoldenrod; - extern const Color palegreen; - extern const Color paleturquoise; - extern const Color palevioletred; - extern const Color papayawhip; - extern const Color peachpuff; - extern const Color peru; - extern const Color pink; - extern const Color plum; - extern const Color powderblue; - extern const Color purple; - extern const Color red; - extern const Color rosybrown; - extern const Color royalblue; - extern const Color saddlebrown; - extern const Color salmon; - extern const Color sandybrown; - extern const Color seagreen; - extern const Color seashell; - extern const Color sienna; - extern const Color silver; - extern const Color skyblue; - extern const Color slateblue; - extern const Color slategray; - extern const Color slategrey; - extern const Color snow; - extern const Color springgreen; - extern const Color steelblue; - extern const Color tan; - extern const Color teal; - extern const Color thistle; - extern const Color tomato; - extern const Color turquoise; - extern const Color violet; - extern const Color wheat; - extern const Color white; - extern const Color whitesmoke; - extern const Color yellow; - extern const Color yellowgreen; - extern const Color rebeccapurple; - extern const Color transparent; - } - - Color_Ptr_Const name_to_color(const char*); - Color_Ptr_Const name_to_color(const std::string&); - const char* color_to_name(const int); - const char* color_to_name(const Color&); - const char* color_to_name(const double); - -} - -#endif diff --git a/src/libsass/src/constants.cpp b/src/libsass/src/constants.cpp deleted file mode 100644 index 0ba28e20c..000000000 --- a/src/libsass/src/constants.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "sass.hpp" -#include "constants.hpp" - -namespace Sass { - namespace Constants { - - extern const unsigned long MaxCallStack = 1024; - - // https://github.com/sass/libsass/issues/592 - // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity - // https://github.com/sass/sass/issues/1495#issuecomment-61189114 - extern const unsigned long Specificity_Star = 0; - extern const unsigned long Specificity_Universal = 0; - extern const unsigned long Specificity_Element = 1; - extern const unsigned long Specificity_Base = 1000; - extern const unsigned long Specificity_Class = 1000; - extern const unsigned long Specificity_Attr = 1000; - extern const unsigned long Specificity_Pseudo = 1000; - extern const unsigned long Specificity_ID = 1000000; - - // sass keywords - extern const char at_root_kwd[] = "@at-root"; - extern const char import_kwd[] = "@import"; - extern const char mixin_kwd[] = "@mixin"; - extern const char function_kwd[] = "@function"; - extern const char return_kwd[] = "@return"; - extern const char include_kwd[] = "@include"; - extern const char content_kwd[] = "@content"; - extern const char extend_kwd[] = "@extend"; - extern const char if_kwd[] = "@if"; - extern const char else_kwd[] = "@else"; - extern const char if_after_else_kwd[] = "if"; - extern const char for_kwd[] = "@for"; - extern const char from_kwd[] = "from"; - extern const char to_kwd[] = "to"; - extern const char through_kwd[] = "through"; - extern const char each_kwd[] = "@each"; - extern const char in_kwd[] = "in"; - extern const char while_kwd[] = "@while"; - extern const char warn_kwd[] = "@warn"; - extern const char error_kwd[] = "@error"; - extern const char debug_kwd[] = "@debug"; - extern const char default_kwd[] = "default"; - extern const char global_kwd[] = "global"; - extern const char null_kwd[] = "null"; - extern const char optional_kwd[] = "optional"; - extern const char with_kwd[] = "with"; - extern const char without_kwd[] = "without"; - extern const char all_kwd[] = "all"; - extern const char rule_kwd[] = "rule"; - - // css standard units - extern const char em_kwd[] = "em"; - extern const char ex_kwd[] = "ex"; - extern const char px_kwd[] = "px"; - extern const char cm_kwd[] = "cm"; - extern const char mm_kwd[] = "mm"; - extern const char pt_kwd[] = "pt"; - extern const char pc_kwd[] = "pc"; - extern const char deg_kwd[] = "deg"; - extern const char rad_kwd[] = "rad"; - extern const char grad_kwd[] = "grad"; - extern const char turn_kwd[] = "turn"; - extern const char ms_kwd[] = "ms"; - extern const char s_kwd[] = "s"; - extern const char Hz_kwd[] = "Hz"; - extern const char kHz_kwd[] = "kHz"; - - // vendor prefixes - extern const char vendor_opera_kwd[] = "-o-"; - extern const char vendor_webkit_kwd[] = "-webkit-"; - extern const char vendor_mozilla_kwd[] = "-moz-"; - extern const char vendor_ms_kwd[] = "-ms-"; - extern const char vendor_khtml_kwd[] = "-khtml-"; - - // css functions and keywords - extern const char charset_kwd[] = "@charset"; - extern const char media_kwd[] = "@media"; - extern const char supports_kwd[] = "@supports"; - extern const char keyframes_kwd[] = "keyframes"; - extern const char only_kwd[] = "only"; - extern const char rgb_fn_kwd[] = "rgb("; - extern const char url_fn_kwd[] = "url("; - extern const char url_kwd[] = "url"; - // extern const char url_prefix_fn_kwd[] = "url-prefix("; - extern const char important_kwd[] = "important"; - extern const char pseudo_not_fn_kwd[] = ":not("; - extern const char even_kwd[] = "even"; - extern const char odd_kwd[] = "odd"; - extern const char progid_kwd[] = "progid"; - extern const char expression_kwd[] = "expression"; - extern const char calc_fn_kwd[] = "calc"; - - extern const char almost_any_value_class[] = "\"'#!;{}"; - - // css selector keywords - extern const char sel_deep_kwd[] = "/deep/"; - - // css attribute-matching operators - extern const char tilde_equal[] = "~="; - extern const char pipe_equal[] = "|="; - extern const char caret_equal[] = "^="; - extern const char dollar_equal[] = "$="; - extern const char star_equal[] = "*="; - - // relational & logical operators and constants - extern const char and_kwd[] = "and"; - extern const char or_kwd[] = "or"; - extern const char not_kwd[] = "not"; - extern const char gt[] = ">"; - extern const char gte[] = ">="; - extern const char lt[] = "<"; - extern const char lte[] = "<="; - extern const char eq[] = "=="; - extern const char neq[] = "!="; - extern const char true_kwd[] = "true"; - extern const char false_kwd[] = "false"; - - // miscellaneous punctuation and delimiters - extern const char percent_str[] = "%"; - extern const char empty_str[] = ""; - extern const char slash_slash[] = "//"; - extern const char slash_star[] = "/*"; - extern const char star_slash[] = "*/"; - extern const char hash_lbrace[] = "#{"; - extern const char rbrace[] = "}"; - extern const char rparen[] = ")"; - extern const char sign_chars[] = "-+"; - extern const char op_chars[] = "-+"; - extern const char hyphen[] = "-"; - extern const char ellipsis[] = "..."; - // extern const char url_space_chars[] = " \t\r\n\f"; - // type names - extern const char numeric_name[] = "numeric value"; - extern const char number_name[] = "number"; - extern const char percentage_name[] = "percentage"; - extern const char dimension_name[] = "numeric dimension"; - extern const char string_name[] = "string"; - extern const char bool_name[] = "bool"; - extern const char color_name[] = "color"; - extern const char list_name[] = "list"; - extern const char map_name[] = "map"; - extern const char arglist_name[] = "arglist"; - - // constants for uri parsing (RFC 3986 Appendix A.) - extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; - extern const char real_uri_chars[] = "#%&"; - - // some specific constant character classes - // they must be static to be useable by lexer - extern const char static_ops[] = "*/%"; - // some character classes for the parser - extern const char selector_list_delims[] = "){};!"; - extern const char complex_selector_delims[] = ",){};!"; - extern const char selector_combinator_ops[] = "+~>"; - // optional modifiers for alternative compare context - extern const char attribute_compare_modifiers[] = "~|^$*"; - extern const char selector_lookahead_ops[] = "*&%,()[]"; - - // byte order marks - // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) - extern const unsigned char utf_8_bom[] = { 0xEF, 0xBB, 0xBF }; - extern const unsigned char utf_16_bom_be[] = { 0xFE, 0xFF }; - extern const unsigned char utf_16_bom_le[] = { 0xFF, 0xFE }; - extern const unsigned char utf_32_bom_be[] = { 0x00, 0x00, 0xFE, 0xFF }; - extern const unsigned char utf_32_bom_le[] = { 0xFF, 0xFE, 0x00, 0x00 }; - extern const unsigned char utf_7_bom_1[] = { 0x2B, 0x2F, 0x76, 0x38 }; - extern const unsigned char utf_7_bom_2[] = { 0x2B, 0x2F, 0x76, 0x39 }; - extern const unsigned char utf_7_bom_3[] = { 0x2B, 0x2F, 0x76, 0x2B }; - extern const unsigned char utf_7_bom_4[] = { 0x2B, 0x2F, 0x76, 0x2F }; - extern const unsigned char utf_7_bom_5[] = { 0x2B, 0x2F, 0x76, 0x38, 0x2D }; - extern const unsigned char utf_1_bom[] = { 0xF7, 0x64, 0x4C }; - extern const unsigned char utf_ebcdic_bom[] = { 0xDD, 0x73, 0x66, 0x73 }; - extern const unsigned char scsu_bom[] = { 0x0E, 0xFE, 0xFF }; - extern const unsigned char bocu_1_bom[] = { 0xFB, 0xEE, 0x28 }; - extern const unsigned char gb_18030_bom[] = { 0x84, 0x31, 0x95, 0x33 }; - - } -} diff --git a/src/libsass/src/constants.hpp b/src/libsass/src/constants.hpp deleted file mode 100644 index 4fe93571e..000000000 --- a/src/libsass/src/constants.hpp +++ /dev/null @@ -1,181 +0,0 @@ -#ifndef SASS_CONSTANTS_H -#define SASS_CONSTANTS_H - -namespace Sass { - namespace Constants { - - // The maximum call stack that can be created - extern const unsigned long MaxCallStack; - - // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity - // The following list of selectors is by increasing specificity: - extern const unsigned long Specificity_Star; - extern const unsigned long Specificity_Universal; - extern const unsigned long Specificity_Element; - extern const unsigned long Specificity_Base; - extern const unsigned long Specificity_Class; - extern const unsigned long Specificity_Attr; - extern const unsigned long Specificity_Pseudo; - extern const unsigned long Specificity_ID; - - // sass keywords - extern const char at_root_kwd[]; - extern const char import_kwd[]; - extern const char mixin_kwd[]; - extern const char function_kwd[]; - extern const char return_kwd[]; - extern const char include_kwd[]; - extern const char content_kwd[]; - extern const char extend_kwd[]; - extern const char if_kwd[]; - extern const char else_kwd[]; - extern const char if_after_else_kwd[]; - extern const char for_kwd[]; - extern const char from_kwd[]; - extern const char to_kwd[]; - extern const char through_kwd[]; - extern const char each_kwd[]; - extern const char in_kwd[]; - extern const char while_kwd[]; - extern const char warn_kwd[]; - extern const char error_kwd[]; - extern const char debug_kwd[]; - extern const char default_kwd[]; - extern const char global_kwd[]; - extern const char null_kwd[]; - extern const char optional_kwd[]; - extern const char with_kwd[]; - extern const char without_kwd[]; - extern const char all_kwd[]; - extern const char rule_kwd[]; - - // css standard units - extern const char em_kwd[]; - extern const char ex_kwd[]; - extern const char px_kwd[]; - extern const char cm_kwd[]; - extern const char mm_kwd[]; - extern const char pt_kwd[]; - extern const char pc_kwd[]; - extern const char deg_kwd[]; - extern const char rad_kwd[]; - extern const char grad_kwd[]; - extern const char turn_kwd[]; - extern const char ms_kwd[]; - extern const char s_kwd[]; - extern const char Hz_kwd[]; - extern const char kHz_kwd[]; - - // vendor prefixes - extern const char vendor_opera_kwd[]; - extern const char vendor_webkit_kwd[]; - extern const char vendor_mozilla_kwd[]; - extern const char vendor_ms_kwd[]; - extern const char vendor_khtml_kwd[]; - - // css functions and keywords - extern const char charset_kwd[]; - extern const char media_kwd[]; - extern const char supports_kwd[]; - extern const char keyframes_kwd[]; - extern const char only_kwd[]; - extern const char rgb_fn_kwd[]; - extern const char url_fn_kwd[]; - extern const char url_kwd[]; - // extern const char url_prefix_fn_kwd[]; - extern const char important_kwd[]; - extern const char pseudo_not_fn_kwd[]; - extern const char even_kwd[]; - extern const char odd_kwd[]; - extern const char progid_kwd[]; - extern const char expression_kwd[]; - extern const char calc_fn_kwd[]; - - // char classes for "regular expressions" - extern const char almost_any_value_class[]; - - // css selector keywords - extern const char sel_deep_kwd[]; - - // css attribute-matching operators - extern const char tilde_equal[]; - extern const char pipe_equal[]; - extern const char caret_equal[]; - extern const char dollar_equal[]; - extern const char star_equal[]; - - // relational & logical operators and constants - extern const char and_kwd[]; - extern const char or_kwd[]; - extern const char not_kwd[]; - extern const char gt[]; - extern const char gte[]; - extern const char lt[]; - extern const char lte[]; - extern const char eq[]; - extern const char neq[]; - extern const char true_kwd[]; - extern const char false_kwd[]; - - // miscellaneous punctuation and delimiters - extern const char percent_str[]; - extern const char empty_str[]; - extern const char slash_slash[]; - extern const char slash_star[]; - extern const char star_slash[]; - extern const char hash_lbrace[]; - extern const char rbrace[]; - extern const char rparen[]; - extern const char sign_chars[]; - extern const char op_chars[]; - extern const char hyphen[]; - extern const char ellipsis[]; - // extern const char url_space_chars[]; - - // type names - extern const char numeric_name[]; - extern const char number_name[]; - extern const char percentage_name[]; - extern const char dimension_name[]; - extern const char string_name[]; - extern const char bool_name[]; - extern const char color_name[]; - extern const char list_name[]; - extern const char map_name[]; - extern const char arglist_name[]; - - // constants for uri parsing (RFC 3986 Appendix A.) - extern const char uri_chars[]; - extern const char real_uri_chars[]; - - // some specific constant character classes - // they must be static to be useable by lexer - extern const char static_ops[]; - extern const char selector_list_delims[]; - extern const char complex_selector_delims[]; - extern const char selector_combinator_ops[]; - extern const char attribute_compare_modifiers[]; - extern const char selector_lookahead_ops[]; - - // byte order marks - // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) - extern const unsigned char utf_8_bom[]; - extern const unsigned char utf_16_bom_be[]; - extern const unsigned char utf_16_bom_le[]; - extern const unsigned char utf_32_bom_be[]; - extern const unsigned char utf_32_bom_le[]; - extern const unsigned char utf_7_bom_1[]; - extern const unsigned char utf_7_bom_2[]; - extern const unsigned char utf_7_bom_3[]; - extern const unsigned char utf_7_bom_4[]; - extern const unsigned char utf_7_bom_5[]; - extern const unsigned char utf_1_bom[]; - extern const unsigned char utf_ebcdic_bom[]; - extern const unsigned char scsu_bom[]; - extern const unsigned char bocu_1_bom[]; - extern const unsigned char gb_18030_bom[]; - - } -} - -#endif diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp deleted file mode 100644 index b199412cd..000000000 --- a/src/libsass/src/context.cpp +++ /dev/null @@ -1,926 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include -#include - -#include "ast.hpp" -#include "util.hpp" -#include "sass.h" -#include "context.hpp" -#include "plugins.hpp" -#include "constants.hpp" -#include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "output.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include "check_nesting.hpp" -#include "cssize.hpp" -#include "listize.hpp" -#include "extend.hpp" -#include "remove_placeholders.hpp" -#include "functions.hpp" -#include "sass_functions.hpp" -#include "backtrace.hpp" -#include "sass2scss.h" -#include "prelexer.hpp" -#include "emitter.hpp" - -namespace Sass { - using namespace Constants; - using namespace File; - using namespace Sass; - - inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) - { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } - - static std::string safe_input(const char* in_path) - { - // enforce some safe defaults - // used to create relative file links - std::string safe_path(in_path ? in_path : ""); - return safe_path == "" ? "stdin" : safe_path; - } - - static std::string safe_output(const char* out_path, const std::string& input_path = "") - { - std::string safe_path(out_path ? out_path : ""); - // maybe we can extract an output path from input path - if (safe_path == "" && input_path != "") { - int lastindex = static_cast(input_path.find_last_of(".")); - return (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css"; - } - // enforce some safe defaults - // used to create relative file links - return safe_path == "" ? "stdout" : safe_path; - } - - Context::Context(struct Sass_Context& c_ctx) - : CWD(File::get_cwd()), - c_options(c_ctx), - entry_path(""), - head_imports(0), - plugins(), - emitter(c_options), - - ast_gc(), - strings(), - resources(), - sheets(), - subset_map(), - import_stack(), - callee_stack(), - traces(), - c_compiler(NULL), - - c_headers (std::vector()), - c_importers (std::vector()), - c_functions (std::vector()), - - indent (safe_str(c_options.indent, " ")), - linefeed (safe_str(c_options.linefeed, "\n")), - - input_path (make_canonical_path(safe_input(c_options.input_path))), - output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), - source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), - source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) - - { - - // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. - // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. - // include_paths.push_back(CWD); - - // collect more paths from different options - collect_extensions(c_options.extension); - collect_extensions(c_options.extensions); - collect_include_paths(c_options.include_path); - collect_include_paths(c_options.include_paths); - collect_plugin_paths(c_options.plugin_path); - collect_plugin_paths(c_options.plugin_paths); - - // load plugins and register custom behaviors - for(auto plug : plugin_paths) plugins.load_plugins(plug); - for(auto fn : plugins.get_headers()) c_headers.push_back(fn); - for(auto fn : plugins.get_importers()) c_importers.push_back(fn); - for(auto fn : plugins.get_functions()) c_functions.push_back(fn); - - // sort the items by priority (lowest first) - sort (c_headers.begin(), c_headers.end(), sort_importers); - sort (c_importers.begin(), c_importers.end(), sort_importers); - - emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); - - } - - void Context::add_c_function(Sass_Function_Entry function) - { - c_functions.push_back(function); - } - void Context::add_c_header(Sass_Importer_Entry header) - { - c_headers.push_back(header); - // need to sort the array afterwards (no big deal) - sort (c_headers.begin(), c_headers.end(), sort_importers); - } - void Context::add_c_importer(Sass_Importer_Entry importer) - { - c_importers.push_back(importer); - // need to sort the array afterwards (no big deal) - sort (c_importers.begin(), c_importers.end(), sort_importers); - } - - Context::~Context() - { - // resources were allocated by malloc - for (size_t i = 0; i < resources.size(); ++i) { - free(resources[i].contents); - free(resources[i].srcmap); - } - // free all strings we kept alive during compiler execution - for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); - // everything that gets put into sources will be freed by us - // this shouldn't have anything in it anyway!? - for (size_t m = 0; m < import_stack.size(); ++m) { - sass_import_take_source(import_stack[m]); - sass_import_take_srcmap(import_stack[m]); - sass_delete_import(import_stack[m]); - } - // clear inner structures (vectors) and input source - resources.clear(); import_stack.clear(); - subset_map.clear(), sheets.clear(); - } - - Data_Context::~Data_Context() - { - // --> this will be freed by resources - // make sure we free the source even if not processed! - // if (resources.size() == 0 && source_c_str) free(source_c_str); - // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); - // source_c_str = 0; srcmap_c_str = 0; - } - - File_Context::~File_Context() - { - } - - void Context::collect_extensions(const char* exts_str) - { - if (exts_str) { - const char* beg = exts_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - std::string ext(beg, end - beg); - if (!ext.empty()) { - extensions.push_back(ext); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - std::string ext(beg); - if (!ext.empty()) { - extensions.push_back(ext); - } - } - } - - void Context::collect_extensions(string_list* paths_array) - { - while (paths_array) - { - collect_extensions(paths_array->string); - paths_array = paths_array->next; - } - } - - void Context::collect_include_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - std::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - std::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - } - } - - void Context::collect_include_paths(string_list* paths_array) - { - while (paths_array) - { - collect_include_paths(paths_array->string); - paths_array = paths_array->next; - } - } - - void Context::collect_plugin_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - std::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - std::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - } - } - - void Context::collect_plugin_paths(string_list* paths_array) - { - while (paths_array) - { - collect_plugin_paths(paths_array->string); - paths_array = paths_array->next; - } - } - - // resolve the imp_path in base_path or include_paths - // looks for alternatives and returns a list from one directory - std::vector Context::find_includes(const Importer& import) - { - // include configured extensions - std::vector exts(File::defaultExtensions); - if (extensions.size() > 0) { - exts.insert(exts.end(), extensions.begin(), extensions.end()); - } - // make sure we resolve against an absolute path - std::string base_path(rel2abs(import.base_path)); - // first try to resolve the load path relative to the base path - std::vector vec(resolve_includes(base_path, import.imp_path, exts)); - // then search in every include path (but only if nothing found yet) - for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) - { - // call resolve_includes and individual base path and append all results - std::vector resolved(resolve_includes(include_paths[i], import.imp_path, exts)); - if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); - } - // return vector - return vec; - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res) - { - - // do not parse same resource twice - // maybe raise an error in this case - // if (sheets.count(inc.abs_path)) { - // free(res.contents); free(res.srcmap); - // throw std::runtime_error("duplicate resource registered"); - // return; - // } - - // get index for this resource - size_t idx = resources.size(); - - // tell emitter about new resource - emitter.add_source_index(idx); - - // put resources under our control - // the memory will be freed later - resources.push_back(res); - - // add a relative link to the working directory - included_files.push_back(inc.abs_path); - // add a relative link to the source map output file - srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); - - // get pointer to the loaded content - Sass_Import_Entry import = sass_make_import( - inc.imp_path.c_str(), - inc.abs_path.c_str(), - res.contents, - res.srcmap - ); - // add the entry to the stack - import_stack.push_back(import); - - // get pointer to the loaded content - const char* contents = resources[idx].contents; - // keep a copy of the path around (for parserstates) - // ToDo: we clean it, but still not very elegant!? - strings.push_back(sass_copy_c_string(inc.abs_path.c_str())); - // create the initial parser state from resource - ParserState pstate(strings.back(), contents, idx); - - // check existing import stack for possible recursion - for (size_t i = 0; i < import_stack.size() - 2; ++i) { - auto parent = import_stack[i]; - if (std::strcmp(parent->abs_path, import->abs_path) == 0) { - std::string cwd(File::get_cwd()); - // make path relative to the current directory - std::string stack("An @import loop has been found:"); - for (size_t n = 1; n < i + 2; ++n) { - stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + - " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); - } - // implement error throw directly until we - // decided how to handle full stack traces - throw Exception::InvalidSyntax(pstate, traces, stack); - // error(stack, prstate ? *prstate : pstate, import_stack); - } - } - - // create a parser instance from the given c_str buffer - Parser p(Parser::from_c_str(contents, *this, traces, pstate)); - // do not yet dispose these buffers - sass_import_take_source(import); - sass_import_take_srcmap(import); - // then parse the root block - Block_Obj root = p.parse(); - // delete memory of current stack frame - sass_delete_import(import_stack.back()); - // remove current stack frame - import_stack.pop_back(); - // create key/value pair for ast node - std::pair - ast_pair(inc.abs_path, { res, root }); - // register resulting resource - sheets.insert(ast_pair); - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) - { - traces.push_back(Backtrace(prstate)); - register_resource(inc, res); - traces.pop_back(); - } - - // Add a new import to the context (called from `import_url`) - Include Context::load_import(const Importer& imp, ParserState pstate) - { - - // search for valid imports (ie. partials) on the filesystem - // this may return more than one valid result (ambiguous imp_path) - const std::vector resolved(find_includes(imp)); - - // error nicely on ambiguous imp_path - if (resolved.size() > 1) { - std::stringstream msg_stream; - msg_stream << "It's not clear which file to import for "; - msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; - msg_stream << "Candidates:" << "\n"; - for (size_t i = 0, L = resolved.size(); i < L; ++i) - { msg_stream << " " << resolved[i].imp_path << "\n"; } - msg_stream << "Please delete or rename all but one of these files." << "\n"; - error(msg_stream.str(), pstate, traces); - } - - // process the resolved entry - else if (resolved.size() == 1) { - bool use_cache = c_importers.size() == 0; - if (resolved[0].deprecated) { - // emit deprecation warning when import resolves to a .css file - deprecated( - "Including .css files with @import is non-standard behaviour which will be removed in future versions of LibSass.", - "Use a custom importer to maintain this behaviour. Check your implementations documentation on how to create a custom importer.", - true, pstate - ); - } - // use cache for the resource loading - if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; - // try to read the content of the resolved file entry - // the memory buffer returned must be freed by us! - if (char* contents = read_file(resolved[0].abs_path)) { - // register the newly resolved file resource - register_resource(resolved[0], { contents, 0 }, pstate); - // return resolved entry - return resolved[0]; - } - } - - // nothing found - return { imp, "" }; - - } - - void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { - - ParserState pstate(imp->pstate()); - std::string imp_path(unquote(load_path)); - std::string protocol("file"); - - using namespace Prelexer; - if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { - - protocol = std::string(imp_path.c_str(), proto - 3); - // if (protocol.compare("file") && true) { } - } - - // add urls (protocol other than file) and urls without procotol to `urls` member - // ToDo: if ctx_path is already a file resource, we should not add it here? - if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { - imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); - } - else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { - String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); - Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); - Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); - loc_args->append(loc_arg); - Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); - imp->urls().push_back(new_url); - } - else { - const Importer importer(imp_path, ctx_path); - Include include(load_import(importer, pstate)); - if (include.abs_path.empty()) { - error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); - } - imp->incs().push_back(include); - } - - } - - - // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet - bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) - { - // unique counter - size_t count = 0; - // need one correct import - bool has_import = false; - // process all custom importers (or custom headers) - for (Sass_Importer_Entry& importer_ent : importers) { - // int priority = sass_importer_get_priority(importer); - Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); - // skip importer if it returns NULL - if (Sass_Import_List includes = - fn(load_path.c_str(), importer_ent, c_compiler) - ) { - // get c pointer copy to iterate over - Sass_Import_List it_includes = includes; - while (*it_includes) { ++count; - // create unique path to use as key - std::string uniq_path = load_path; - if (!only_one && count) { - std::stringstream path_strm; - path_strm << uniq_path << ":" << count; - uniq_path = path_strm.str(); - } - // create the importer struct - Importer importer(uniq_path, ctx_path); - // query data from the current include - Sass_Import_Entry include_ent = *it_includes; - char* source = sass_import_take_source(include_ent); - char* srcmap = sass_import_take_srcmap(include_ent); - size_t line = sass_import_get_error_line(include_ent); - size_t column = sass_import_get_error_column(include_ent); - const char *abs_path = sass_import_get_abs_path(include_ent); - // handle error message passed back from custom importer - // it may (or may not) override the line and column info - if (const char* err_message = sass_import_get_error_message(include_ent)) { - if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); - if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); - else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); - } - // content for import was set - else if (source) { - // resolved abs_path should be set by custom importer - // use the created uniq_path as fallback (maybe enforce) - std::string path_key(abs_path ? abs_path : uniq_path); - // create the importer struct - Include include(importer, path_key); - // attach information to AST node - imp->incs().push_back(include); - // register the resource buffers - register_resource(include, { source, srcmap }, pstate); - } - // only a path was retuned - // try to load it like normal - else if(abs_path) { - // checks some urls to preserve - // `http://`, `https://` and `//` - // or dispatchs to `import_file` - // which will check for a `.css` extension - // or resolves the file on the filesystem - // added and resolved via `add_file` - // finally stores everything on `imp` - import_url(imp, abs_path, ctx_path); - } - // move to next - ++it_includes; - } - // deallocate the returned memory - sass_delete_import_list(includes); - // set success flag - has_import = true; - // break out of loop - if (only_one) break; - } - } - // return result - return has_import; - } - - void register_function(Context&, Signature sig, Native_Function f, Env* env); - void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); - void register_overload_stub(Context&, std::string name, Env* env); - void register_built_in_functions(Context&, Env* env); - void register_c_functions(Context&, Env* env, Sass_Function_List); - void register_c_function(Context&, Env* env, Sass_Function_Entry); - - char* Context::render(Block_Obj root) - { - // check for valid block - if (!root) return 0; - // start the render process - root->perform(&emitter); - // finish emitter stream - emitter.finalize(); - // get the resulting buffer from stream - OutputBuffer emitted = emitter.get_buffer(); - // should we append a source map url? - if (!c_options.omit_source_map_url) { - // generate an embeded source map - if (c_options.source_map_embed) { - emitted.buffer += linefeed; - emitted.buffer += format_embedded_source_map(); - } - // or just link the generated one - else if (source_map_file != "") { - emitted.buffer += linefeed; - emitted.buffer += format_source_mapping_url(source_map_file); - } - } - // create a copy of the resulting buffer string - // this must be freed or taken over by implementor - return sass_copy_c_string(emitted.buffer.c_str()); - } - - void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, ParserState pstate) - { - // create a custom import to resolve headers - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - // dispatch headers which will add custom functions - // custom headers are added to the import instance - call_headers(entry_path, ctx_path, pstate, imp); - // increase head count to skip later - head_imports += resources.size() - 1; - // add the statement if we have urls - if (!imp->urls().empty()) root->append(imp); - // process all other resources (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); - } - } - - Block_Obj File_Context::parse() - { - - // check if entry file is given - if (input_path.empty()) return 0; - - // create absolute path from input filename - // ToDo: this should be resolved via custom importers - std::string abs_path(rel2abs(input_path, CWD)); - - // try to load the entry file - char* contents = read_file(abs_path); - - // alternatively also look inside each include path folder - // I think this differs from ruby sass (IMO too late to remove) - for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { - // build absolute path for this include path entry - abs_path = rel2abs(input_path, include_paths[i]); - // try to load the resulting path - contents = read_file(abs_path); - } - - // abort early if no content could be loaded (various reasons) - if (!contents) throw std::runtime_error("File to read not found or unreadable: " + input_path); - - // store entry path - entry_path = abs_path; - - // create entry only for import stack - Sass_Import_Entry import = sass_make_import( - input_path.c_str(), - entry_path.c_str(), - contents, - 0 - ); - // add the entry to the stack - import_stack.push_back(import); - - // create the source entry for file entry - register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); - - // create root ast tree node - return compile(); - - } - - Block_Obj Data_Context::parse() - { - - // check if source string is given - if (!source_c_str) return 0; - - // convert indented sass syntax - if(c_options.is_indented_syntax_src) { - // call sass2scss to convert the string - char * converted = sass2scss(source_c_str, - // preserve the structure as much as possible - SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); - // replace old source_c_str with converted - free(source_c_str); source_c_str = converted; - } - - // remember entry path (defaults to stdin for string) - entry_path = input_path.empty() ? "stdin" : input_path; - - // ToDo: this may be resolved via custom importers - std::string abs_path(rel2abs(entry_path)); - char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); - strings.push_back(abs_path_c_str); - - // create entry only for the import stack - Sass_Import_Entry import = sass_make_import( - entry_path.c_str(), - abs_path_c_str, - source_c_str, - srcmap_c_str - ); - // add the entry to the stack - import_stack.push_back(import); - - // register a synthetic resource (path does not really exist, skip in includes) - register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); - - // create root ast tree node - return compile(); - } - - - - // parse root block from includes - Block_Obj Context::compile() - { - // abort if there is no data - if (resources.size() == 0) return 0; - // get root block from the first style sheet - Block_Obj root = sheets.at(entry_path).root; - // abort on invalid root - if (root.isNull()) return 0; - Env global; // create root environment - // register built-in functions on env - register_built_in_functions(*this, &global); - // register custom functions (defined via C-API) - for (size_t i = 0, S = c_functions.size(); i < S; ++i) - { register_c_function(*this, &global, c_functions[i]); } - // create initial backtrace entry - // create crtp visitor objects - Expand expand(*this, &global); - Cssize cssize(*this); - CheckNesting check_nesting; - // check nesting in all files - for (auto sheet : sheets) { - auto styles = sheet.second; - check_nesting(styles.root); - } - // expand and eval the tree - root = expand(root); - // check nesting - check_nesting(root); - // merge and bubble certain rules - root = cssize(root); - // should we extend something? - if (!subset_map.empty()) { - // create crtp visitor object - Extend extend(subset_map); - extend.setEval(expand.eval); - // extend tree nodes - extend(root); - } - - // clean up by removing empty placeholders - // ToDo: maybe we can do this somewhere else? - Remove_Placeholders remove_placeholders; - root->perform(&remove_placeholders); - // return processed tree - return root; - } - // EO compile - - std::string Context::format_embedded_source_map() - { - std::string map = emitter.render_srcmap(*this); - std::istringstream is( map ); - std::ostringstream buffer; - base64::encoder E; - E.encode(is, buffer); - std::string url = "data:application/json;base64," + buffer.str(); - url.erase(url.size() - 1); - return "/*# sourceMappingURL=" + url + " */"; - } - - std::string Context::format_source_mapping_url(const std::string& file) - { - std::string url = abs2rel(file, output_path, CWD); - return "/*# sourceMappingURL=" + url + " */"; - } - - char* Context::render_srcmap() - { - if (source_map_file == "") return 0; - std::string map = emitter.render_srcmap(*this); - return sass_copy_c_string(map.c_str()); - } - - - // for data context we want to start after "stdin" - // we probably always want to skip the header includes? - std::vector Context::get_included_files(bool skip, size_t headers) - { - // create a copy of the vector for manipulations - std::vector includes = included_files; - if (includes.size() == 0) return includes; - if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } - else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } - includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); - std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); - return includes; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) - { - Definition_Ptr def = make_native_function(sig, f, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) - { - Definition_Ptr def = make_native_function(sig, f, ctx); - std::stringstream ss; - ss << def->name() << "[f]" << arity; - def->environment(env); - (*env)[ss.str()] = def; - } - - void register_overload_stub(Context& ctx, std::string name, Env* env) - { - Definition_Ptr stub = SASS_MEMORY_NEW(Definition, - ParserState("[built-in function]"), - 0, - name, - 0, - 0, - true); - (*env)[name + "[f]"] = stub; - } - - - void register_built_in_functions(Context& ctx, Env* env) - { - using namespace Functions; - // RGB Functions - register_function(ctx, rgb_sig, rgb, env); - register_overload_stub(ctx, "rgba", env); - register_function(ctx, rgba_4_sig, rgba_4, 4, env); - register_function(ctx, rgba_2_sig, rgba_2, 2, env); - register_function(ctx, red_sig, red, env); - register_function(ctx, green_sig, green, env); - register_function(ctx, blue_sig, blue, env); - register_function(ctx, mix_sig, mix, env); - // HSL Functions - register_function(ctx, hsl_sig, hsl, env); - register_function(ctx, hsla_sig, hsla, env); - register_function(ctx, hue_sig, hue, env); - register_function(ctx, saturation_sig, saturation, env); - register_function(ctx, lightness_sig, lightness, env); - register_function(ctx, adjust_hue_sig, adjust_hue, env); - register_function(ctx, lighten_sig, lighten, env); - register_function(ctx, darken_sig, darken, env); - register_function(ctx, saturate_sig, saturate, env); - register_function(ctx, desaturate_sig, desaturate, env); - register_function(ctx, grayscale_sig, grayscale, env); - register_function(ctx, complement_sig, complement, env); - register_function(ctx, invert_sig, invert, env); - // Opacity Functions - register_function(ctx, alpha_sig, alpha, env); - register_function(ctx, opacity_sig, alpha, env); - register_function(ctx, opacify_sig, opacify, env); - register_function(ctx, fade_in_sig, opacify, env); - register_function(ctx, transparentize_sig, transparentize, env); - register_function(ctx, fade_out_sig, transparentize, env); - // Other Color Functions - register_function(ctx, adjust_color_sig, adjust_color, env); - register_function(ctx, scale_color_sig, scale_color, env); - register_function(ctx, change_color_sig, change_color, env); - register_function(ctx, ie_hex_str_sig, ie_hex_str, env); - // String Functions - register_function(ctx, unquote_sig, sass_unquote, env); - register_function(ctx, quote_sig, sass_quote, env); - register_function(ctx, str_length_sig, str_length, env); - register_function(ctx, str_insert_sig, str_insert, env); - register_function(ctx, str_index_sig, str_index, env); - register_function(ctx, str_slice_sig, str_slice, env); - register_function(ctx, to_upper_case_sig, to_upper_case, env); - register_function(ctx, to_lower_case_sig, to_lower_case, env); - // Number Functions - register_function(ctx, percentage_sig, percentage, env); - register_function(ctx, round_sig, round, env); - register_function(ctx, ceil_sig, ceil, env); - register_function(ctx, floor_sig, floor, env); - register_function(ctx, abs_sig, abs, env); - register_function(ctx, min_sig, min, env); - register_function(ctx, max_sig, max, env); - register_function(ctx, random_sig, random, env); - // List Functions - register_function(ctx, length_sig, length, env); - register_function(ctx, nth_sig, nth, env); - register_function(ctx, set_nth_sig, set_nth, env); - register_function(ctx, index_sig, index, env); - register_function(ctx, join_sig, join, env); - register_function(ctx, append_sig, append, env); - register_function(ctx, zip_sig, zip, env); - register_function(ctx, list_separator_sig, list_separator, env); - register_function(ctx, is_bracketed_sig, is_bracketed, env); - // Map Functions - register_function(ctx, map_get_sig, map_get, env); - register_function(ctx, map_merge_sig, map_merge, env); - register_function(ctx, map_remove_sig, map_remove, env); - register_function(ctx, map_keys_sig, map_keys, env); - register_function(ctx, map_values_sig, map_values, env); - register_function(ctx, map_has_key_sig, map_has_key, env); - register_function(ctx, keywords_sig, keywords, env); - // Introspection Functions - register_function(ctx, type_of_sig, type_of, env); - register_function(ctx, unit_sig, unit, env); - register_function(ctx, unitless_sig, unitless, env); - register_function(ctx, comparable_sig, comparable, env); - register_function(ctx, variable_exists_sig, variable_exists, env); - register_function(ctx, global_variable_exists_sig, global_variable_exists, env); - register_function(ctx, function_exists_sig, function_exists, env); - register_function(ctx, mixin_exists_sig, mixin_exists, env); - register_function(ctx, feature_exists_sig, feature_exists, env); - register_function(ctx, call_sig, call, env); - register_function(ctx, content_exists_sig, content_exists, env); - register_function(ctx, get_function_sig, get_function, env); - // Boolean Functions - register_function(ctx, not_sig, sass_not, env); - register_function(ctx, if_sig, sass_if, env); - // Misc Functions - register_function(ctx, inspect_sig, inspect, env); - register_function(ctx, unique_id_sig, unique_id, env); - // Selector functions - register_function(ctx, selector_nest_sig, selector_nest, env); - register_function(ctx, selector_append_sig, selector_append, env); - register_function(ctx, selector_extend_sig, selector_extend, env); - register_function(ctx, selector_replace_sig, selector_replace, env); - register_function(ctx, selector_unify_sig, selector_unify, env); - register_function(ctx, is_superselector_sig, is_superselector, env); - register_function(ctx, simple_selectors_sig, simple_selectors, env); - register_function(ctx, selector_parse_sig, selector_parse, env); - } - - void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) - { - while (descrs && *descrs) { - register_c_function(ctx, env, *descrs); - ++descrs; - } - } - void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) - { - Definition_Ptr def = make_c_function(descr, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - -} diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp deleted file mode 100644 index f14e69f6d..000000000 --- a/src/libsass/src/context.hpp +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef SASS_CONTEXT_H -#define SASS_CONTEXT_H - -#include -#include -#include - -#define BUFFERSIZE 255 -#include "b64/encode.h" - -#include "ast_fwd_decl.hpp" -#include "kwd_arg_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "sass_context.hpp" -#include "environment.hpp" -#include "source_map.hpp" -#include "subset_map.hpp" -#include "backtrace.hpp" -#include "output.hpp" -#include "plugins.hpp" -#include "file.hpp" - - -struct Sass_Function; - -namespace Sass { - - class Context { - public: - void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); - bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; - bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; - - private: - bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); - - public: - const std::string CWD; - struct Sass_Options& c_options; - std::string entry_path; - size_t head_imports; - Plugins plugins; - Output emitter; - - // generic ast node garbage container - // used to avoid possible circular refs - std::vector ast_gc; - // resources add under our control - // these are guaranteed to be freed - std::vector strings; - std::vector resources; - std::map sheets; - Subset_Map subset_map; - std::vector import_stack; - std::vector callee_stack; - std::vector traces; - - struct Sass_Compiler* c_compiler; - - // absolute paths to includes - std::vector included_files; - // relative includes for sourcemap - std::vector srcmap_links; - // vectors above have same size - - std::vector plugin_paths; // relative paths to load plugins - std::vector include_paths; // lookup paths for includes - std::vector extensions; // lookup extensions for imports` - - - - - - void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); - - std::vector c_headers; - std::vector c_importers; - std::vector c_functions; - - void add_c_header(Sass_Importer_Entry header); - void add_c_importer(Sass_Importer_Entry importer); - void add_c_function(Sass_Function_Entry function); - - const std::string indent; // String to be used for indentation - const std::string linefeed; // String to be used for line feeds - const std::string input_path; // for relative paths in src-map - const std::string output_path; // for relative paths to the output - const std::string source_map_file; // path to source map file (enables feature) - const std::string source_map_root; // path for sourceRoot property (pass-through) - - virtual ~Context(); - Context(struct Sass_Context&); - virtual Block_Obj parse() = 0; - virtual Block_Obj compile(); - virtual char* render(Block_Obj root); - virtual char* render_srcmap(); - - void register_resource(const Include&, const Resource&); - void register_resource(const Include&, const Resource&, ParserState&); - std::vector find_includes(const Importer& import); - Include load_import(const Importer&, ParserState pstate); - - Sass_Output_Style output_style() { return c_options.output_style; }; - std::vector get_included_files(bool skip = false, size_t headers = 0); - - private: - void collect_plugin_paths(const char* paths_str); - void collect_plugin_paths(string_list* paths_array); - void collect_include_paths(const char* paths_str); - void collect_include_paths(string_list* paths_array); - void collect_extensions(const char* extensions_str); - void collect_extensions(string_list* extensions_array); - std::string format_embedded_source_map(); - std::string format_source_mapping_url(const std::string& out_path); - - - // void register_built_in_functions(Env* env); - // void register_function(Signature sig, Native_Function f, Env* env); - // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); - // void register_overload_stub(std::string name, Env* env); - - public: - const std::string& cwd() { return CWD; }; - }; - - class File_Context : public Context { - public: - File_Context(struct Sass_File_Context& ctx) - : Context(ctx) - { } - virtual ~File_Context(); - virtual Block_Obj parse(); - }; - - class Data_Context : public Context { - public: - char* source_c_str; - char* srcmap_c_str; - Data_Context(struct Sass_Data_Context& ctx) - : Context(ctx) - { - source_c_str = ctx.source_string; - srcmap_c_str = ctx.srcmap_string; - ctx.source_string = 0; // passed away - ctx.srcmap_string = 0; // passed away - } - virtual ~Data_Context(); - virtual Block_Obj parse(); - }; - -} - -#endif diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp deleted file mode 100644 index 6a12fdf7b..000000000 --- a/src/libsass/src/cssize.cpp +++ /dev/null @@ -1,606 +0,0 @@ -#include "sass.hpp" -#include -#include -#include - -#include "cssize.hpp" -#include "context.hpp" - -namespace Sass { - - Cssize::Cssize(Context& ctx) - : ctx(ctx), - traces(ctx.traces), - block_stack(std::vector()), - p_stack(std::vector()) - { } - - Statement_Ptr Cssize::parent() - { - return p_stack.size() ? p_stack.back() : block_stack.front(); - } - - Block_Ptr Cssize::operator()(Block_Ptr b) - { - Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); - // bb->tabs(b->tabs()); - block_stack.push_back(bb); - append_block(b, bb); - block_stack.pop_back(); - return bb.detach(); - } - - Statement_Ptr Cssize::operator()(Trace_Ptr t) - { - traces.push_back(Backtrace(t->pstate())); - auto result = t->block()->perform(this); - traces.pop_back(); - return result; - } - - Statement_Ptr Cssize::operator()(Declaration_Ptr d) - { - String_Obj property = Cast(d->property()); - - if (Declaration_Ptr dd = Cast(parent())) { - String_Obj parent_property = Cast(dd->property()); - property = SASS_MEMORY_NEW(String_Constant, - d->property()->pstate(), - parent_property->to_string() + "-" + property->to_string()); - if (!dd->value()) { - d->tabs(dd->tabs() + 1); - } - } - - Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, - d->pstate(), - property, - d->value(), - d->is_important(), - d->is_custom_property()); - dd->is_indented(d->is_indented()); - dd->tabs(d->tabs()); - - p_stack.push_back(dd); - Block_Obj bb = d->block() ? operator()(d->block()) : NULL; - p_stack.pop_back(); - - if (bb && bb->length()) { - if (dd->value() && !dd->value()->is_invisible()) { - bb->unshift(dd); - } - return bb.detach(); - } - else if (dd->value() && !dd->value()->is_invisible()) { - return dd.detach(); - } - - return 0; - } - - Statement_Ptr Cssize::operator()(Directive_Ptr r) - { - if (!r->block() || !r->block()->length()) return r; - - if (parent()->statement_type() == Statement::RULESET) - { - return (r->is_keyframes()) ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); - } - - p_stack.push_back(r); - Directive_Obj rr = SASS_MEMORY_NEW(Directive, - r->pstate(), - r->keyword(), - r->selector(), - r->block() ? operator()(r->block()) : 0); - if (r->value()) rr->value(r->value()); - p_stack.pop_back(); - - bool directive_exists = false; - size_t L = rr->block() ? rr->block()->length() : 0; - for (size_t i = 0; i < L && !directive_exists; ++i) { - Statement_Obj s = r->block()->at(i); - if (s->statement_type() != Statement::BUBBLE) directive_exists = true; - else { - Bubble_Obj s_obj = Cast(s); - s = s_obj->node(); - if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; - else directive_exists = (Cast(s)->keyword() == rr->keyword()); - } - - } - - Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); - if (!(directive_exists || rr->is_keyframes())) - { - Directive_Ptr empty_node = Cast(rr); - empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); - result->append(empty_node); - } - - Block_Obj db = rr->block(); - if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); - Block_Obj ss = debubble(db, rr); - for (size_t i = 0, L = ss->length(); i < L; ++i) { - result->append(ss->at(i)); - } - - return result; - } - - Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) - { - if (!r->block() || !r->block()->length()) return r; - - Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, - r->pstate(), - operator()(r->block())); - if (!r->name().isNull()) rr->name(r->name()); - - return debubble(rr->block(), rr); - } - - Statement_Ptr Cssize::operator()(Ruleset_Ptr r) - { - p_stack.push_back(r); - // this can return a string schema - // string schema is not a statement! - // r->block() is already a string schema - // and that is comming from propset expand - Block_Ptr bb = operator()(r->block()); - // this should protect us (at least a bit) from our mess - // fixing this properly is harder that it should be ... - if (Cast(bb) == NULL) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, - r->pstate(), - r->selector(), - bb); - - rr->is_root(r->is_root()); - // rr->tabs(r->block()->tabs()); - p_stack.pop_back(); - - if (!rr->block()) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - - Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - for (size_t i = 0, L = rr->block()->length(); i < L; i++) - { - Statement_Ptr s = rr->block()->at(i); - if (bubblable(s)) rules->append(s); - if (!bubblable(s)) props->append(s); - } - - if (props->length()) - { - Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - pb->concat(props); - rr->block(pb); - - for (size_t i = 0, L = rules->length(); i < L; i++) - { - Statement_Ptr stm = rules->at(i); - stm->tabs(stm->tabs() + 1); - } - - rules->unshift(rr); - } - - Block_Ptr ptr = rules; - rules = debubble(rules); - void* lp = ptr; - void* rp = rules; - if (lp != rp) { - Block_Obj obj = ptr; - } - - if (!(!rules->length() || - !bubblable(rules->last()) || - parent()->statement_type() == Statement::RULESET)) - { - rules->last()->group_end(true); - } - return rules; - } - - Statement_Ptr Cssize::operator()(Null_Ptr m) - { - return 0; - } - - Statement_Ptr Cssize::operator()(Media_Block_Ptr m) - { - if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } - - if (parent()->statement_type() == Statement::MEDIA) - { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } - - p_stack.push_back(m); - - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); - } - - Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) - { - if (!m->block()->length()) - { return m; } - - if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } - - p_stack.push_back(m); - - Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, - m->pstate(), - m->condition(), - operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); - } - - Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) - { - bool tmp = false; - for (size_t i = 0, L = p_stack.size(); i < L; ++i) { - Statement_Ptr s = p_stack[i]; - tmp |= m->exclude_node(s); - } - - if (!tmp && m->block()) - { - Block_Ptr bb = operator()(m->block()); - for (size_t i = 0, L = bb->length(); i < L; ++i) { - // (bb->elements())[i]->tabs(m->tabs()); - Statement_Obj stm = bb->at(i); - if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); - } - if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); - return bb; - } - - if (m->exclude_node(parent())) - { - return SASS_MEMORY_NEW(Bubble, m->pstate(), m); - } - - return bubble(m); - } - - Statement_Ptr Cssize::bubble(Directive_Ptr m) - { - Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); - wrapper_block->append(new_rule); - Directive_Obj mm = SASS_MEMORY_NEW(Directive, - m->pstate(), - m->keyword(), - m->selector(), - wrapper_block); - if (m->value()) mm->value(m->value()); - - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) - { - if (!m || !m->block()) return NULL; - Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - if (new_rule) { - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - wrapper_block->append(new_rule); - } - - At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, - m->pstate(), - wrapper_block, - m->expression()); - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) - { - Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, - m->pstate(), - m->condition(), - wrapper_block); - - mm->tabs(m->tabs()); - - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement_Ptr Cssize::bubble(Media_Block_Ptr m) - { - Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - wrapper_block); - - mm->tabs(m->tabs()); - - return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - } - - bool Cssize::bubblable(Statement_Ptr s) - { - return Cast(s) || s->bubbles(); - } - - Block_Ptr Cssize::flatten(Block_Ptr b) - { - Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr ss = b->at(i); - if (Block_Ptr bb = Cast(ss)) { - Block_Obj bs = flatten(bb); - for (size_t j = 0, K = bs->length(); j < K; ++j) { - result->append(bs->at(j)); - } - } - else { - result->append(ss); - } - } - return result; - } - - std::vector> Cssize::slice_by_bubble(Block_Ptr b) - { - std::vector> results; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj value = b->at(i); - bool key = Cast(value) != NULL; - - if (!results.empty() && results.back().first == key) - { - Block_Obj wrapper_block = results.back().second; - wrapper_block->append(value); - } - else - { - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); - wrapper_block->append(value); - results.push_back(std::make_pair(key, wrapper_block)); - } - } - return results; - } - - Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) - { - Has_Block_Obj previous_parent = 0; - std::vector> baz = slice_by_bubble(children); - Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); - - for (size_t i = 0, L = baz.size(); i < L; ++i) { - bool is_bubble = baz[i].first; - Block_Obj slice = baz[i].second; - - if (!is_bubble) { - if (!parent) { - result->append(slice); - } - else if (previous_parent) { - previous_parent->block()->concat(slice); - } - else { - previous_parent = Cast(SASS_MEMORY_COPY(parent)); - previous_parent->block(slice); - previous_parent->tabs(parent->tabs()); - - result->append(previous_parent); - } - continue; - } - - for (size_t j = 0, K = slice->length(); j < K; ++j) - { - Statement_Ptr ss; - Statement_Obj stm = slice->at(j); - // this has to go now here (too bad) - Bubble_Obj node = Cast(stm); - Media_Block_Ptr m1 = NULL; - Media_Block_Ptr m2 = NULL; - if (parent) m1 = Cast(parent); - if (node) m2 = Cast(node->node()); - if (!parent || - parent->statement_type() != Statement::MEDIA || - node->node()->statement_type() != Statement::MEDIA || - (m1 && m2 && *m1->media_queries() == *m2->media_queries()) - ) - { - ss = node->node(); - } - else - { - List_Obj mq = merge_media_queries( - Cast(node->node()), - Cast(parent) - ); - if (!mq->length()) continue; - if (Media_Block* b = Cast(node->node())) { - b->media_queries(mq); - } - ss = node->node(); - } - - if (!ss) continue; - - ss->tabs(ss->tabs() + node->tabs()); - ss->group_end(node->group_end()); - - Block_Obj bb = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - bb->append(ss->perform(this)); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - - Block_Ptr wrapper = flatten(bb); - wrapper_block->append(wrapper); - - if (wrapper->length()) { - previous_parent = NULL; - } - - if (wrapper_block) { - result->append(wrapper_block); - } - } - } - - return flatten(result); - } - - Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) - { - return static_cast(n); - } - - void Cssize::append_block(Block_Ptr b, Block_Ptr cur) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj ith = b->at(i)->perform(this); - if (Block_Ptr bb = Cast(ith)) { - for (size_t j = 0, K = bb->length(); j < K; ++j) { - cur->append(bb->at(j)); - } - } - else if (ith) { - cur->append(ith); - } - } - } - - List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) - { - List_Ptr qq = SASS_MEMORY_NEW(List, - m1->media_queries()->pstate(), - m1->media_queries()->length(), - SASS_COMMA); - - for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { - for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { - Expression_Obj l1 = m1->media_queries()->at(i); - Expression_Obj l2 = m2->media_queries()->at(j); - Media_Query_Ptr mq1 = Cast(l1); - Media_Query_Ptr mq2 = Cast(l2); - Media_Query_Ptr mq = merge_media_query(mq1, mq2); - if (mq) qq->append(mq); - } - } - - return qq; - } - - - Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) - { - - std::string type; - std::string mod; - - std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); - std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; - std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); - std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; - - - if (t1.empty()) t1 = t2; - if (t2.empty()) t2 = t1; - - if ((m1 == "not") ^ (m2 == "not")) { - if (t1 == t2) { - return 0; - } - type = m1 == "not" ? t2 : t1; - mod = m1 == "not" ? m2 : m1; - } - else if (m1 == "not" && m2 == "not") { - if (t1 != t2) { - return 0; - } - type = t1; - mod = "not"; - } - else if (t1 != t2) { - return 0; - } else { - type = t1; - mod = m1.empty() ? m2 : m1; - } - - Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, - mq1->pstate(), - 0, - mq1->length() + mq2->length(), - mod == "not", - mod == "only"); - - if (!type.empty()) { - mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); - } - - mm->concat(mq2); - mm->concat(mq1); - - return mm; - } -} diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp deleted file mode 100644 index 5a6c704b0..000000000 --- a/src/libsass/src/cssize.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef SASS_CSSIZE_H -#define SASS_CSSIZE_H - -#include "ast.hpp" -#include "context.hpp" -#include "operation.hpp" -#include "environment.hpp" - -namespace Sass { - - struct Backtrace; - - class Cssize : public Operation_CRTP { - - Context& ctx; - Backtraces& traces; - std::vector block_stack; - std::vector p_stack; - - Statement_Ptr fallback_impl(AST_Node_Ptr n); - - public: - Cssize(Context&); - ~Cssize() { } - - Selector_List_Ptr selector(); - - Block_Ptr operator()(Block_Ptr); - Statement_Ptr operator()(Ruleset_Ptr); - // Statement_Ptr operator()(Bubble_Ptr); - Statement_Ptr operator()(Media_Block_Ptr); - Statement_Ptr operator()(Supports_Block_Ptr); - Statement_Ptr operator()(At_Root_Block_Ptr); - Statement_Ptr operator()(Directive_Ptr); - Statement_Ptr operator()(Keyframe_Rule_Ptr); - Statement_Ptr operator()(Trace_Ptr); - Statement_Ptr operator()(Declaration_Ptr); - // Statement_Ptr operator()(Assignment_Ptr); - // Statement_Ptr operator()(Import_Ptr); - // Statement_Ptr operator()(Import_Stub_Ptr); - // Statement_Ptr operator()(Warning_Ptr); - // Statement_Ptr operator()(Error_Ptr); - // Statement_Ptr operator()(Comment_Ptr); - // Statement_Ptr operator()(If_Ptr); - // Statement_Ptr operator()(For_Ptr); - // Statement_Ptr operator()(Each_Ptr); - // Statement_Ptr operator()(While_Ptr); - // Statement_Ptr operator()(Return_Ptr); - // Statement_Ptr operator()(Extension_Ptr); - // Statement_Ptr operator()(Definition_Ptr); - // Statement_Ptr operator()(Mixin_Call_Ptr); - // Statement_Ptr operator()(Content_Ptr); - Statement_Ptr operator()(Null_Ptr); - - Statement_Ptr parent(); - std::vector> slice_by_bubble(Block_Ptr); - Statement_Ptr bubble(Directive_Ptr); - Statement_Ptr bubble(At_Root_Block_Ptr); - Statement_Ptr bubble(Media_Block_Ptr); - Statement_Ptr bubble(Supports_Block_Ptr); - - Block_Ptr debubble(Block_Ptr children, Statement_Ptr parent = 0); - Block_Ptr flatten(Block_Ptr); - bool bubblable(Statement_Ptr); - - List_Ptr merge_media_queries(Media_Block_Ptr, Media_Block_Ptr); - Media_Query_Ptr merge_media_query(Media_Query_Ptr, Media_Query_Ptr); - - template - Statement_Ptr fallback(U x) { return fallback_impl(x); } - - void append_block(Block_Ptr, Block_Ptr); - }; - -} - -#endif diff --git a/src/libsass/src/debug.hpp b/src/libsass/src/debug.hpp deleted file mode 100644 index 43fe05e67..000000000 --- a/src/libsass/src/debug.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SASS_DEBUG_H -#define SASS_DEBUG_H - -#include - -#ifndef UINT32_MAX - #define UINT32_MAX 0xffffffffU -#endif - -enum dbg_lvl_t : uint32_t { - NONE = 0, - TRIM = 1, - CHUNKS = 2, - SUBWEAVE = 4, - WEAVE = 8, - EXTEND_COMPOUND = 16, - EXTEND_COMPLEX = 32, - LCS = 64, - EXTEND_OBJECT = 128, - ALL = UINT32_MAX -}; - -#ifdef DEBUG - -#ifndef DEBUG_LVL -const uint32_t debug_lvl = UINT32_MAX; -#else -const uint32_t debug_lvl = (DEBUG_LVL); -#endif // DEBUG_LVL - -#define DEBUG_PRINT(lvl, x) if((lvl) & debug_lvl) { std::cerr << x; } -#define DEBUG_PRINTLN(lvl, x) if((lvl) & debug_lvl) { std::cerr << x << std::endl; } -#define DEBUG_EXEC(lvl, x) if((lvl) & debug_lvl) { x; } - -#else // DEBUG - -#define DEBUG_PRINT(lvl, x) -#define DEBUG_PRINTLN(lvl, x) -#define DEBUG_EXEC(lvl, x) - -#endif // DEBUG - -#endif // SASS_DEBUG diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp deleted file mode 100644 index f1ceabd9a..000000000 --- a/src/libsass/src/debugger.hpp +++ /dev/null @@ -1,801 +0,0 @@ -#ifndef SASS_DEBUGGER_H -#define SASS_DEBUGGER_H - -#include -#include -#include "node.hpp" -#include "ast_fwd_decl.hpp" - -using namespace Sass; - -inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); - -inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); -} - -inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : set) { - debug_ast(pair, ind + ""); - // debug_ast(set[pair], ind + "first: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) -{ - size_t pos = 0; - while((pos = str.find(oldStr, pos)) != std::string::npos) - { - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); - } - return str; -} - -inline std::string prettyprint(const std::string& str) { - std::string clean = str_replace(str, "\n", "\\n"); - clean = str_replace(clean, " ", "\\t"); - clean = str_replace(clean, "\r", "\\r"); - return clean; -} - -inline std::string longToHex(long long t) { - std::stringstream is; - is << std::hex << t; - return is.str(); -} - -inline std::string pstate_source_position(AST_Node_Ptr node) -{ - std::stringstream str; - Position start(node->pstate()); - Position end(start + node->pstate().offset); - str << (start.file == std::string::npos ? -1 : start.file) - << "@[" << start.line << ":" << start.column << "]" - << "-[" << end.line << ":" << end.column << "]"; -#ifdef DEBUG_SHARED_PTR - str << "x" << node->getRefCount() << "" - << " " << node->getDbgFile() - << "@" << node->getDbgLine(); -#endif - return str.str(); -} - -inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) -{ - if (node == 0) return; - if (ind == "") std::cerr << "####################################################################\n"; - if (Cast(node)) { - Bubble_Ptr bubble = Cast(node); - std::cerr << ind << "Bubble " << bubble; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << bubble->tabs(); - std::cerr << std::endl; - debug_ast(bubble->node(), ind + " ", env); - } else if (Cast(node)) { - Trace_Ptr trace = Cast(node); - std::cerr << ind << "Trace " << trace; - std::cerr << " (" << pstate_source_position(node) << ")" - << " [name:" << trace->name() << ", type: " << trace->type() << "]" - << std::endl; - debug_ast(trace->block(), ind + " ", env); - } else if (Cast(node)) { - At_Root_Block_Ptr root_block = Cast(node); - std::cerr << ind << "At_Root_Block " << root_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << root_block->tabs(); - std::cerr << std::endl; - debug_ast(root_block->expression(), ind + ":", env); - debug_ast(root_block->block(), ind + " ", env); - } else if (Cast(node)) { - Selector_List_Ptr selector = Cast(node); - std::cerr << ind << "Selector_List " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); - std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->schema(), ind + "#{} "); - - for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - -// } else if (Cast(node)) { -// Expression_Ptr expression = Cast(node); -// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; - - } else if (Cast(node)) { - Parent_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Parent_Selector " << selector; -// if (selector->not_selector()) cerr << " [in_declaration]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; -// debug_ast(selector->selector(), ind + "->", env); - - } else if (Cast(node)) { - Complex_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Complex_Selector " << selector - << " (" << pstate_source_position(node) << ")" - << " <" << selector->hash() << ">" - << " [length:" << longToHex(selector->length()) << "]" - << " [weight:" << longToHex(selector->specificity()) << "]" - << " [@media:" << selector->media_block() << "]" - << (selector->is_invisible() ? " [INVISIBLE]": " -") - << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_parent_ref() ? " [has parent]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << (selector->has_line_break() ? " [line-break]": " -") - << " -- "; - std::string del; - switch (selector->combinator()) { - case Complex_Selector::PARENT_OF: del = ">"; break; - case Complex_Selector::PRECEDES: del = "~"; break; - case Complex_Selector::ADJACENT_TO: del = "+"; break; - case Complex_Selector::ANCESTOR_OF: del = " "; break; - case Complex_Selector::REFERENCE: del = "//"; break; - } - // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); - if (selector->tail()) { - debug_ast(selector->tail(), ind + "{" + del + "}", env); - } else if(del != " ") { - std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; - } - ComplexSelectorSet set = selector->sources(); - // debug_sources_set(set, ind + " @--> "); - } else if (Cast(node)) { - Compound_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Compound_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->extended() ? " [extended]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Wrapped_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Wrapped_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->selector(), ind + " () ", env); - } else if (Cast(node)) { - Pseudo_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Pseudo_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->expression(), ind + " <= ", env); - } else if (Cast(node)) { - Attribute_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Attribute_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); - } else if (Cast(node)) { - Class_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Class_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - } else if (Cast(node)) { - Id_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Id_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - } else if (Cast(node)) { - Element_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Element_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; - std::cerr << std::endl; - } else if (Cast(node)) { - - Placeholder_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; - std::cerr << " (" << pstate_source_position(selector) << ")" - << " <" << selector->hash() << ">" - << " [@media:" << selector->media_block() << "]" - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << std::endl; - - } else if (Cast(node)) { - Simple_Selector* selector = Cast(node); - std::cerr << ind << "Simple_Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; - - } else if (Cast(node)) { - Selector_Schema_Ptr selector = Cast(node); - std::cerr << ind << "Selector_Schema " << selector; - std::cerr << " (" << pstate_source_position(node) << ")" - << " [@media:" << selector->media_block() << "]" - << (selector->connect_parent() ? " [connect-parent]": " -") - << std::endl; - - debug_ast(selector->contents(), ind + " "); - // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { - Selector_Ptr selector = Cast(node); - std::cerr << ind << "Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << std::endl; - - } else if (Cast(node)) { - Media_Query_Expression_Ptr block = Cast(node); - std::cerr << ind << "Media_Query_Expression " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - - } else if (Cast(node)) { - Media_Query_Ptr block = Cast(node); - std::cerr << ind << "Media_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_negated() ? " [is_negated]": " -") - << (block->is_restricted() ? " [is_restricted]": " -") - << std::endl; - debug_ast(block->media_type(), ind + " "); - for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { - Media_Block_Ptr block = Cast(node); - std::cerr << ind << "Media_Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->media_queries(), ind + " =@ "); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Supports_Block_Ptr block = Cast(node); - std::cerr << ind << "Supports_Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->condition(), ind + " =@ "); - debug_ast(block->block(), ind + " <>"); - } else if (Cast(node)) { - Supports_Operator_Ptr block = Cast(node); - std::cerr << ind << "Supports_Operator " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->left(), ind + " left) "); - debug_ast(block->right(), ind + " right) "); - } else if (Cast(node)) { - Supports_Negation_Ptr block = Cast(node); - std::cerr << ind << "Supports_Negation " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->condition(), ind + " condition) "); - } else if (Cast(node)) { - At_Root_Query_Ptr block = Cast(node); - std::cerr << ind << "At_Root_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { - Supports_Declaration_Ptr block = Cast(node); - std::cerr << ind << "Supports_Declaration " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { - Block_Ptr root_block = Cast(node); - std::cerr << ind << "Block " << root_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (root_block->is_root()) std::cerr << " [root]"; - std::cerr << " " << root_block->tabs() << std::endl; - for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Warning_Ptr block = Cast(node); - std::cerr << ind << "Warning " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->message(), ind + " : "); - } else if (Cast(node)) { - Error_Ptr block = Cast(node); - std::cerr << ind << "Error " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Debug_Ptr block = Cast(node); - std::cerr << ind << "Debug " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->value(), ind + " "); - } else if (Cast(node)) { - Comment_Ptr block = Cast(node); - std::cerr << ind << "Comment " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << - " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(block->text(), ind + "// ", env); - } else if (Cast(node)) { - If_Ptr block = Cast(node); - std::cerr << ind << "If " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->predicate(), ind + " = "); - debug_ast(block->block(), ind + " <>"); - debug_ast(block->alternative(), ind + " ><"); - } else if (Cast(node)) { - Return_Ptr block = Cast(node); - std::cerr << ind << "Return " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Extension_Ptr block = Cast(node); - std::cerr << ind << "Extension " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "-> ", env); - } else if (Cast(node)) { - Content_Ptr block = Cast(node); - std::cerr << ind << "Content " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [@media:" << block->media_block() << "]"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Import_Stub_Ptr block = Cast(node); - std::cerr << ind << "Import_Stub " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->imp_path() << "] "; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Import_Ptr block = Cast(node); - std::cerr << ind << "Import " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - // std::vector files_; - for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); - debug_ast(block->import_queries(), ind + "@@ "); - } else if (Cast(node)) { - Assignment_Ptr block = Cast(node); - std::cerr << ind << "Assignment " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; - debug_ast(block->value(), ind + "=", env); - } else if (Cast(node)) { - Declaration_Ptr block = Cast(node); - std::cerr << ind << "Declaration " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->property(), ind + " prop: ", env); - debug_ast(block->value(), ind + " value: ", env); - debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Keyframe_Rule_Ptr has_block = Cast(node); - std::cerr << ind << "Keyframe_Rule " << has_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << has_block->tabs() << std::endl; - if (has_block->name()) debug_ast(has_block->name(), ind + "@"); - if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Directive_Ptr block = Cast(node); - std::cerr << ind << "Directive " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "~", env); - debug_ast(block->value(), ind + "+", env); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Each_Ptr block = Cast(node); - std::cerr << ind << "Each " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - For_Ptr block = Cast(node); - std::cerr << ind << "For " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - While_Ptr block = Cast(node); - std::cerr << ind << "While " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Definition_Ptr block = Cast(node); - std::cerr << ind << "Definition " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << block->name() << "] "; - std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; - // this seems to lead to segfaults some times? - // std::cerr << " [signature: " << block->signature() << "] "; - std::cerr << " [native: " << block->native_function() << "] "; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->parameters(), ind + " params: ", env); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Mixin_Call_Ptr block = Cast(node); - std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); - std::cerr << " (" << pstate_source_position(block) << ")"; - std::cerr << " [" << block->name() << "]"; - std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; - debug_ast(block->arguments(), ind + " args: "); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (Ruleset_Ptr ruleset = Cast(node)) { - std::cerr << ind << "Ruleset " << ruleset; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [indent: " << ruleset->tabs() << "]"; - std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << (ruleset->is_root() ? " [root]" : ""); - std::cerr << std::endl; - debug_ast(ruleset->selector(), ind + ">"); - debug_ast(ruleset->block(), ind + " "); - } else if (Cast(node)) { - Block_Ptr block = Cast(node); - std::cerr << ind << "Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << " [indent: " << block->tabs() << "]" << std::endl; - for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Variable_Ptr expression = Cast(node); - std::cerr << ind << "Variable " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]" << std::endl; - std::string name(expression->name()); - if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); - } else if (Cast(node)) { - Function_Call_Schema_Ptr expression = Cast(node); - std::cerr << ind << "Function_Call_Schema " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << "" << std::endl; - debug_ast(expression->name(), ind + "name: ", env); - debug_ast(expression->arguments(), ind + " args: ", env); - } else if (Cast(node)) { - Function_Call_Ptr expression = Cast(node); - std::cerr << ind << "Function_Call " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->arguments(), ind + " args: ", env); - debug_ast(expression->func(), ind + " func: ", env); - } else if (Cast(node)) { - Function_Ptr expression = Cast(node); - std::cerr << ind << "Function " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->definition(), ind + " definition: ", env); - } else if (Cast(node)) { - Arguments_Ptr expression = Cast(node); - std::cerr << ind << "Arguments " << expression; - if (expression->is_delayed()) std::cerr << " [delayed]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; - if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; - if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; - std::cerr << std::endl; - for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Argument_Ptr expression = Cast(node); - std::cerr << ind << "Argument " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->value().ptr() << "]"; - std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [rest: " << expression->is_rest_argument() << "] "; - std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; - debug_ast(expression->value(), ind + " value: ", env); - } else if (Cast(node)) { - Parameters_Ptr expression = Cast(node); - std::cerr << ind << "Parameters " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; - std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; - std::cerr << std::endl; - for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Parameter_Ptr expression = Cast(node); - std::cerr << ind << "Parameter " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [default: " << expression->default_value().ptr() << "] "; - std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; - } else if (Cast(node)) { - Unary_Expression_Ptr expression = Cast(node); - std::cerr << ind << "Unary_Expression " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type() << "]" << std::endl; - debug_ast(expression->operand(), ind + " operand: ", env); - } else if (Cast(node)) { - Binary_Expression_Ptr expression = Cast(node); - std::cerr << ind << "Binary_Expression " << expression; - if (expression->is_interpolant()) std::cerr << " [is interpolant] "; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [ws_before: " << expression->op().ws_before << "] "; - std::cerr << " [ws_after: " << expression->op().ws_after << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type_name() << "]" << std::endl; - debug_ast(expression->left(), ind + " left: ", env); - debug_ast(expression->right(), ind + " right: ", env); - } else if (Cast(node)) { - Map_Ptr expression = Cast(node); - std::cerr << ind << "Map " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [Hashed]" << std::endl; - for (const auto& i : expression->elements()) { - debug_ast(i.first, ind + " key: "); - debug_ast(i.second, ind + " val: "); - } - } else if (Cast(node)) { - List_Ptr expression = Cast(node); - std::cerr << ind << "List " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " (" << expression->length() << ") " << - (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << - " [delayed: " << expression->is_delayed() << "] " << - " [interpolant: " << expression->is_interpolant() << "] " << - " [listized: " << expression->from_selector() << "] " << - " [arglist: " << expression->is_arglist() << "] " << - " [bracketed: " << expression->is_bracketed() << "] " << - " [expanded: " << expression->is_expanded() << "] " << - " [hash: " << expression->hash() << "] " << - std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Content_Ptr expression = Cast(node); - std::cerr << ind << "Content " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [@media:" << expression->media_block() << "]"; - std::cerr << " [Statement]" << std::endl; - } else if (Cast(node)) { - Boolean_Ptr expression = Cast(node); - std::cerr << ind << "Boolean " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [" << expression->value() << "]" << std::endl; - } else if (Cast(node)) { - Color_Ptr expression = Cast(node); - std::cerr << ind << "Color " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; - } else if (Cast(node)) { - Number_Ptr expression = Cast(node); - std::cerr << ind << "Number " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [" << expression->value() << expression->unit() << "]" << - " [hash: " << expression->hash() << "] " << - std::endl; - } else if (Cast(node)) { - Null_Ptr expression = Cast(node); - std::cerr << ind << "Null " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] " - // " [hash: " << expression->hash() << "] " - << std::endl; - } else if (Cast(node)) { - String_Quoted_Ptr expression = Cast(node); - std::cerr << ind << "String_Quoted " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (Cast(node)) { - String_Constant_Ptr expression = Cast(node); - std::cerr << ind << "String_Constant " << expression; - if (expression->concrete_type()) { - std::cerr << " " << expression->concrete_type(); - } - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (Cast(node)) { - String_Schema_Ptr expression = Cast(node); - std::cerr << ind << "String_Schema " << expression; - std::cerr << " (" << pstate_source_position(expression) << ")"; - std::cerr << " " << expression->concrete_type(); - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->css()) std::cerr << " [css]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [is interpolant]"; - if (expression->has_interpolant()) std::cerr << " [has interpolant]"; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - String_Ptr expression = Cast(node); - std::cerr << ind << "String " << expression; - std::cerr << " " << expression->concrete_type(); - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; - } else if (Cast(node)) { - Expression_Ptr expression = Cast(node); - std::cerr << ind << "Expression " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - switch (expression->concrete_type()) { - case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; - case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; - case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; - case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; - case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; - case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; - case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; - case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; - case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; - case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; - case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; - case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; - case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; - case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; - case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; - } - std::cerr << std::endl; - } else if (Cast(node)) { - Has_Block_Ptr has_block = Cast(node); - std::cerr << ind << "Has_Block " << has_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << has_block->tabs() << std::endl; - if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Statement_Ptr statement = Cast(node); - std::cerr << ind << "Statement " << statement; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << statement->tabs() << std::endl; - } - - if (ind == "") std::cerr << "####################################################################\n"; -} - -inline void debug_node(Node* node, std::string ind = "") -{ - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; - if (node->isCombinator()) { - std::cerr << ind; - std::cerr << "Combinator "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - switch (node->combinator()) { - case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; - case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; - case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; - case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; - case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; - } - std::cerr << std::endl; - // debug_ast(node->combinator(), ind + " "); - } else if (node->isSelector()) { - std::cerr << ind; - std::cerr << "Selector "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - debug_ast(node->selector(), ind + " "); - } else if (node->isCollection()) { - std::cerr << ind; - std::cerr << "Collection "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - for(auto n : (*node->collection())) { - debug_node(&n, ind + " "); - } - } else if (node->isNil()) { - std::cerr << ind; - std::cerr << "Nil "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } else { - std::cerr << ind; - std::cerr << "OTHER "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; -} - -/* -inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) -{ - debug_ast(const_cast(node), ind, env); -} -*/ -inline void debug_node(const Node* node, std::string ind = "") -{ - debug_node(const_cast(node), ind); -} - -inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &it : map.values()) { - debug_ast(it.first, ind + "first: "); - debug_ast(it.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : *entries) { - debug_ast(pair.first, ind + "first: "); - debug_ast(pair.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -#endif // SASS_DEBUGGER diff --git a/src/libsass/src/emitter.cpp b/src/libsass/src/emitter.cpp deleted file mode 100644 index 161e689a9..000000000 --- a/src/libsass/src/emitter.cpp +++ /dev/null @@ -1,297 +0,0 @@ -#include "sass.hpp" -#include "util.hpp" -#include "context.hpp" -#include "output.hpp" -#include "emitter.hpp" -#include "utf8_string.hpp" - -namespace Sass { - - Emitter::Emitter(struct Sass_Output_Options& opt) - : wbuf(), - opt(opt), - indentation(0), - scheduled_space(0), - scheduled_linefeed(0), - scheduled_delimiter(false), - scheduled_crutch(0), - scheduled_mapping(0), - in_custom_property(false), - in_comment(false), - in_wrapped(false), - in_media_block(false), - in_declaration(false), - in_space_array(false), - in_comma_array(false) - { } - - // return buffer as string - std::string Emitter::get_buffer(void) - { - return wbuf.buffer; - } - - Sass_Output_Style Emitter::output_style(void) const - { - return opt.output_style; - } - - // PROXY METHODS FOR SOURCE MAPS - - void Emitter::add_source_index(size_t idx) - { wbuf.smap.source_index.push_back(idx); } - - std::string Emitter::render_srcmap(Context &ctx) - { return wbuf.smap.render_srcmap(ctx); } - - void Emitter::set_filename(const std::string& str) - { wbuf.smap.file = str; } - - void Emitter::schedule_mapping(const AST_Node_Ptr node) - { scheduled_mapping = node; } - void Emitter::add_open_mapping(const AST_Node_Ptr node) - { wbuf.smap.add_open_mapping(node); } - void Emitter::add_close_mapping(const AST_Node_Ptr node) - { wbuf.smap.add_close_mapping(node); } - ParserState Emitter::remap(const ParserState& pstate) - { return wbuf.smap.remap(pstate); } - - // MAIN BUFFER MANIPULATION - - // add outstanding delimiter - void Emitter::finalize(bool final) - { - scheduled_space = 0; - if (output_style() == SASS_STYLE_COMPRESSED) - if (final) scheduled_delimiter = false; - if (scheduled_linefeed) - scheduled_linefeed = 1; - flush_schedules(); - } - - // flush scheduled space/linefeed - void Emitter::flush_schedules(void) - { - // check the schedule - if (scheduled_linefeed) { - std::string linefeeds = ""; - - for (size_t i = 0; i < scheduled_linefeed; i++) - linefeeds += opt.linefeed; - scheduled_space = 0; - scheduled_linefeed = 0; - append_string(linefeeds); - - } else if (scheduled_space) { - std::string spaces(scheduled_space, ' '); - scheduled_space = 0; - append_string(spaces); - } - if (scheduled_delimiter) { - scheduled_delimiter = false; - append_string(";"); - } - } - - // prepend some text or token to the buffer - void Emitter::prepend_output(const OutputBuffer& output) - { - wbuf.smap.prepend(output); - wbuf.buffer = output.buffer + wbuf.buffer; - } - - // prepend some text or token to the buffer - void Emitter::prepend_string(const std::string& text) - { - // do not adjust mappings for utf8 bom - // seems they are not counted in any UA - if (text.compare("\xEF\xBB\xBF") != 0) { - wbuf.smap.prepend(Offset(text)); - } - wbuf.buffer = text + wbuf.buffer; - } - - char Emitter::last_char() - { - return wbuf.buffer.back(); - } - - // append a single char to the buffer - void Emitter::append_char(const char chr) - { - // write space/lf - flush_schedules(); - // add to buffer - wbuf.buffer += chr; - // account for data in source-maps - wbuf.smap.append(Offset(chr)); - } - - // append some text or token to the buffer - void Emitter::append_string(const std::string& text) - { - - // write space/lf - flush_schedules(); - - if (in_comment && output_style() == COMPACT) { - // unescape comment nodes - std::string out = comment_to_string(text); - // add to buffer - wbuf.buffer += out; - // account for data in source-maps - wbuf.smap.append(Offset(out)); - } else { - // add to buffer - wbuf.buffer += text; - // account for data in source-maps - wbuf.smap.append(Offset(text)); - } - } - - // append some white-space only text - void Emitter::append_wspace(const std::string& text) - { - if (text.empty()) return; - if (peek_linefeed(text.c_str())) { - scheduled_space = 0; - append_mandatory_linefeed(); - } - } - - // append some text or token to the buffer - // this adds source-mappings for node start and end - void Emitter::append_token(const std::string& text, const AST_Node_Ptr node) - { - flush_schedules(); - add_open_mapping(node); - // hotfix for browser issues - // this is pretty ugly indeed - if (scheduled_crutch) { - add_open_mapping(scheduled_crutch); - scheduled_crutch = 0; - } - append_string(text); - add_close_mapping(node); - } - - // HELPER METHODS - - void Emitter::append_indentation() - { - if (output_style() == COMPRESSED) return; - if (output_style() == COMPACT) return; - if (in_declaration && in_comma_array) return; - if (scheduled_linefeed && indentation) - scheduled_linefeed = 1; - std::string indent = ""; - for (size_t i = 0; i < indentation; i++) - indent += opt.indent; - append_string(indent); - } - - void Emitter::append_delimiter() - { - scheduled_delimiter = true; - if (output_style() == COMPACT) { - if (indentation == 0) { - append_mandatory_linefeed(); - } else { - append_mandatory_space(); - } - } else if (output_style() != COMPRESSED) { - append_optional_linefeed(); - } - } - - void Emitter::append_comma_separator() - { - // scheduled_space = 0; - append_string(","); - append_optional_space(); - } - - void Emitter::append_colon_separator() - { - scheduled_space = 0; - append_string(":"); - if (!in_custom_property) append_optional_space(); - } - - void Emitter::append_mandatory_space() - { - scheduled_space = 1; - } - - void Emitter::append_optional_space() - { - if ((output_style() != COMPRESSED) && buffer().size()) { - unsigned char lst = buffer().at(buffer().length() - 1); - if (!isspace(lst) || scheduled_delimiter) { - if (last_char() != '(') { - append_mandatory_space(); - } - } - } - } - - void Emitter::append_special_linefeed() - { - if (output_style() == COMPACT) { - append_mandatory_linefeed(); - for (size_t p = 0; p < indentation; p++) - append_string(opt.indent); - } - } - - void Emitter::append_optional_linefeed() - { - if (in_declaration && in_comma_array) return; - if (output_style() == COMPACT) { - append_mandatory_space(); - } else { - append_mandatory_linefeed(); - } - } - - void Emitter::append_mandatory_linefeed() - { - if (output_style() != COMPRESSED) { - scheduled_linefeed = 1; - scheduled_space = 0; - // flush_schedules(); - } - } - - void Emitter::append_scope_opener(AST_Node_Ptr node) - { - scheduled_linefeed = 0; - append_optional_space(); - flush_schedules(); - if (node) add_open_mapping(node); - append_string("{"); - append_optional_linefeed(); - // append_optional_space(); - ++ indentation; - } - void Emitter::append_scope_closer(AST_Node_Ptr node) - { - -- indentation; - scheduled_linefeed = 0; - if (output_style() == COMPRESSED) - scheduled_delimiter = false; - if (output_style() == EXPANDED) { - append_optional_linefeed(); - append_indentation(); - } else { - append_optional_space(); - } - append_string("}"); - if (node) add_close_mapping(node); - append_optional_linefeed(); - if (indentation != 0) return; - if (output_style() != COMPRESSED) - scheduled_linefeed = 2; - } - -} diff --git a/src/libsass/src/emitter.hpp b/src/libsass/src/emitter.hpp deleted file mode 100644 index 3bf8f60da..000000000 --- a/src/libsass/src/emitter.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef SASS_EMITTER_H -#define SASS_EMITTER_H - -#include -#include "sass.hpp" -#include "sass/base.h" -#include "source_map.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - class Context; - - class Emitter { - - public: - Emitter(struct Sass_Output_Options& opt); - virtual ~Emitter() { } - - protected: - OutputBuffer wbuf; - public: - const std::string& buffer(void) { return wbuf.buffer; } - const SourceMap smap(void) { return wbuf.smap; } - const OutputBuffer output(void) { return wbuf; } - // proxy methods for source maps - void add_source_index(size_t idx); - void set_filename(const std::string& str); - void add_open_mapping(const AST_Node_Ptr node); - void add_close_mapping(const AST_Node_Ptr node); - void schedule_mapping(const AST_Node_Ptr node); - std::string render_srcmap(Context &ctx); - ParserState remap(const ParserState& pstate); - - public: - struct Sass_Output_Options& opt; - size_t indentation; - size_t scheduled_space; - size_t scheduled_linefeed; - bool scheduled_delimiter; - AST_Node_Ptr scheduled_crutch; - AST_Node_Ptr scheduled_mapping; - - public: - // output strings different in custom css properties - bool in_custom_property; - // output strings different in comments - bool in_comment; - // selector list does not get linefeeds - bool in_wrapped; - // lists always get a space after delimiter - bool in_media_block; - // nested list must not have parentheses - bool in_declaration; - // nested lists need parentheses - bool in_space_array; - bool in_comma_array; - - public: - // return buffer as std::string - std::string get_buffer(void); - // flush scheduled space/linefeed - Sass_Output_Style output_style(void) const; - // add outstanding linefeed - void finalize(bool final = true); - // flush scheduled space/linefeed - void flush_schedules(void); - // prepend some text or token to the buffer - void prepend_string(const std::string& text); - void prepend_output(const OutputBuffer& out); - // append some text or token to the buffer - void append_string(const std::string& text); - // append a single character to buffer - void append_char(const char chr); - // append some white-space only text - void append_wspace(const std::string& text); - // append some text or token to the buffer - // this adds source-mappings for node start and end - void append_token(const std::string& text, const AST_Node_Ptr node); - // query last appended character - char last_char(); - - public: // syntax sugar - void append_indentation(); - void append_optional_space(void); - void append_mandatory_space(void); - void append_special_linefeed(void); - void append_optional_linefeed(void); - void append_mandatory_linefeed(void); - void append_scope_opener(AST_Node_Ptr node = 0); - void append_scope_closer(AST_Node_Ptr node = 0); - void append_comma_separator(void); - void append_colon_separator(void); - void append_delimiter(void); - - }; - -} - -#endif diff --git a/src/libsass/src/environment.cpp b/src/libsass/src/environment.cpp deleted file mode 100644 index e382e7e05..000000000 --- a/src/libsass/src/environment.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "environment.hpp" - -namespace Sass { - - template - Environment::Environment(bool is_shadow) - : local_frame_(environment_map()), - parent_(0), is_shadow_(false) - { } - template - Environment::Environment(Environment* env, bool is_shadow) - : local_frame_(environment_map()), - parent_(env), is_shadow_(is_shadow) - { } - template - Environment::Environment(Environment& env, bool is_shadow) - : local_frame_(environment_map()), - parent_(&env), is_shadow_(is_shadow) - { } - - // link parent to create a stack - template - void Environment::link(Environment& env) { parent_ = &env; } - template - void Environment::link(Environment* env) { parent_ = env; } - - // this is used to find the global frame - // which is the second last on the stack - template - bool Environment::is_lexical() const - { - return !! parent_ && parent_->parent_; - } - - // only match the real root scope - // there is still a parent around - // not sure what it is actually use for - // I guess we store functions etc. there - template - bool Environment::is_global() const - { - return parent_ && ! parent_->parent_; - } - - template - environment_map& Environment::local_frame() { - return local_frame_; - } - - template - bool Environment::has_local(const std::string& key) const - { return local_frame_.find(key) != local_frame_.end(); } - - template EnvResult - Environment::find_local(const std::string& key) - { - auto end = local_frame_.end(); - auto it = local_frame_.find(key); - return EnvResult(it, it != end); - } - - template - T& Environment::get_local(const std::string& key) - { return local_frame_[key]; } - - template - void Environment::set_local(const std::string& key, const T& val) - { - local_frame_[key] = val; - } - template - void Environment::set_local(const std::string& key, T&& val) - { - local_frame_[key] = val; - } - - template - void Environment::del_local(const std::string& key) - { local_frame_.erase(key); } - - template - Environment* Environment::global_env() - { - Environment* cur = this; - while (cur->is_lexical()) { - cur = cur->parent_; - } - return cur; - } - - template - bool Environment::has_global(const std::string& key) - { return global_env()->has(key); } - - template - T& Environment::get_global(const std::string& key) - { return (*global_env())[key]; } - - template - void Environment::set_global(const std::string& key, const T& val) - { - global_env()->local_frame_[key] = val; - } - template - void Environment::set_global(const std::string& key, T&& val) - { - global_env()->local_frame_[key] = val; - } - - template - void Environment::del_global(const std::string& key) - { global_env()->local_frame_.erase(key); } - - template - Environment* Environment::lexical_env(const std::string& key) - { - Environment* cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur; - } - cur = cur->parent_; - } - return this; - } - - // see if we have a lexical variable - // move down the stack but stop before we - // reach the global frame (is not included) - template - bool Environment::has_lexical(const std::string& key) const - { - auto cur = this; - while (cur->is_lexical()) { - if (cur->has_local(key)) return true; - cur = cur->parent_; - } - return false; - } - - // see if we have a lexical we could update - // either update already existing lexical value - // or if flag is set, we create one if no lexical found - template - void Environment::set_lexical(const std::string& key, const T& val) - { - Environment* cur = this; - bool shadow = false; - while ((cur && cur->is_lexical()) || shadow) { - EnvResult rv(cur->find_local(key)); - if (rv.found) { - rv.it->second = val; - return; - } - shadow = cur->is_shadow(); - cur = cur->parent_; - } - set_local(key, val); - } - // this one moves the value - template - void Environment::set_lexical(const std::string& key, T&& val) - { - Environment* cur = this; - bool shadow = false; - while ((cur && cur->is_lexical()) || shadow) { - EnvResult rv(cur->find_local(key)); - if (rv.found) { - rv.it->second = val; - return; - } - shadow = cur->is_shadow(); - cur = cur->parent_; - } - set_local(key, val); - } - - // look on the full stack for key - // include all scopes available - template - bool Environment::has(const std::string& key) const - { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return true; - } - cur = cur->parent_; - } - return false; - } - - // look on the full stack for key - // include all scopes available - template EnvResult - Environment::find(const std::string& key) - { - auto cur = this; - while (true) { - EnvResult rv(cur->find_local(key)); - if (rv.found) return rv; - cur = cur->parent_; - if (!cur) return rv; - } - }; - - // use array access for getter and setter functions - template - T& Environment::operator[](const std::string& key) - { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur->get_local(key); - } - cur = cur->parent_; - } - return get_local(key); - } -/* - #ifdef DEBUG - template - size_t Environment::print(std::string prefix) - { - size_t indent = 0; - if (parent_) indent = parent_->print(prefix) + 1; - std::cerr << prefix << std::string(indent, ' ') << "== " << this << std::endl; - for (typename environment_map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { - if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { - std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second; - if (Value_Ptr val = Cast(i->second)) - { std::cerr << " : " << val->to_string(); } - std::cerr << std::endl; - } - } - return indent ; - } - #endif -*/ - // compile implementation for AST_Node - template class Environment; - -} - diff --git a/src/libsass/src/environment.hpp b/src/libsass/src/environment.hpp deleted file mode 100644 index a6939be23..000000000 --- a/src/libsass/src/environment.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef SASS_ENVIRONMENT_H -#define SASS_ENVIRONMENT_H - -#include -#include "ast_fwd_decl.hpp" -#include "ast_def_macros.hpp" - -namespace Sass { - - typedef environment_map::iterator EnvIter; - - class EnvResult { - public: - EnvIter it; - bool found; - public: - EnvResult(EnvIter it, bool found) - : it(it), found(found) {} - }; - - template - class Environment { - // TODO: test with map - environment_map local_frame_; - ADD_PROPERTY(Environment*, parent) - ADD_PROPERTY(bool, is_shadow) - - public: - Environment(bool is_shadow = false); - Environment(Environment* env, bool is_shadow = false); - Environment(Environment& env, bool is_shadow = false); - - // link parent to create a stack - void link(Environment& env); - void link(Environment* env); - - // this is used to find the global frame - // which is the second last on the stack - bool is_lexical() const; - - // only match the real root scope - // there is still a parent around - // not sure what it is actually use for - // I guess we store functions etc. there - bool is_global() const; - - // scope operates on the current frame - - environment_map& local_frame(); - - bool has_local(const std::string& key) const; - - EnvResult find_local(const std::string& key); - - T& get_local(const std::string& key); - - // set variable on the current frame - void set_local(const std::string& key, const T& val); - void set_local(const std::string& key, T&& val); - - void del_local(const std::string& key); - - // global operates on the global frame - // which is the second last on the stack - Environment* global_env(); - // get the env where the variable already exists - // if it does not yet exist, we return current env - Environment* lexical_env(const std::string& key); - - bool has_global(const std::string& key); - - T& get_global(const std::string& key); - - // set a variable on the global frame - void set_global(const std::string& key, const T& val); - void set_global(const std::string& key, T&& val); - - void del_global(const std::string& key); - - // see if we have a lexical variable - // move down the stack but stop before we - // reach the global frame (is not included) - bool has_lexical(const std::string& key) const; - - // see if we have a lexical we could update - // either update already existing lexical value - // or we create a new one on the current frame - void set_lexical(const std::string& key, T&& val); - void set_lexical(const std::string& key, const T& val); - - // look on the full stack for key - // include all scopes available - bool has(const std::string& key) const; - - // look on the full stack for key - // include all scopes available - EnvResult find(const std::string& key); - - // use array access for getter and setter functions - T& operator[](const std::string& key); - - #ifdef DEBUG - size_t print(std::string prefix = ""); - #endif - - }; - - // define typedef for our use case - typedef Environment Env; - -} - -#endif diff --git a/src/libsass/src/error_handling.cpp b/src/libsass/src/error_handling.cpp deleted file mode 100644 index 745f65508..000000000 --- a/src/libsass/src/error_handling.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "prelexer.hpp" -#include "backtrace.hpp" -#include "error_handling.hpp" - -#include - -namespace Sass { - - namespace Exception { - - Base::Base(ParserState pstate, std::string msg, Backtraces traces) - : std::runtime_error(msg), msg(msg), - prefix("Error"), pstate(pstate), traces(traces) - { } - - InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg) - : Base(pstate, msg, traces) - { } - - - InvalidParent::InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector) - : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) - { - msg = "Invalid parent selector for \""; - msg += selector->to_string(Sass_Inspect_Options()); - msg += "\": \""; - msg += parent->to_string(Sass_Inspect_Options()); - msg += "\""; - } - - InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg) - : Base(pstate, def_msg, traces), name(name), arg(arg) - { - msg = "Variable keyword argument map must have string keys.\n"; - msg += name + " is not a string in " + arg->to_string() + "."; - } - - InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value) - : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) - { - msg = arg + ": \""; - if (value) msg += value->to_string(Sass_Inspect_Options()); - msg += "\" is not a " + type; - msg += " for `" + fn + "'"; - } - - MissingArgument::MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype) - : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) - { - msg = fntype + " " + fn; - msg += " is missing argument "; - msg += arg + "."; - } - - InvalidSyntax::InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg) - : Base(pstate, msg, traces) - { } - - NestingLimitError::NestingLimitError(ParserState pstate, Backtraces traces, std::string msg) - : Base(pstate, msg, traces) - { } - - DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) - : Base(org.pstate(), def_msg, traces), dup(dup), org(org) - { - msg = "Duplicate key "; - msg += dup.get_duplicate_key()->inspect(); - msg += " in map ("; - msg += org.inspect(); - msg += ")."; - } - - TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const std::string type) - : Base(var.pstate(), def_msg, traces), var(var), type(type) - { - msg = var.to_string(); - msg += " is not an "; - msg += type; - msg += "."; - } - - InvalidValue::InvalidValue(Backtraces traces, const Expression& val) - : Base(val.pstate(), def_msg, traces), val(val) - { - msg = val.to_string(); - msg += " isn't a valid CSS value."; - } - - StackError::StackError(Backtraces traces, const AST_Node& node) - : Base(node.pstate(), def_msg, traces), node(node) - { - msg = "stack level too deep"; - } - - IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) - { - msg = "Incompatible units: '"; - msg += rhs.unit(); - msg += "' and '"; - msg += lhs.unit(); - msg += "'."; - } - - IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs) - { - msg = "Incompatible units: '"; - msg += unit_to_string(rhs); - msg += "' and '"; - msg += unit_to_string(lhs); - msg += "'."; - } - - AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) - : OperationError(), lhs(lhs), rhs(rhs), op(op) - { - msg = "Alpha channels must be equal: "; - msg += lhs->to_string({ NESTED, 5 }); - msg += " " + sass_op_to_name(op) + " "; - msg += rhs->to_string({ NESTED, 5 }); - msg += "."; - } - - ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) - : OperationError(), lhs(lhs), rhs(rhs) - { - msg = "divided by 0"; - } - - UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) - : OperationError(), lhs(lhs), rhs(rhs), op(op) - { - msg = def_op_msg + ": \""; - msg += lhs->to_string({ NESTED, 5 }); - msg += " " + sass_op_to_name(op) + " "; - msg += rhs->to_string({ TO_SASS, 5 }); - msg += "\"."; - } - - InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) - : UndefinedOperation(lhs, rhs, op) - { - msg = def_op_null_msg + ": \""; - msg += lhs->inspect(); - msg += " " + sass_op_to_name(op) + " "; - msg += rhs->inspect(); - msg += "\"."; - } - - SassValueError::SassValueError(Backtraces traces, ParserState pstate, OperationError& err) - : Base(pstate, err.what(), traces) - { - msg = err.what(); - prefix = err.errtype(); - } - - } - - - void warn(std::string msg, ParserState pstate) - { - std::cerr << "Warning: " << msg << std::endl; - } - - void warning(std::string msg, ParserState pstate) - { - std::string cwd(Sass::File::get_cwd()); - std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); - - std::cerr << "WARNING on line " << pstate.line+1 << ", column " << pstate.column+1 << " of " << output_path << ":" << std::endl; - std::cerr << msg << std::endl << std::endl; - } - - void warn(std::string msg, ParserState pstate, Backtrace* bt) - { - warn(msg, pstate); - } - - void deprecated_function(std::string msg, ParserState pstate) - { - std::string cwd(Sass::File::get_cwd()); - std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); - - std::cerr << "DEPRECATION WARNING: " << msg << std::endl; - std::cerr << "will be an error in future versions of Sass." << std::endl; - std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; - } - - void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate) - { - std::string cwd(Sass::File::get_cwd()); - std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - std::string output_path(Sass::File::path_for_console(rel_path, pstate.path, pstate.path)); - - std::cerr << "DEPRECATION WARNING on line " << pstate.line + 1; - if (with_column) std::cerr << ", column " << pstate.column + pstate.offset.column + 1; - if (output_path.length()) std::cerr << " of " << output_path; - std::cerr << ":" << std::endl; - std::cerr << msg << std::endl; - if (msg2.length()) std::cerr << msg2 << std::endl; - std::cerr << std::endl; - } - - void deprecated_bind(std::string msg, ParserState pstate) - { - std::string cwd(Sass::File::get_cwd()); - std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); - - std::cerr << "WARNING: " << msg << std::endl; - std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; - std::cerr << "This will be an error in future versions of Sass." << std::endl; - } - - // should be replaced with error with backtraces - void coreError(std::string msg, ParserState pstate) - { - Backtraces traces; - throw Exception::InvalidSyntax(pstate, traces, msg); - } - - void error(std::string msg, ParserState pstate, Backtraces& traces) - { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSyntax(pstate, traces, msg); - } - -} diff --git a/src/libsass/src/error_handling.hpp b/src/libsass/src/error_handling.hpp deleted file mode 100644 index f863792ea..000000000 --- a/src/libsass/src/error_handling.hpp +++ /dev/null @@ -1,216 +0,0 @@ -#ifndef SASS_ERROR_HANDLING_H -#define SASS_ERROR_HANDLING_H - -#include -#include -#include -#include "position.hpp" -#include "backtrace.hpp" -#include "ast_fwd_decl.hpp" -#include "sass/functions.h" - -namespace Sass { - - struct Backtrace; - - namespace Exception { - - const std::string def_msg = "Invalid sass detected"; - const std::string def_op_msg = "Undefined operation"; - const std::string def_op_null_msg = "Invalid null operation"; - const std::string def_nesting_limit = "Code too deeply neested"; - - class Base : public std::runtime_error { - protected: - std::string msg; - std::string prefix; - public: - ParserState pstate; - Backtraces traces; - public: - Base(ParserState pstate, std::string msg, Backtraces traces); - virtual const char* errtype() const { return prefix.c_str(); } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~Base() throw() {}; - }; - - class InvalidSass : public Base { - public: - InvalidSass(ParserState pstate, Backtraces traces, std::string msg); - virtual ~InvalidSass() throw() {}; - }; - - class InvalidParent : public Base { - protected: - Selector_Ptr parent; - Selector_Ptr selector; - public: - InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector); - virtual ~InvalidParent() throw() {}; - }; - - class MissingArgument : public Base { - protected: - std::string fn; - std::string arg; - std::string fntype; - public: - MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype); - virtual ~MissingArgument() throw() {}; - }; - - class InvalidArgumentType : public Base { - protected: - std::string fn; - std::string arg; - std::string type; - const Value_Ptr value; - public: - InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); - virtual ~InvalidArgumentType() throw() {}; - }; - - class InvalidVarKwdType : public Base { - protected: - std::string name; - const Argument_Ptr arg; - public: - InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg = 0); - virtual ~InvalidVarKwdType() throw() {}; - }; - - class InvalidSyntax : public Base { - public: - InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg); - virtual ~InvalidSyntax() throw() {}; - }; - - class NestingLimitError : public Base { - public: - NestingLimitError(ParserState pstate, Backtraces traces, std::string msg = def_nesting_limit); - virtual ~NestingLimitError() throw() {}; - }; - - class DuplicateKeyError : public Base { - protected: - const Map& dup; - const Expression& org; - public: - DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org); - virtual const char* errtype() const { return "Error"; } - virtual ~DuplicateKeyError() throw() {}; - }; - - class TypeMismatch : public Base { - protected: - const Expression& var; - const std::string type; - public: - TypeMismatch(Backtraces traces, const Expression& var, const std::string type); - virtual const char* errtype() const { return "Error"; } - virtual ~TypeMismatch() throw() {}; - }; - - class InvalidValue : public Base { - protected: - const Expression& val; - public: - InvalidValue(Backtraces traces, const Expression& val); - virtual const char* errtype() const { return "Error"; } - virtual ~InvalidValue() throw() {}; - }; - - class StackError : public Base { - protected: - const AST_Node& node; - public: - StackError(Backtraces traces, const AST_Node& node); - virtual const char* errtype() const { return "SystemStackError"; } - virtual ~StackError() throw() {}; - }; - - /* common virtual base class (has no pstate or trace) */ - class OperationError : public std::runtime_error { - protected: - std::string msg; - public: - OperationError(std::string msg = def_op_msg) - : std::runtime_error(msg), msg(msg) - {}; - public: - virtual const char* errtype() const { return "Error"; } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~OperationError() throw() {}; - }; - - class ZeroDivisionError : public OperationError { - protected: - const Expression& lhs; - const Expression& rhs; - public: - ZeroDivisionError(const Expression& lhs, const Expression& rhs); - virtual const char* errtype() const { return "ZeroDivisionError"; } - virtual ~ZeroDivisionError() throw() {}; - }; - - class IncompatibleUnits : public OperationError { - protected: - // const Sass::UnitType lhs; - // const Sass::UnitType rhs; - public: - IncompatibleUnits(const Units& lhs, const Units& rhs); - IncompatibleUnits(const UnitType lhs, const UnitType rhs); - virtual ~IncompatibleUnits() throw() {}; - }; - - class UndefinedOperation : public OperationError { - protected: - Expression_Ptr_Const lhs; - Expression_Ptr_Const rhs; - const Sass_OP op; - public: - UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); - // virtual const char* errtype() const { return "Error"; } - virtual ~UndefinedOperation() throw() {}; - }; - - class InvalidNullOperation : public UndefinedOperation { - public: - InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); - virtual ~InvalidNullOperation() throw() {}; - }; - - class AlphaChannelsNotEqual : public OperationError { - protected: - Expression_Ptr_Const lhs; - Expression_Ptr_Const rhs; - const Sass_OP op; - public: - AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); - // virtual const char* errtype() const { return "Error"; } - virtual ~AlphaChannelsNotEqual() throw() {}; - }; - - class SassValueError : public Base { - public: - SassValueError(Backtraces traces, ParserState pstate, OperationError& err); - virtual ~SassValueError() throw() {}; - }; - - } - - void warn(std::string msg, ParserState pstate); - void warn(std::string msg, ParserState pstate, Backtrace* bt); - void warning(std::string msg, ParserState pstate); - - void deprecated_function(std::string msg, ParserState pstate); - void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate); - void deprecated_bind(std::string msg, ParserState pstate); - // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); - - void coreError(std::string msg, ParserState pstate); - void error(std::string msg, ParserState pstate, Backtraces& traces); - -} - -#endif diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp deleted file mode 100644 index 841f7277b..000000000 --- a/src/libsass/src/eval.cpp +++ /dev/null @@ -1,1663 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include -#include - -#include "file.hpp" -#include "eval.hpp" -#include "ast.hpp" -#include "bind.hpp" -#include "util.hpp" -#include "inspect.hpp" -#include "operators.hpp" -#include "environment.hpp" -#include "position.hpp" -#include "sass/values.h" -#include "to_value.hpp" -#include "to_c.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "color_maps.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - Eval::Eval(Expand& exp) - : exp(exp), - ctx(exp.ctx), - traces(exp.traces), - force(false), - is_in_comment(false), - is_in_selector_schema(false) - { - bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); - bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); - } - Eval::~Eval() { } - - Env* Eval::environment() - { - return exp.environment(); - } - - Selector_List_Obj Eval::selector() - { - return exp.selector(); - } - - Expression_Ptr Eval::operator()(Block_Ptr b) - { - Expression_Ptr val = 0; - for (size_t i = 0, L = b->length(); i < L; ++i) { - val = b->at(i)->perform(this); - if (val) return val; - } - return val; - } - - Expression_Ptr Eval::operator()(Assignment_Ptr a) - { - Env* env = exp.environment(); - std::string var(a->variable()); - if (a->is_global()) { - if (a->is_default()) { - if (env->has_global(var)) { - Expression_Ptr e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } - else { - env->set_global(var, a->value()->perform(this)); - } - } - else { - env->set_global(var, a->value()->perform(this)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Ptr e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(this)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); - } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - Expression_Ptr e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } - } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(this)); - } - else { - env->set_local(var, a->value()->perform(this)); - } - } - else { - env->set_lexical(var, a->value()->perform(this)); - } - return 0; - } - - Expression_Ptr Eval::operator()(If_Ptr i) - { - Expression_Obj rv = 0; - Env env(exp.environment()); - exp.env_stack.push_back(&env); - Expression_Obj cond = i->predicate()->perform(this); - if (!cond->is_false()) { - rv = i->block()->perform(this); - } - else { - Block_Obj alt = i->alternative(); - if (alt) rv = alt->perform(this); - } - exp.env_stack.pop_back(); - return rv.detach(); - } - - // For does not create a new env scope - // But iteration vars are reset afterwards - Expression_Ptr Eval::operator()(For_Ptr f) - { - std::string variable(f->variable()); - Expression_Obj low = f->lower_bound()->perform(this); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); - } - Expression_Obj high = f->upper_bound()->perform(this); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); - } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - std::stringstream msg; msg << "Incompatible units: '" - << sass_end->unit() << "' and '" - << sass_start->unit() << "'."; - error(msg.str(), low->pstate(), traces); - } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - exp.env_stack.push_back(&env); - Block_Obj body = f->block(); - Expression_Ptr val = 0; - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; - } - } - exp.env_stack.pop_back(); - return val; - } - - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Expression_Ptr Eval::operator()(Each_Ptr e) - { - std::vector variables(e->variables()); - Expression_Obj expr = e->list()->perform(this); - Env env(environment(), true); - exp.env_stack.push_back(&env); - List_Obj list = 0; - Map_Ptr map = 0; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); - } - else if (Selector_List_Ptr ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); - list = Cast(rv); - } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); - } - else { - list = Cast(expr); - } - - Block_Obj body = e->block(); - Expression_Obj val = 0; - - if (map) { - for (Expression_Obj key : map->keys()) { - Expression_Obj value = map->at(key); - - if (variables.size() == 1) { - List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(key); - variable->append(value); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], key); - env.set_local(variables[1], value); - } - - val = body->perform(this); - if (val) break; - } - } - else { - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Ptr item = list->at(i); - // unwrap value if the expression is an argument - if (Argument_Ptr arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List_Ptr scalars = Cast(item)) { - if (variables.size() == 1) { - Expression_Ptr var = scalars; - env.set_local(variables[0], var); - } else { - // XXX: this is never hit via spec tests - for (size_t j = 0, K = variables.size(); j < K; ++j) { - Expression_Ptr res = j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) - : scalars->at(j); - env.set_local(variables[j], res); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - // XXX: this is never hit via spec tests - Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } - } - val = body->perform(this); - if (val) break; - } - } - exp.env_stack.pop_back(); - return val.detach(); - } - - Expression_Ptr Eval::operator()(While_Ptr w) - { - Expression_Obj pred = w->predicate(); - Block_Obj body = w->block(); - Env env(environment(), true); - exp.env_stack.push_back(&env); - Expression_Obj cond = pred->perform(this); - while (!cond->is_false()) { - Expression_Obj val = body->perform(this); - if (val) { - exp.env_stack.pop_back(); - return val.detach(); - } - cond = pred->perform(this); - } - exp.env_stack.pop_back(); - return 0; - } - - Expression_Ptr Eval::operator()(Return_Ptr r) - { - return r->value()->perform(this); - } - - Expression_Ptr Eval::operator()(Warning_Ptr w) - { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; - Expression_Obj message = w->message()->perform(this); - Env* env = exp.environment(); - - // try to use generic function - if (env->has("@warn[f]")) { - - // add call stack entry - ctx.callee_stack.push_back({ - "@warn", - w->pstate().path, - w->pstate().line + 1, - w->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition_Ptr def = Cast((*env)["@warn[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - std::string result(unquote(message->to_sass())); - std::cerr << "WARNING: " << result << std::endl; - traces.push_back(Backtrace(w->pstate())); - std::cerr << traces_to_string(traces, " "); - std::cerr << std::endl; - ctx.c_options.output_style = outstyle; - traces.pop_back(); - return 0; - } - - Expression_Ptr Eval::operator()(Error_Ptr e) - { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; - Expression_Obj message = e->message()->perform(this); - Env* env = exp.environment(); - - // try to use generic function - if (env->has("@error[f]")) { - - // add call stack entry - ctx.callee_stack.push_back({ - "@error", - e->pstate().path, - e->pstate().line + 1, - e->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition_Ptr def = Cast((*env)["@error[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - std::string result(unquote(message->to_sass())); - ctx.c_options.output_style = outstyle; - error(result, e->pstate(), traces); - return 0; - } - - Expression_Ptr Eval::operator()(Debug_Ptr d) - { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; - Expression_Obj message = d->value()->perform(this); - Env* env = exp.environment(); - - // try to use generic function - if (env->has("@debug[f]")) { - - // add call stack entry - ctx.callee_stack.push_back({ - "@debug", - d->pstate().path, - d->pstate().line + 1, - d->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition_Ptr def = Cast((*env)["@debug[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - std::string cwd(ctx.cwd()); - std::string result(unquote(message->to_sass())); - std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); - std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); - ctx.c_options.output_style = outstyle; - - std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; - std::cerr << std::endl; - return 0; - } - - Expression_Ptr Eval::operator()(List_Ptr l) - { - // special case for unevaluated map - if (l->separator() == SASS_HASH) { - Map_Obj lm = SASS_MEMORY_NEW(Map, - l->pstate(), - l->length() / 2); - for (size_t i = 0, L = l->length(); i < L; i += 2) - { - Expression_Obj key = (*l)[i+0]->perform(this); - Expression_Obj val = (*l)[i+1]->perform(this); - // make sure the color key never displays its real name - key->is_delayed(true); // verified - *lm << std::make_pair(key, val); - } - if (lm->has_duplicate_key()) { - traces.push_back(Backtrace(l->pstate())); - throw Exception::DuplicateKeyError(traces, *lm, *l); - } - - lm->is_interpolant(l->is_interpolant()); - return lm->perform(this); - } - // check if we should expand it - if (l->is_expanded()) return l; - // regular case for unevaluated lists - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - ll->is_interpolant(l->is_interpolant()); - ll->from_selector(l->from_selector()); - ll->is_expanded(true); - return ll.detach(); - } - - Expression_Ptr Eval::operator()(Map_Ptr m) - { - if (m->is_expanded()) return m; - - // make sure we're not starting with duplicate keys. - // the duplicate key state will have been set in the parser phase. - if (m->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *m, *m); - } - - Map_Obj mm = SASS_MEMORY_NEW(Map, - m->pstate(), - m->length()); - for (auto key : m->keys()) { - Expression_Ptr ex_key = key->perform(this); - Expression_Ptr ex_val = m->at(key); - if (ex_val == NULL) continue; - ex_val = ex_val->perform(this); - *mm << std::make_pair(ex_key, ex_val); - } - - // check the evaluated keys aren't duplicates. - if (mm->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *mm, *m); - } - - mm->is_expanded(true); - return mm.detach(); - } - - Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) - { - - Expression_Obj lhs = b_in->left(); - Expression_Obj rhs = b_in->right(); - enum Sass_OP op_type = b_in->optype(); - - if (op_type == Sass_OP::AND) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (!*lhs) return lhs.detach(); - return rhs->perform(this); - } - else if (op_type == Sass_OP::OR) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (*lhs) return lhs.detach(); - return rhs->perform(this); - } - - // Evaluate variables as early o - while (Variable_Ptr l_v = Cast(lhs)) { - lhs = operator()(l_v); - } - while (Variable_Ptr r_v = Cast(rhs)) { - rhs = operator()(r_v); - } - - Binary_Expression_Obj b = b_in; - - // Evaluate sub-expressions early on - while (Binary_Expression_Ptr l_b = Cast(lhs)) { - if (!force && l_b->is_delayed()) break; - lhs = operator()(l_b); - } - while (Binary_Expression_Ptr r_b = Cast(rhs)) { - if (!force && r_b->is_delayed()) break; - rhs = operator()(r_b); - } - - // don't eval delayed expressions (the '/' when used as a separator) - if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { - b->right(b->right()->perform(this)); - b->left(b->left()->perform(this)); - return b.detach(); - } - - // specific types we know are final - // handle them early to avoid overhead - if (Number_Ptr l_n = Cast(lhs)) { - // lhs is number and rhs is number - if (Number_Ptr r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; - case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; - case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - // lhs is number and rhs is color - else if (Color_Ptr r_c = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - } - else if (Color_Ptr l_c = Cast(lhs)) { - // lhs is color and rhs is color - if (Color_Ptr r_c = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; - case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; - case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - // lhs is color and rhs is number - else if (Number_Ptr r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } - } - } - - String_Schema_Obj ret_schema; - - // only the last item will be used to eval the binary expression - if (String_Schema_Ptr s_l = Cast(b->left())) { - if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), s_l->last(), b->right()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified - for (size_t i = 0; i < s_l->length() - 1; ++i) { - ret_schema->append(s_l->at(i)->perform(this)); - } - ret_schema->append(bin_ex->perform(this)); - return ret_schema->perform(this); - } - } - if (String_Schema_Ptr s_r = Cast(b->right())) { - - if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), b->left(), s_r->first()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified - ret_schema->append(bin_ex->perform(this)); - for (size_t i = 1; i < s_r->length(); ++i) { - ret_schema->append(s_r->at(i)->perform(this)); - } - return ret_schema->perform(this); - } - } - - // fully evaluate their values - if (op_type == Sass_OP::EQ || - op_type == Sass_OP::NEQ || - op_type == Sass_OP::GT || - op_type == Sass_OP::GTE || - op_type == Sass_OP::LT || - op_type == Sass_OP::LTE) - { - LOCAL_FLAG(force, true); - lhs->is_expanded(false); - lhs->set_delayed(false); - lhs = lhs->perform(this); - rhs->is_expanded(false); - rhs->set_delayed(false); - rhs = rhs->perform(this); - } - else { - lhs = lhs->perform(this); - } - - // not a logical connective, so go ahead and eval the rhs - rhs = rhs->perform(this); - AST_Node_Obj lu = lhs; - AST_Node_Obj ru = rhs; - - Expression::Concrete_Type l_type; - Expression::Concrete_Type r_type; - - // Is one of the operands an interpolant? - String_Schema_Obj s1 = Cast(b->left()); - String_Schema_Obj s2 = Cast(b->right()); - Binary_Expression_Obj b1 = Cast(b->left()); - Binary_Expression_Obj b2 = Cast(b->right()); - - bool schema_op = false; - - bool force_delay = (s2 && s2->is_left_interpolant()) || - (s1 && s1->is_right_interpolant()) || - (b1 && b1->is_right_interpolant()) || - (b2 && b2->is_left_interpolant()); - - if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) - { - if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || - op_type == Sass_OP::EQ) { - // If possible upgrade LHS to a number (for number to string compare) - if (String_Constant_Ptr str = Cast(lhs)) { - std::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { - lhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - // If possible upgrade RHS to a number (for string to number compare) - if (String_Constant_Ptr str = Cast(rhs)) { - std::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { - rhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - } - - To_Value to_value(ctx); - Value_Obj v_l = Cast(lhs->perform(&to_value)); - Value_Obj v_r = Cast(rhs->perform(&to_value)); - - if (force_delay) { - std::string str(""); - str += v_l->to_string(ctx.c_options); - if (b->op().ws_before) str += " "; - str += b->separator(); - if (b->op().ws_after) str += " "; - str += v_r->to_string(ctx.c_options); - String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); - val->is_interpolant(b->left()->has_interpolant()); - return val; - } - } - - // see if it's a relational expression - try { - switch(op_type) { - case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); - case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); - case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); - default: break; - } - } - catch (Exception::OperationError& err) - { - // throw Exception::Base(b->pstate(), err.what()); - traces.push_back(Backtrace(b->pstate())); - throw Exception::SassValueError(traces, b->pstate(), err); - } - - l_type = lhs->concrete_type(); - r_type = rhs->concrete_type(); - - // ToDo: throw error in op functions - // ToDo: then catch and re-throw them - Expression_Obj rv; - try { - ParserState pstate(b->pstate()); - if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { - Number_Ptr l_n = Cast(lhs); - Number_Ptr r_n = Cast(rhs); - l_n->reduce(); r_n->reduce(); - rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); - } - else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { - Number_Ptr l_n = Cast(lhs); - Color_Ptr r_c = Cast(rhs); - rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { - Color_Ptr l_c = Cast(lhs); - Number_Ptr r_n = Cast(rhs); - rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { - Color_Ptr l_c = Cast(lhs); - Color_Ptr r_c = Cast(rhs); - rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); - } - else { - To_Value to_value(ctx); - // this will leak if perform does not return a value! - Value_Obj v_l = Cast(lhs->perform(&to_value)); - Value_Obj v_r = Cast(rhs->perform(&to_value)); - bool interpolant = b->is_right_interpolant() || - b->is_left_interpolant() || - b->is_interpolant(); - if (op_type == Sass_OP::SUB) interpolant = false; - // if (op_type == Sass_OP::DIV) interpolant = true; - // check for type violations - if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_l->pstate())); - throw Exception::InvalidValue(traces, *v_l); - } - if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_r->pstate())); - throw Exception::InvalidValue(traces, *v_r); - } - Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress - if (String_Constant_Ptr str = Cast(ex)) - { - if (str->concrete_type() == Expression::STRING) - { - String_Constant_Ptr lstr = Cast(lhs); - String_Constant_Ptr rstr = Cast(rhs); - if (op_type != Sass_OP::SUB) { - if (String_Constant_Ptr org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } - } - } - } - ex->is_interpolant(b->is_interpolant()); - rv = ex; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b->pstate())); - // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(traces, b->pstate(), err); - } - - if (rv) { - if (schema_op) { - // XXX: this is never hit via spec tests - (*s2)[0] = rv; - rv = s2->perform(this); - } - } - - return rv.detach(); - - } - - Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) - { - Expression_Obj operand = u->operand()->perform(this); - if (u->optype() == Unary_Expression::NOT) { - Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); - result->value(!result->value()); - return result; - } - else if (Number_Obj nr = Cast(operand)) { - // negate value for minus unary expression - if (u->optype() == Unary_Expression::MINUS) { - Number_Obj cpy = SASS_MEMORY_COPY(nr); - cpy->value( - cpy->value() ); // negate value - return cpy.detach(); // return the copy - } - else if (u->optype() == Unary_Expression::SLASH) { - std::string str = '/' + nr->to_string(ctx.c_options); - return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); - } - // nothing for positive - return nr.detach(); - } - else { - // Special cases: +/- variables which evaluate to null ouput just +/-, - // but +/- null itself outputs the string - if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { - u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); - } - // Never apply unary opertions on colors @see #2140 - else if (Color_Ptr color = Cast(operand)) { - // Use the color name if this was eval with one - if (color->disp().length() > 0) { - operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); - u->operand(operand); - } - } - else { - u->operand(operand); - } - - return SASS_MEMORY_NEW(String_Quoted, - u->pstate(), - u->inspect()); - } - // unreachable - return u; - } - - Expression_Ptr Eval::operator()(Function_Call_Ptr c) - { - if (traces.size() > Constants::MaxCallStack) { - // XXX: this is never hit via spec tests - std::ostringstream stm; - stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str(), c->pstate(), traces); - } - std::string name(Util::normalize_underscores(c->name())); - std::string full_name(name + "[f]"); - // we make a clone here, need to implement that further - Arguments_Obj args = c->arguments(); - - Env* env = environment(); - if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { - if (!env->has("*[f]")) { - for (Argument_Obj arg : args->elements()) { - if (List_Obj ls = Cast(arg->value())) { - if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); - } - } - args = Cast(args->perform(this)); - Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, - c->pstate(), - c->name(), - args); - if (args->has_named_arguments()) { - error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); - } - String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, - c->pstate(), - lit->to_string(ctx.c_options)); - str->is_interpolant(c->is_interpolant()); - return str; - } else { - // call generic function - full_name = "*[f]"; - } - } - - // further delay for calls - if (full_name != "call[f]") { - args->set_delayed(false); // verified - } - if (full_name != "if[f]") { - args = Cast(args->perform(this)); - } - Definition_Ptr def = Cast((*env)[full_name]); - - if (c->func()) def = c->func()->definition(); - - if (def->is_overload_stub()) { - std::stringstream ss; - size_t L = args->length(); - // account for rest arguments - if (args->has_rest_argument() && args->length() > 0) { - // get the rest arguments list - List_Ptr rest = Cast(args->last()->value()); - // arguments before rest argument plus rest - if (rest) L += rest->length() - 1; - } - ss << full_name << L; - full_name = ss.str(); - std::string resolved_name(full_name); - if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); - def = Cast((*env)[resolved_name]); - } - - Expression_Obj result = c; - Block_Obj body = def->block(); - Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - - if (c->is_css()) return result.detach(); - - Parameters_Obj params = def->parameters(); - Env fn_env(def->environment()); - exp.env_stack.push_back(&fn_env); - - if (func || body) { - bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - std::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().path, - c->pstate().line + 1, - c->pstate().column + 1, - SASS_CALLEE_FUNCTION, - { env } - }); - - // eval the body if user-defined or special, invoke underlying CPP function if native - if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { - result = body->perform(this); - } - else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); - } - if (!result) { - error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); - } - ctx.callee_stack.pop_back(); - traces.pop_back(); - } - - // else if it's a user-defined c function - // convert call into C-API compatible form - else if (c_function) { - Sass_Function_Fn c_func = sass_function_get_function(c_function); - if (full_name == "*[f]") { - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); - Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); - new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); - new_args->concat(args); - args = new_args; - } - - // populates env with default values for params - std::string ff(c->name()); - bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - std::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().path, - c->pstate().line + 1, - c->pstate().column + 1, - SASS_CALLEE_C_FUNCTION, - { env } - }); - - To_C to_c; - union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); - for(size_t i = 0; i < params->length(); i++) { - Parameter_Obj param = params->at(i); - std::string key = param->name(); - AST_Node_Obj node = fn_env.get_local(key); - Expression_Obj arg = Cast(node); - sass_list_set_value(c_args, i, arg->perform(&to_c)); - } - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - if (sass_value_get_tag(c_val) == SASS_ERROR) { - error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); - } else if (sass_value_get_tag(c_val) == SASS_WARNING) { - error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); - } - result = cval_to_astnode(c_val, traces, c->pstate()); - - ctx.callee_stack.pop_back(); - traces.pop_back(); - sass_delete_value(c_args); - if (c_val != c_args) - sass_delete_value(c_val); - } - - // link back to function definition - // only do this for custom functions - if (result->pstate().file == std::string::npos) - result->pstate(c->pstate()); - - result = result->perform(this); - result->is_interpolant(c->is_interpolant()); - exp.env_stack.pop_back(); - return result.detach(); - } - - Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) - { - Expression_Ptr evaluated_name = s->name()->perform(this); - Expression_Ptr evaluated_args = s->arguments()->perform(this); - String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); - ss->append(evaluated_name); - ss->append(evaluated_args); - return ss->perform(this); - } - - Expression_Ptr Eval::operator()(Variable_Ptr v) - { - Expression_Obj value = 0; - Env* env = environment(); - const std::string& name(v->name()); - EnvResult rv(env->find(name)); - if (rv.found) value = static_cast(rv.it->second.ptr()); - else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); - if (Argument_Ptr arg = Cast(value)) value = arg->value(); - if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag - value->is_interpolant(v->is_interpolant()); - if (force) value->is_expanded(false); - value->set_delayed(false); // verified - value = value->perform(this); - if(!force) rv.it->second = value; - return value.detach(); - } - - Expression_Ptr Eval::operator()(Color_Ptr c) - { - return c; - } - - Expression_Ptr Eval::operator()(Number_Ptr n) - { - return n; - } - - Expression_Ptr Eval::operator()(Boolean_Ptr b) - { - return b; - } - - void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { - - bool needs_closing_brace = false; - - if (Arguments_Ptr args = Cast(ex)) { - List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); - for(auto arg : args->elements()) { - ll->append(arg->value()); - } - ll->is_interpolant(args->is_interpolant()); - needs_closing_brace = true; - res += "("; - ex = ll; - } - if (Number_Ptr nr = Cast(ex)) { - Number reduced(nr); - reduced.reduce(); - if (!reduced.is_valid_css_unit()) { - traces.push_back(Backtrace(nr->pstate())); - throw Exception::InvalidValue(traces, *nr); - } - } - if (Argument_Ptr arg = Cast(ex)) { - ex = arg->value(); - } - if (String_Quoted_Ptr sq = Cast(ex)) { - if (was_itpl) { - bool was_interpolant = ex->is_interpolant(); - ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); - ex->is_interpolant(was_interpolant); - } - } - - if (Cast(ex)) { return; } - - // parent selector needs another go - if (Cast(ex)) { - // XXX: this is never hit via spec tests - ex = ex->perform(this); - } - - if (List_Ptr l = Cast(ex)) { - List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); - // this fixes an issue with bourbon sample, not really sure why - // if (l->size() && Cast((*l)[0])) { res += ""; } - for(Expression_Obj item : *l) { - item->is_interpolant(l->is_interpolant()); - std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); - bool is_null = Cast(item) != 0; // rl != "" - if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); - } - // Check indicates that we probably should not get a list - // here. Normally single list items are already unwrapped. - if (l->size() > 1) { - // string_to_output would fail "#{'_\a' '_\a'}"; - std::string str(ll->to_string(ctx.c_options)); - str = read_hex_escapes(str); // read escapes - newline_to_space(str); // replace directly - res += str; // append to result string - } else { - res += (ll->to_string(ctx.c_options)); - } - ll->is_interpolant(l->is_interpolant()); - } - - // Value - // Function_Call - // Selector_List - // String_Quoted - // String_Constant - // Parent_Selector - // Binary_Expression - else { - // ex = ex->perform(this); - if (into_quotes && ex->is_interpolant()) { - res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); - } else { - std::string str(ex ? ex->to_string(ctx.c_options) : ""); - if (into_quotes) str = read_hex_escapes(str); - res += str; // append to result string - } - } - - if (needs_closing_brace) res += ")"; - - } - - Expression_Ptr Eval::operator()(String_Schema_Ptr s) - { - size_t L = s->length(); - bool into_quotes = false; - if (L > 1) { - if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { - if (String_Constant_Ptr l = Cast((*s)[0])) { - if (String_Constant_Ptr r = Cast((*s)[L - 1])) { - if (r->value().size() > 0) { - if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; - if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; - } - } - } - } - } - bool was_quoted = false; - bool was_interpolant = false; - std::string res(""); - for (size_t i = 0; i < L; ++i) { - bool is_quoted = Cast((*s)[i]) != NULL; - if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - Expression_Obj ex = (*s)[i]->perform(this); - interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); - was_quoted = Cast((*s)[i]) != NULL; - was_interpolant = (*s)[i]->is_interpolant(); - - } - if (!s->is_interpolant()) { - if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); - return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); - } - // string schema seems to have a special unquoting behavior (also handles "nested" quotes) - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); - // if (s->is_interpolant()) str->quote_mark(0); - // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); - if (str->quote_mark()) str->quote_mark('*'); - else if (!is_in_comment) str->value(string_to_output(str->value())); - str->is_interpolant(s->is_interpolant()); - return str.detach(); - } - - - Expression_Ptr Eval::operator()(String_Constant_Ptr s) - { - return s; - } - - Expression_Ptr Eval::operator()(String_Quoted_Ptr s) - { - String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); - str->value(s->value()); - str->quote_mark(s->quote_mark()); - str->is_interpolant(s->is_interpolant()); - return str; - } - - Expression_Ptr Eval::operator()(Supports_Operator_Ptr c) - { - Expression_Ptr left = c->left()->perform(this); - Expression_Ptr right = c->right()->perform(this); - Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, - c->pstate(), - Cast(left), - Cast(right), - c->operand()); - return cc; - } - - Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) - { - Expression_Ptr condition = c->condition()->perform(this); - Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, - c->pstate(), - Cast(condition)); - return cc; - } - - Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) - { - Expression_Ptr feature = c->feature()->perform(this); - Expression_Ptr value = c->value()->perform(this); - Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, - c->pstate(), - feature, - value); - return cc; - } - - Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) - { - Expression_Ptr value = c->value()->perform(this); - Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, - c->pstate(), - value); - return cc; - } - - Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) - { - Expression_Obj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - Expression_Obj value = e->value(); - value = (value ? value->perform(this) : 0); - Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, - e->pstate(), - Cast(feature), - value); - return ee; - } - - Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) - { - String_Obj t = q->media_type(); - t = static_cast(t.isNull() ? 0 : t->perform(this)); - Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, - q->pstate(), - t, - q->length(), - q->is_negated(), - q->is_restricted()); - for (size_t i = 0, L = q->length(); i < L; ++i) { - qq->append(static_cast((*q)[i]->perform(this))); - } - return qq.detach(); - } - - Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) - { - Expression_Obj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - if (feature && Cast(feature)) { - feature = SASS_MEMORY_NEW(String_Quoted, - feature->pstate(), - Cast(feature)->value()); - } - Expression_Obj value = e->value(); - value = (value ? value->perform(this) : 0); - if (value && Cast(value)) { - // XXX: this is never hit via spec tests - value = SASS_MEMORY_NEW(String_Quoted, - value->pstate(), - Cast(value)->value()); - } - return SASS_MEMORY_NEW(Media_Query_Expression, - e->pstate(), - feature, - value, - e->is_interpolated()); - } - - Expression_Ptr Eval::operator()(Null_Ptr n) - { - return n; - } - - Expression_Ptr Eval::operator()(Argument_Ptr a) - { - Expression_Obj val = a->value()->perform(this); - bool is_rest_argument = a->is_rest_argument(); - bool is_keyword_argument = a->is_keyword_argument(); - - if (a->is_rest_argument()) { - if (val->concrete_type() == Expression::MAP) { - is_rest_argument = false; - is_keyword_argument = true; - } - else if(val->concrete_type() != Expression::LIST) { - List_Obj wrapper = SASS_MEMORY_NEW(List, - val->pstate(), - 0, - SASS_COMMA, - true); - wrapper->append(val); - val = wrapper; - } - } - return SASS_MEMORY_NEW(Argument, - a->pstate(), - val, - a->name(), - is_rest_argument, - is_keyword_argument); - } - - Expression_Ptr Eval::operator()(Arguments_Ptr a) - { - Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); - if (a->length() == 0) return aa.detach(); - for (size_t i = 0, L = a->length(); i < L; ++i) { - Expression_Obj rv = (*a)[i]->perform(this); - Argument_Ptr arg = Cast(rv); - if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { - aa->append(arg); - } - } - - if (a->has_rest_argument()) { - Expression_Obj rest = a->get_rest_argument()->perform(this); - Expression_Obj splat = Cast(rest)->value()->perform(this); - - Sass_Separator separator = SASS_COMMA; - List_Ptr ls = Cast(splat); - Map_Ptr ms = Cast(splat); - - List_Obj arglist = SASS_MEMORY_NEW(List, - splat->pstate(), - 0, - ls ? ls->separator() : separator, - true); - - if (ls && ls->is_arglist()) { - arglist->concat(ls); - } else if (ms) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); - } else if (ls) { - arglist->concat(ls); - } else { - arglist->append(splat); - } - if (arglist->length()) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); - } - } - - if (a->has_keyword_argument()) { - Expression_Obj rv = a->get_keyword_argument()->perform(this); - Argument_Ptr rvarg = Cast(rv); - Expression_Obj kwarg = rvarg->value()->perform(this); - - aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); - } - return aa.detach(); - } - - Expression_Ptr Eval::operator()(Comment_Ptr c) - { - return 0; - } - - inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) - { - return static_cast(n); - } - - // All the binary helpers. - - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) - { - using std::strlen; - using std::strcpy; - Expression_Ptr e = NULL; - switch (sass_value_get_tag(v)) { - case SASS_BOOLEAN: { - e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); - } break; - case SASS_NUMBER: { - e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); - } break; - case SASS_COLOR: { - e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); - } break; - case SASS_STRING: { - if (sass_string_is_quoted(v)) - e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); - else { - e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); - } - } break; - case SASS_LIST: { - List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); - for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { - l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); - } - l->is_bracketed(sass_list_get_is_bracketed(v)); - e = l; - } break; - case SASS_MAP: { - Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); - for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { - *m << std::make_pair( - cval_to_astnode(sass_map_get_key(v, i), traces, pstate), - cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); - } - e = m; - } break; - case SASS_NULL: { - e = SASS_MEMORY_NEW(Null, pstate); - } break; - case SASS_ERROR: { - error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); - } break; - case SASS_WARNING: { - error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); - } break; - default: break; - } - return e; - } - - Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) - { - std::vector rv; - Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); - sl->is_optional(s->is_optional()); - sl->media_block(s->media_block()); - sl->is_optional(s->is_optional()); - for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()((*s)[i])); - } - - // we should actually permutate parent first - // but here we have permutated the selector first - size_t round = 0; - while (round != std::string::npos) { - bool abort = true; - for (size_t i = 0, iL = rv.size(); i < iL; ++i) { - if (rv[i]->length() > round) { - sl->append((*rv[i])[round]); - abort = false; - } - } - if (abort) { - round = std::string::npos; - } else { - ++ round; - } - - } - return sl.detach(); - } - - - Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) - { - bool implicit_parent = !exp.old_at_root_without_rule; - if (is_in_selector_schema) exp.selector_stack.push_back(0); - Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); - if (is_in_selector_schema) exp.selector_stack.pop_back(); - for (size_t i = 0; i < resolved->length(); i++) { - Complex_Selector_Ptr is = resolved->at(i)->first(); - while (is) { - if (is->head()) { - is->head(operator()(is->head())); - } - is = is->tail(); - } - } - return resolved.detach(); - } - - Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) - { - for (size_t i = 0; i < s->length(); i++) { - Simple_Selector_Ptr ss = s->at(i); - // skip parents here (called via resolve_parent_refs) - if (ss == NULL || Cast(ss)) continue; - s->at(i) = Cast(ss->perform(this)); - } - return s; - } - - Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) - { - LOCAL_FLAG(is_in_selector_schema, true); - // the parser will look for a brace to end the selector - ctx.c_options.in_selector = true; // do not compress colors - Expression_Obj sel = s->contents()->perform(this); - std::string result_str(sel->to_string(ctx.c_options)); - ctx.c_options.in_selector = false; // flag temporary only - result_str = unquote(Util::rtrim(result_str)); - char* temp_cstr = sass_copy_c_string(result_str.c_str()); - ctx.strings.push_back(temp_cstr); // attach to context - Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); - p.last_media_block = s->media_block(); - // a selector schema may or may not connect to parent? - bool chroot = s->connect_parent() == false; - Selector_List_Obj sl = p.parse_selector_list(chroot); - auto vec_str_rend = ctx.strings.rend(); - auto vec_str_rbegin = ctx.strings.rbegin(); - // remove the first item searching from the back - // we cannot assume our item is still the last one - // order is not important, so we can optimize this - auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); - // undefined behavior if not found! - if (it != vec_str_rend) { - // overwrite with last item - *it = ctx.strings.back(); - // remove last one from vector - ctx.strings.pop_back(); - // free temporary copy - free(temp_cstr); - } - flag_is_in_selector_schema.reset(); - return operator()(sl); - } - - Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) - { - if (Selector_List_Obj pr = selector()) { - exp.selector_stack.pop_back(); - Selector_List_Obj rv = operator()(pr); - exp.selector_stack.push_back(rv); - return rv.detach(); - } else { - return SASS_MEMORY_NEW(Null, p->pstate()); - } - } - - Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) - { - return s; - } - - // hotfix to avoid invalid nested `:not` selectors - // probably the wrong place, but this should ultimately - // be fixed by implement superselector correctly for `:not` - // first use of "find" (ATM only implemented for selectors) - bool hasNotSelector(AST_Node_Obj obj) { - if (Wrapped_Selector_Ptr w = Cast(obj)) { - return w->name() == ":not"; - } - return false; - } - - Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) - { - - if (s->name() == ":not") { - if (exp.selector_stack.back()) { - if (s->selector()->find(hasNotSelector)) { - s->selector()->clear(); - s->name(" "); - } else if (s->selector()->length() == 1) { - Complex_Selector_Ptr cs = s->selector()->at(0); - if (cs->tail()) { - s->selector()->clear(); - s->name(" "); - } - } else if (s->selector()->length() > 1) { - s->selector()->clear(); - s->name(" "); - } - } - } - return s; - }; - -} diff --git a/src/libsass/src/eval.hpp b/src/libsass/src/eval.hpp deleted file mode 100644 index aeaada87e..000000000 --- a/src/libsass/src/eval.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef SASS_EVAL_H -#define SASS_EVAL_H - -#include "ast.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "operation.hpp" -#include "environment.hpp" - -namespace Sass { - - class Expand; - class Context; - - class Eval : public Operation_CRTP { - - private: - Expression_Ptr fallback_impl(AST_Node_Ptr n); - - public: - Expand& exp; - Context& ctx; - Backtraces& traces; - Eval(Expand& exp); - ~Eval(); - - bool force; - bool is_in_comment; - bool is_in_selector_schema; - - Boolean_Obj bool_true; - Boolean_Obj bool_false; - - Env* environment(); - Selector_List_Obj selector(); - - // for evaluating function bodies - Expression_Ptr operator()(Block_Ptr); - Expression_Ptr operator()(Assignment_Ptr); - Expression_Ptr operator()(If_Ptr); - Expression_Ptr operator()(For_Ptr); - Expression_Ptr operator()(Each_Ptr); - Expression_Ptr operator()(While_Ptr); - Expression_Ptr operator()(Return_Ptr); - Expression_Ptr operator()(Warning_Ptr); - Expression_Ptr operator()(Error_Ptr); - Expression_Ptr operator()(Debug_Ptr); - - Expression_Ptr operator()(List_Ptr); - Expression_Ptr operator()(Map_Ptr); - Expression_Ptr operator()(Binary_Expression_Ptr); - Expression_Ptr operator()(Unary_Expression_Ptr); - Expression_Ptr operator()(Function_Call_Ptr); - Expression_Ptr operator()(Function_Call_Schema_Ptr); - Expression_Ptr operator()(Variable_Ptr); - Expression_Ptr operator()(Number_Ptr); - Expression_Ptr operator()(Color_Ptr); - Expression_Ptr operator()(Boolean_Ptr); - Expression_Ptr operator()(String_Schema_Ptr); - Expression_Ptr operator()(String_Quoted_Ptr); - Expression_Ptr operator()(String_Constant_Ptr); - // Expression_Ptr operator()(Selector_List_Ptr); - Media_Query_Ptr operator()(Media_Query_Ptr); - Expression_Ptr operator()(Media_Query_Expression_Ptr); - Expression_Ptr operator()(At_Root_Query_Ptr); - Expression_Ptr operator()(Supports_Operator_Ptr); - Expression_Ptr operator()(Supports_Negation_Ptr); - Expression_Ptr operator()(Supports_Declaration_Ptr); - Expression_Ptr operator()(Supports_Interpolation_Ptr); - Expression_Ptr operator()(Null_Ptr); - Expression_Ptr operator()(Argument_Ptr); - Expression_Ptr operator()(Arguments_Ptr); - Expression_Ptr operator()(Comment_Ptr); - - // these will return selectors - Selector_List_Ptr operator()(Selector_List_Ptr); - Selector_List_Ptr operator()(Complex_Selector_Ptr); - Compound_Selector_Ptr operator()(Compound_Selector_Ptr); - Simple_Selector_Ptr operator()(Simple_Selector_Ptr s); - Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s); - // they don't have any specific implementation (yet) - // Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; }; - // Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; }; - // Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; }; - // Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; }; - // Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; }; - // actual evaluated selectors - Selector_List_Ptr operator()(Selector_Schema_Ptr); - Expression_Ptr operator()(Parent_Selector_Ptr); - - template - Expression_Ptr fallback(U x) { return fallback_impl(x); } - - private: - void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); - - }; - - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate = ParserState("[AST]")); - -} - -#endif diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp deleted file mode 100644 index d8dc03f14..000000000 --- a/src/libsass/src/expand.cpp +++ /dev/null @@ -1,817 +0,0 @@ -#include "sass.hpp" -#include -#include - -#include "ast.hpp" -#include "expand.hpp" -#include "bind.hpp" -#include "eval.hpp" -#include "backtrace.hpp" -#include "context.hpp" -#include "parser.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - // simple endless recursion protection - const size_t maxRecursion = 500; - - Expand::Expand(Context& ctx, Env* env, std::vector* stack) - : ctx(ctx), - traces(ctx.traces), - eval(Eval(*this)), - recursions(0), - in_keyframes(false), - at_root_without_rule(false), - old_at_root_without_rule(false), - env_stack(std::vector()), - block_stack(std::vector()), - call_stack(std::vector()), - selector_stack(std::vector()), - media_block_stack(std::vector()) - { - env_stack.push_back(0); - env_stack.push_back(env); - block_stack.push_back(0); - call_stack.push_back(0); - if (stack == NULL) { selector_stack.push_back(0); } - else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } - media_block_stack.push_back(0); - } - - Env* Expand::environment() - { - if (env_stack.size() > 0) - return env_stack.back(); - return 0; - } - - Selector_List_Obj Expand::selector() - { - if (selector_stack.size() > 0) - return selector_stack.back(); - return 0; - } - - // blocks create new variable scopes - Block_Ptr Expand::operator()(Block_Ptr b) - { - // create new local environment - // set the current env as parent - Env env(environment()); - // copy the block object (add items later) - Block_Obj bb = SASS_MEMORY_NEW(Block, - b->pstate(), - b->length(), - b->is_root()); - // setup block and env stack - this->block_stack.push_back(bb); - this->env_stack.push_back(&env); - // operate on block - // this may throw up! - this->append_block(b); - // revert block and env stack - this->block_stack.pop_back(); - this->env_stack.pop_back(); - // return copy - return bb.detach(); - } - - Statement_Ptr Expand::operator()(Ruleset_Ptr r) - { - LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); - - if (in_keyframes) { - Block_Ptr bb = operator()(r->block()); - Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); - if (r->selector()) { - if (Selector_List_Ptr s = r->selector()) { - selector_stack.push_back(0); - k->name(s->eval(eval)); - selector_stack.pop_back(); - } - } - return k.detach(); - } - - // reset when leaving scope - LOCAL_FLAG(at_root_without_rule, false); - - // `&` is allowed in `@at-root`! - bool has_parent_selector = false; - for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { - Selector_List_Obj ll = selector_stack.at(i); - has_parent_selector = ll != 0 && ll->length() > 0; - } - - Selector_List_Obj sel = r->selector(); - if (sel) sel = sel->eval(eval); - - // check for parent selectors in base level rules - if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { - if (Selector_List_Ptr selector_list = Cast(r->selector())) { - for (Complex_Selector_Obj complex_selector : selector_list->elements()) { - Complex_Selector_Ptr tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - Parent_Selector_Ptr ptr = Cast(header); - if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); - } - tail = tail->tail(); - } - } - } - } - else { - if (sel->length() == 0 || sel->has_parent_ref()) { - if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); - } - } - } - - // do not connect parent again - sel->remove_parent_selectors(); - selector_stack.push_back(sel); - Env env(environment()); - if (block_stack.back()->is_root()) { - env_stack.push_back(&env); - } - sel->set_media_block(media_block_stack.back()); - Block_Obj blk = 0; - if (r->block()) blk = operator()(r->block()); - Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, - r->pstate(), - sel, - blk); - selector_stack.pop_back(); - if (block_stack.back()->is_root()) { - env_stack.pop_back(); - } - - rr->is_root(r->is_root()); - rr->tabs(r->tabs()); - - return rr; - } - - Statement_Ptr Expand::operator()(Supports_Block_Ptr f) - { - Expression_Obj condition = f->condition()->perform(&eval); - Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, - f->pstate(), - Cast(condition), - operator()(f->block())); - return ff.detach(); - } - - Statement_Ptr Expand::operator()(Media_Block_Ptr m) - { - Media_Block_Obj cpy = SASS_MEMORY_COPY(m); - // Media_Blocks are prone to have circular references - // Copy could leak memory if it does not get picked up - // Looks like we are able to reset block reference for copy - // Good as it will ensure a low memory overhead for this fix - // So this is a cheap solution with a minimal price - ctx.ast_gc.push_back(cpy); cpy->block(0); - Expression_Obj mq = eval(m->media_queries()); - std::string str_mq(mq->to_string(ctx.c_options)); - char* str = sass_copy_c_string(str_mq.c_str()); - ctx.strings.push_back(str); - Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); - mq = p.parse_media_queries(); // re-assign now - cpy->media_queries(mq); - media_block_stack.push_back(cpy); - Block_Obj blk = operator()(m->block()); - Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - mq, - blk); - media_block_stack.pop_back(); - mm->tabs(m->tabs()); - return mm; - } - - Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) - { - Block_Obj ab = a->block(); - Expression_Obj ae = a->expression(); - - if (ae) ae = ae->perform(&eval); - else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); - - LOCAL_FLAG(at_root_without_rule, true); - LOCAL_FLAG(in_keyframes, false); - - ; - - Block_Obj bb = ab ? operator()(ab) : NULL; - At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, - a->pstate(), - bb, - Cast(ae)); - return aa.detach(); - } - - Statement_Ptr Expand::operator()(Directive_Ptr a) - { - LOCAL_FLAG(in_keyframes, a->is_keyframes()); - Block_Ptr ab = a->block(); - Selector_List_Ptr as = a->selector(); - Expression_Ptr av = a->value(); - selector_stack.push_back(0); - if (av) av = av->perform(&eval); - if (as) as = eval(as); - selector_stack.pop_back(); - Block_Ptr bb = ab ? operator()(ab) : NULL; - Directive_Ptr aa = SASS_MEMORY_NEW(Directive, - a->pstate(), - a->keyword(), - as, - bb, - av); - return aa; - } - - Statement_Ptr Expand::operator()(Declaration_Ptr d) - { - Block_Obj ab = d->block(); - String_Obj old_p = d->property(); - Expression_Obj prop = old_p->perform(&eval); - String_Obj new_p = Cast(prop); - // we might get a color back - if (!new_p) { - std::string str(prop->to_string(ctx.c_options)); - new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); - } - Expression_Obj value = d->value(); - if (value) value = value->perform(&eval); - Block_Obj bb = ab ? operator()(ab) : NULL; - if (!bb) { - if (!value || (value->is_invisible() && !d->is_important())) return 0; - } - Declaration_Ptr decl = SASS_MEMORY_NEW(Declaration, - d->pstate(), - new_p, - value, - d->is_important(), - d->is_custom_property(), - bb); - decl->tabs(d->tabs()); - return decl; - } - - Statement_Ptr Expand::operator()(Assignment_Ptr a) - { - Env* env = environment(); - const std::string& var(a->variable()); - if (a->is_global()) { - if (a->is_default()) { - if (env->has_global(var)) { - Expression_Obj e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Obj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(&eval)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); - } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - Expression_Obj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(&eval)); - } - else { - env->set_local(var, a->value()->perform(&eval)); - } - } - else { - env->set_lexical(var, a->value()->perform(&eval)); - } - return 0; - } - - Statement_Ptr Expand::operator()(Import_Ptr imp) - { - Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); - if (imp->import_queries() && imp->import_queries()->size()) { - Expression_Obj ex = imp->import_queries()->perform(&eval); - result->import_queries(Cast(ex)); - } - for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { - result->urls().push_back(imp->urls()[i]->perform(&eval)); - } - // all resources have been dropped for Input_Stubs - // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} - return result.detach(); - } - - Statement_Ptr Expand::operator()(Import_Stub_Ptr i) - { - traces.push_back(Backtrace(i->pstate())); - // get parent node from call stack - AST_Node_Obj parent = call_stack.back(); - if (Cast(parent) == NULL) { - error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); - } - // we don't seem to need that actually afterall - Sass_Import_Entry import = sass_make_import( - i->imp_path().c_str(), - i->abs_path().c_str(), - 0, 0 - ); - ctx.import_stack.push_back(import); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); - block_stack.back()->append(trace); - block_stack.push_back(trace_block); - - const std::string& abs_path(i->resource().abs_path); - append_block(ctx.sheets.at(abs_path).root); - sass_delete_import(ctx.import_stack.back()); - ctx.import_stack.pop_back(); - block_stack.pop_back(); - traces.pop_back(); - return 0; - } - - Statement_Ptr Expand::operator()(Warning_Ptr w) - { - // eval handles this too, because warnings may occur in functions - w->perform(&eval); - return 0; - } - - Statement_Ptr Expand::operator()(Error_Ptr e) - { - // eval handles this too, because errors may occur in functions - e->perform(&eval); - return 0; - } - - Statement_Ptr Expand::operator()(Debug_Ptr d) - { - // eval handles this too, because warnings may occur in functions - d->perform(&eval); - return 0; - } - - Statement_Ptr Expand::operator()(Comment_Ptr c) - { - if (ctx.output_style() == COMPRESSED) { - // comments should not be evaluated in compact - // https://github.com/sass/libsass/issues/2359 - if (!c->is_important()) return NULL; - } - eval.is_in_comment = true; - Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); - eval.is_in_comment = false; - // TODO: eval the text, once we're parsing/storing it as a String_Schema - return rv; - } - - Statement_Ptr Expand::operator()(If_Ptr i) - { - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(i); - Expression_Obj rv = i->predicate()->perform(&eval); - if (*rv) { - append_block(i->block()); - } - else { - Block_Ptr alt = i->alternative(); - if (alt) append_block(alt); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // For does not create a new env scope - // But iteration vars are reset afterwards - Statement_Ptr Expand::operator()(For_Ptr f) - { - std::string variable(f->variable()); - Expression_Obj low = f->lower_bound()->perform(&eval); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); - } - Expression_Obj high = f->upper_bound()->perform(&eval); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); - } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - std::stringstream msg; msg << "Incompatible units: '" - << sass_start->unit() << "' and '" - << sass_end->unit() << "'."; - error(msg.str(), low->pstate(), traces); - } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(f); - Block_Ptr body = f->block(); - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Statement_Ptr Expand::operator()(Each_Ptr e) - { - std::vector variables(e->variables()); - Expression_Obj expr = e->list()->perform(&eval); - List_Obj list = 0; - Map_Obj map; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); - } - else if (Selector_List_Ptr ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); - list = Cast(rv); - } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); - } - else { - list = Cast(expr); - } - // remember variables and then reset them - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(e); - Block_Ptr body = e->block(); - - if (map) { - for (auto key : map->keys()) { - Expression_Obj k = key->perform(&eval); - Expression_Obj v = map->at(key)->perform(&eval); - - if (variables.size() == 1) { - List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(k); - variable->append(v); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], k); - env.set_local(variables[1], v); - } - append_block(body); - } - } - else { - // bool arglist = list->is_arglist(); - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Obj item = list->at(i); - // unwrap value if the expression is an argument - if (Argument_Obj arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List_Obj scalars = Cast(item)) { - if (variables.size() == 1) { - List_Obj var = scalars; - // if (arglist) var = (*scalars)[0]; - env.set_local(variables[0], var); - } else { - for (size_t j = 0, K = variables.size(); j < K; ++j) { - Expression_Obj res = j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) - : (*scalars)[j]->perform(&eval); - env.set_local(variables[j], res); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } - } - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement_Ptr Expand::operator()(While_Ptr w) - { - Expression_Obj pred = w->predicate(); - Block_Ptr body = w->block(); - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(w); - Expression_Obj cond = pred->perform(&eval); - while (!cond->is_false()) { - append_block(body); - cond = pred->perform(&eval); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement_Ptr Expand::operator()(Return_Ptr r) - { - error("@return may only be used within a function", r->pstate(), traces); - return 0; - } - - - void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { - - if (Selector_List_Obj sl = Cast(s)) { - for (Complex_Selector_Obj complex_selector : sl->elements()) { - Complex_Selector_Obj tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - if (Cast(header) == NULL) continue; // skip all others - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); - } - tail = tail->tail(); - } - } - } - - - Selector_List_Obj contextualized = Cast(s->perform(&eval)); - if (contextualized == false) return; - for (auto complex_sel : contextualized->elements()) { - Complex_Selector_Obj c = complex_sel; - if (!c->head() || c->tail()) { - std::string sel_str(contextualized->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); - } - Compound_Selector_Obj target = c->head(); - if (contextualized->is_optional()) target->is_optional(true); - for (size_t i = 0, L = extender->length(); i < L; ++i) { - Complex_Selector_Obj sel = (*extender)[i]; - if (!(sel->head() && sel->head()->length() > 0 && - Cast((*sel->head())[0]))) - { - Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); - hh->media_block((*extender)[i]->media_block()); - Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); - ssel->media_block((*extender)[i]->media_block()); - if (sel->has_line_feed()) ssel->has_line_feed(true); - Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); - ps->media_block((*extender)[i]->media_block()); - hh->append(ps); - ssel->tail(sel); - ssel->head(hh); - sel = ssel; - } - // if (c->has_line_feed()) sel->has_line_feed(true); - ctx.subset_map.put(target, std::make_pair(sel, target)); - } - } - - } - - Statement* Expand::operator()(Extension_Ptr e) - { - if (Selector_List_Ptr extender = selector()) { - Selector_List_Ptr sl = e->selector(); - // abort on invalid selector - if (sl == NULL) return NULL; - if (Selector_Schema_Ptr schema = sl->schema()) { - if (schema->has_real_parent_ref()) { - // put root block on stack again (ignore parents) - // selector schema must not connect in eval! - block_stack.push_back(block_stack.at(1)); - sl = eval(sl->schema()); - block_stack.pop_back(); - } else { - selector_stack.push_back(0); - sl = eval(sl->schema()); - selector_stack.pop_back(); - } - } - for (Complex_Selector_Obj cs : sl->elements()) { - if (!cs.isNull() && !cs->head().isNull()) { - cs->head()->media_block(media_block_stack.back()); - } - } - selector_stack.push_back(0); - expand_selector_list(sl, extender); - selector_stack.pop_back(); - } - return 0; - } - - Statement_Ptr Expand::operator()(Definition_Ptr d) - { - Env* env = environment(); - Definition_Obj dd = SASS_MEMORY_COPY(d); - env->local_frame()[d->name() + - (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; - - if (d->type() == Definition::FUNCTION && ( - Prelexer::calc_fn_call(d->name().c_str()) || - d->name() == "element" || - d->name() == "expression" || - d->name() == "url" - )) { - deprecated( - "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", - "This name conflicts with an existing CSS function with special parse rules.", - false, d->pstate() - ); - } - - // set the static link so we can have lexical scoping - dd->environment(env); - return 0; - } - - Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) - { - if (recursions > maxRecursion) { - throw Exception::StackError(traces, *c); - } - - recursions ++; - - Env* env = environment(); - std::string full_name(c->name() + "[m]"); - if (!env->has(full_name)) { - error("no mixin named " + c->name(), c->pstate(), traces); - } - Definition_Obj def = Cast((*env)[full_name]); - Block_Obj body = def->block(); - Parameters_Obj params = def->parameters(); - - if (c->block() && c->name() != "@content" && !body->has_content()) { - error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); - } - Expression_Obj rv = c->arguments()->perform(&eval); - Arguments_Obj args = Cast(rv); - std::string msg(", in mixin `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().path, - c->pstate().line + 1, - c->pstate().column + 1, - SASS_CALLEE_MIXIN, - { env } - }); - - Env new_env(def->environment()); - env_stack.push_back(&new_env); - if (c->block()) { - // represent mixin content blocks as thunks/closures - Definition_Obj thunk = SASS_MEMORY_NEW(Definition, - c->pstate(), - "@content", - SASS_MEMORY_NEW(Parameters, c->pstate()), - c->block(), - Definition::MIXIN); - thunk->environment(env); - new_env.local_frame()["@content[m]"] = thunk; - } - - bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); - - env->set_global("is_in_mixin", bool_true); - if (Block_Ptr pr = block_stack.back()) { - trace_block->is_root(pr->is_root()); - } - block_stack.push_back(trace_block); - for (auto bb : body->elements()) { - if (Ruleset_Ptr r = Cast(bb)) { - r->is_root(trace_block->is_root()); - } - Statement_Obj ith = bb->perform(this); - if (ith) trace->block()->append(ith); - } - block_stack.pop_back(); - env->del_global("is_in_mixin"); - - ctx.callee_stack.pop_back(); - env_stack.pop_back(); - traces.pop_back(); - - recursions --; - return trace.detach(); - } - - Statement_Ptr Expand::operator()(Content_Ptr c) - { - Env* env = environment(); - // convert @content directives into mixin calls to the underlying thunk - if (!env->has("@content[m]")) return 0; - - if (block_stack.back()->is_root()) { - selector_stack.push_back(0); - } - - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, - c->pstate(), - "@content", - SASS_MEMORY_NEW(Arguments, c->pstate())); - - Trace_Obj trace = Cast(call->perform(this)); - - if (block_stack.back()->is_root()) { - selector_stack.pop_back(); - } - - return trace.detach(); - } - - // produce an error if something is not implemented - inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) - { - std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); - String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); - error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); - return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); - } - - // process and add to last block on stack - inline void Expand::append_block(Block_Ptr b) - { - if (b->is_root()) call_stack.push_back(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr stm = b->at(i); - Statement_Obj ith = stm->perform(this); - if (ith) block_stack.back()->append(ith); - } - if (b->is_root()) call_stack.pop_back(); - } - -} diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp deleted file mode 100644 index 3464c98f6..000000000 --- a/src/libsass/src/expand.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef SASS_EXPAND_H -#define SASS_EXPAND_H - -#include - -#include "ast.hpp" -#include "eval.hpp" -#include "operation.hpp" -#include "environment.hpp" - -namespace Sass { - - class Listize; - class Context; - class Eval; - struct Backtrace; - - class Expand : public Operation_CRTP { - public: - - Env* environment(); - Selector_List_Obj selector(); - - Context& ctx; - Backtraces& traces; - Eval eval; - size_t recursions; - bool in_keyframes; - bool at_root_without_rule; - bool old_at_root_without_rule; - - // it's easier to work with vectors - std::vector env_stack; - std::vector block_stack; - std::vector call_stack; - std::vector selector_stack; - std::vector media_block_stack; - - Boolean_Obj bool_true; - - Statement_Ptr fallback_impl(AST_Node_Ptr n); - - private: - void expand_selector_list(Selector_Obj, Selector_List_Obj extender); - - public: - Expand(Context&, Env*, std::vector* stack = NULL); - ~Expand() { } - - Block_Ptr operator()(Block_Ptr); - Statement_Ptr operator()(Ruleset_Ptr); - Statement_Ptr operator()(Media_Block_Ptr); - Statement_Ptr operator()(Supports_Block_Ptr); - Statement_Ptr operator()(At_Root_Block_Ptr); - Statement_Ptr operator()(Directive_Ptr); - Statement_Ptr operator()(Declaration_Ptr); - Statement_Ptr operator()(Assignment_Ptr); - Statement_Ptr operator()(Import_Ptr); - Statement_Ptr operator()(Import_Stub_Ptr); - Statement_Ptr operator()(Warning_Ptr); - Statement_Ptr operator()(Error_Ptr); - Statement_Ptr operator()(Debug_Ptr); - Statement_Ptr operator()(Comment_Ptr); - Statement_Ptr operator()(If_Ptr); - Statement_Ptr operator()(For_Ptr); - Statement_Ptr operator()(Each_Ptr); - Statement_Ptr operator()(While_Ptr); - Statement_Ptr operator()(Return_Ptr); - Statement_Ptr operator()(Extension_Ptr); - Statement_Ptr operator()(Definition_Ptr); - Statement_Ptr operator()(Mixin_Call_Ptr); - Statement_Ptr operator()(Content_Ptr); - - template - Statement_Ptr fallback(U x) { return fallback_impl(x); } - - void append_block(Block_Ptr); - }; - -} - -#endif diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp deleted file mode 100644 index 602269880..000000000 --- a/src/libsass/src/extend.cpp +++ /dev/null @@ -1,2130 +0,0 @@ -#include "sass.hpp" -#include "extend.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "paths.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "node.hpp" -#include "sass_util.hpp" -#include "remove_placeholders.hpp" -#include "debug.hpp" -#include -#include -#include - -/* - NOTES: - - - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which - is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what - ruby already outputs (to make comparisons easier). - - - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing. - Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass. - The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually - used for the porting. - - - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby - output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to - the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see - something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment. - - - Coding conventions in this file (these may need to be changed before merging back into master) - - Very basic hungarian notation: - p prefix for pointers (pSelector) - no prefix for value types and references (selector) - - Use STL iterators where possible - - prefer verbose naming over terse naming - - use typedefs for STL container types for make maintenance easier - - - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators - in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby - sass causes this to be necessary. - - - GLOBAL TODOS: - - - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode. - - - consider making the extend* functions member functions to avoid passing around ctx and subset_map map around. This has the - drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and - can cause additional compile time dependencies. - - - mark the helper methods in this file static to given them compilation unit linkage. - - - implement parent directive matching - - - fix compilation warnings for unused Extend members if we really don't need those references anymore. - */ - - -namespace Sass { - - - -#ifdef DEBUG - - // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp - std::ostream& operator<<(std::ostream& os, const Complex_Selector::Combinator combinator) { - switch (combinator) { - case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; - case Complex_Selector::PARENT_OF: os << "\">\""; break; - case Complex_Selector::PRECEDES: os << "\"~\""; break; - case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; - case Complex_Selector::REFERENCE: os << "\"/\""; break; - } - - return os; - } - - - std::ostream& operator<<(std::ostream& os, Compound_Selector& compoundSelector) { - for (size_t i = 0, L = compoundSelector.length(); i < L; ++i) { - if (i > 0) os << ", "; - os << compoundSelector[i]->to_string(); - } - return os; - } - - std::ostream& operator<<(std::ostream& os, Simple_Selector& simpleSelector) { - os << simpleSelector.to_string(); - return os; - } - - // Print a string representation of a Compound_Selector - static void printSimpleSelector(Simple_Selector* pSimpleSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pSimpleSelector) { - std::cerr << "[" << *pSimpleSelector << "]"; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - // Print a string representation of a Compound_Selector - static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pCompoundSelector) { - std::cerr << "[" << *pCompoundSelector << "]"; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - - std::ostream& operator<<(std::ostream& os, Complex_Selector& complexSelector) { - - os << "["; - Complex_Selector_Ptr pIter = &complexSelector; - bool first = true; - while (pIter) { - if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) { - if (!first) { - os << ", "; - } - first = false; - os << pIter->combinator(); - } - - if (!first) { - os << ", "; - } - first = false; - - if (pIter->head()) { - os << pIter->head()->to_string(); - } else { - os << "NULL_HEAD"; - } - - pIter = pIter->tail(); - } - os << "]"; - - return os; - } - - - // Print a string representation of a Complex_Selector - static void printComplexSelector(Complex_Selector_Ptr pComplexSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pComplexSelector) { - std::cerr << *pComplexSelector; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - bool first = true; - std::cerr << "["; - for(SubSetMapLookup& pair : collection) { - if (first) { - first = false; - } else { - std::cerr << ", "; - } - std::cerr << "["; - Compound_Selector_Ptr pSels = pair.first; - Complex_Selector_Ptr pNewSelector = pair.second; - std::cerr << "[" << *pSels << "], "; - printComplexSelector(pNewSelector, NULL, false); - } - std::cerr << "]"; - - if (newline) { - std::cerr << std::endl; - } - } - - // Print a string representation of a ComplexSelectorSet - static void printSourcesSet(ComplexSelectorSet& sources, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on - // the differences we see when debug printing. - typedef std::deque SourceStrings; - SourceStrings sourceStrings; - for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { - Complex_Selector_Ptr pSource = *iterator; - std::stringstream sstream; - sstream << complexSelectorToNode(pSource); - sourceStrings.push_back(sstream.str()); - } - - // Sort to get consistent output - std::sort(sourceStrings.begin(), sourceStrings.end()); - - std::cerr << "ComplexSelectorSet["; - for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) { - std::string source = *iterator; - if (iterator != sourceStrings.begin()) { - std::cerr << ", "; - } - std::cerr << source; - } - std::cerr << "]"; - - if (newline) { - std::cerr << std::endl; - } - } - - - std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) { - os << "SUBSET_MAP_ENTRIES["; - - for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { - Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge) - Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend - - if (iterator != entries.begin()) { - os << ", "; - } - - os << "("; - - if (pExtComplexSelector) { - std::cerr << *pExtComplexSelector; - } else { - std::cerr << "NULL"; - } - - os << " -> "; - - if (pExtCompoundSelector) { - std::cerr << *pExtCompoundSelector; - } else { - std::cerr << "NULL"; - } - - os << ")"; - - } - - os << "]"; - - return os; - } -#endif - - static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo) { - // TODO: figure out a better way to create a Complex_Selector from scratch - // TODO: There's got to be a better way. This got ugly quick... - Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); - Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); - - pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - - bool isSuperselector = pOne->is_superselector_of(pTwo); - - pOne->clear_innermost(); - pTwo->clear_innermost(); - - return isSuperselector; - } - - void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out) { - for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { - Node& child = *iter; - out.push_back(nodeToComplexSelector(child)); - } - } - - Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque) { - Node result = Node::createCollection(); - - for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { - Complex_Selector_Obj pChild = *iter; - result.collection()->push_back(complexSelectorToNode(pChild)); - } - - return result; - } - - class LcsCollectionComparator { - public: - LcsCollectionComparator() {} - - bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const { - /* - This code is based on the following block from ruby sass' subweave - do |s1, s2| - next s1 if s1 == s2 - next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) - next s2 if parent_superselector?(s1, s2) - next s1 if parent_superselector?(s2, s1) - end - */ - - if (*pOne == *pTwo) { - pOut = pOne; - return true; - } - - if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) { - return false; - } - - if (parentSuperselector(pOne, pTwo)) { - pOut = pTwo; - return true; - } - - if (parentSuperselector(pTwo, pOne)) { - pOut = pOne; - return true; - } - - return false; - } - }; - - - /* - This is the equivalent of ruby's Sass::Util.lcs_backtrace. - - # Computes a single longest common subsequence for arrays x and y. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS - */ - void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { - //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - if (i == 0 || j == 0) { - DEBUG_PRINTLN(LCS, "RETURNING EMPTY") - return; - } - - - Complex_Selector_Obj pCompareOut; - if (comparator(x[i], y[j], pCompareOut)) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") - lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out); - out.push_back(pCompareOut); - return; - } - - if (c[i][j - 1] > c[i - 1][j]) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") - lcs_backtrace(c, x, y, i, j - 1, comparator, out); - return; - } - - DEBUG_PRINTLN(LCS, "FINAL RETURN") - lcs_backtrace(c, x, y, i - 1, j, comparator, out); - return; - } - - /* - This is the equivalent of ruby's Sass::Util.lcs_table. - - # Calculates the memoization table for the Least Common Subsequence algorithm. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS - */ - void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) { - //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - LCSTable c(x.size(), std::vector(y.size())); - - // These shouldn't be necessary since the vector will be initialized to 0 already. - // x.size.times {|i| c[i][0] = 0} - // y.size.times {|j| c[0][j] = 0} - - for (size_t i = 1; i < x.size(); i++) { - for (size_t j = 1; j < y.size(); j++) { - Complex_Selector_Obj pCompareOut; - - if (comparator(x[i], y[j], pCompareOut)) { - c[i][j] = c[i - 1][j - 1] + 1; - } else { - c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); - } - } - } - - out = c; - } - - /* - This is the equivalent of ruby's Sass::Util.lcs. - - # Computes a single longest common subsequence for `x` and `y`. - # If there are more than one longest common subsequences, - # the one returned is that which starts first in `x`. - - # @param x [NodeCollection] - # @param y [NodeCollection] - # @comparator An equality check between elements of `x` and `y`. - # @return [NodeCollection] The LCS - - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - */ - void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { - //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - x.push_front(NULL); - y.push_front(NULL); - - LCSTable table; - lcs_table(x, y, comparator, table); - - return lcs_backtrace(table, x, y, static_cast(x.size()) - 1, static_cast(y.size()) - 1, comparator, out); - } - - - /* - This is the equivalent of ruby's Sequence.trim. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - # Avoid truly horrific quadratic behavior. TODO: I think there - # may be a way to get perfect trimming without going quadratic. - return seqses if seqses.size > 100 - - # Keep the results in a separate array so we can be sure we aren't - # comparing against an already-trimmed selector. This ensures that two - # identical selectors don't mutually trim one another. - result = seqses.dup - - # This is n^2 on the sequences, but only comparing between - # separate sequences should limit the quadratic behavior. - seqses.each_with_index do |seqs1, i| - tempResult = [] - - for seq1 in seqs1 do - max_spec = 0 - for seq in _sources(seq1) do - max_spec = [max_spec, seq.specificity].max - end - - - isMoreSpecificOuter = false - for seqs2 in result do - if seqs1.equal?(seqs2) then - next - end - - # Second Law of Extend: the specificity of a generated selector - # should never be less than the specificity of the extending - # selector. - # - # See https://github.com/nex3/sass/issues/324. - isMoreSpecificInner = false - for seq2 in seqs2 do - isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1) - if isMoreSpecificInner then - break - end - end - - if isMoreSpecificInner then - isMoreSpecificOuter = true - break - end - end - - if !isMoreSpecificOuter then - tempResult.push(seq1) - end - end - - result[i] = tempResult - - end - - result - */ - /* - - IMPROVEMENT: We could probably work directly in the output trimmed deque. - */ - Node Extend::trim(Node& seqses, bool isReplace) { - // See the comments in the above ruby code before embarking on understanding this function. - - // Avoid poor performance in extreme cases. - if (seqses.collection()->size() > 100) { - return seqses; - } - - - DEBUG_PRINTLN(TRIM, "TRIM: " << seqses) - - - Node result = Node::createCollection(); - result.plus(seqses); - - DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result) - - // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're - // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track - // of the index manually. - int toTrimIndex = 0; - - for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) { - Node& seqs1 = *seqsesIter; - - DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex) - - Node tempResult = Node::createCollection(); - tempResult.got_line_feed = seqs1.got_line_feed; - - for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) { - Node& seq1 = *seqs1Iter; - - Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1); - - // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code - // for a good description of sources. - // - // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test. - // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We - // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My - // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely - // a guess though. - unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0; - ComplexSelectorSet sources = pSeq1->sources(); - - DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) - DEBUG_EXEC(TRIM, printSourcesSet(sources, "TRIM SOURCES: ")) - - for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { - const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; - maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity()); - } - - DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity) - - bool isMoreSpecificOuter = false; - - int resultIndex = 0; - - for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) { - Node& seqs2 = *resultIter; - - DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1) - DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2) - - // Do not compare the same sequence to itself. The ruby call we're trying to - // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision. - // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is - // derived from seqses and seqs2 is derived from result. - if (seqs1.collection() == seqs2.collection()) { - DEBUG_PRINTLN(TRIM, "CONTINUE") - continue; - } - - bool isMoreSpecificInner = false; - - for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) { - Node& seq2 = *seqs2Iter; - - Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2); - - DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity()) - DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false")) - DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false")) - - isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1); - - if (isMoreSpecificInner) { - DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC") - break; - } - } - - // If we found something more specific, we're done. Let the outer loop know and stop iterating. - if (isMoreSpecificInner) { - isMoreSpecificOuter = true; - break; - } - - resultIndex++; - } - - if (!isMoreSpecificOuter) { - DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1) - tempResult.collection()->push_back(seq1); - } - - } - - DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result) - DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult) - (*result.collection())[toTrimIndex] = tempResult; - - toTrimIndex++; - - DEBUG_PRINTLN(TRIM, "RESULT: " << result) - } - - return result; - } - - - - static bool parentSuperselector(const Node& one, const Node& two) { - // TODO: figure out a better way to create a Complex_Selector from scratch - // TODO: There's got to be a better way. This got ugly quick... - Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); - Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); - - Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one); - pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two); - pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - - return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); - } - - - class ParentSuperselectorChunker { - public: - ParentSuperselectorChunker(Node& lcs) : mLcs(lcs) {} - Node& mLcs; - - bool operator()(const Node& seq) const { - // {|s| parent_superselector?(s.first, lcs.first)} - if (seq.collection()->size() == 0) return false; - return parentSuperselector(seq.collection()->front(), mLcs.collection()->front()); - } - }; - - class SubweaveEmptyChunker { - public: - bool operator()(const Node& seq) const { - // {|s| s.empty?} - - return seq.collection()->empty(); - } - }; - - /* - # Takes initial subsequences of `seq1` and `seq2` and returns all - # orderings of those subsequences. The initial subsequences are determined - # by a block. - # - # Destructively removes the initial subsequences of `seq1` and `seq2`. - # - # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` - # denoting the boundary of the initial subsequence), this would return - # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and - # `(3 4 5)`. - # - # @param seq1 [Array] - # @param seq2 [Array] - # @yield [a] Used to determine when to cut off the initial subsequences. - # Called repeatedly for each sequence until it returns true. - # @yieldparam a [Array] A final subsequence of one input sequence after - # cutting off some initial subsequence. - # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence - # here. - # @return [Array] All possible orderings of the initial subsequences. - def chunks(seq1, seq2) - chunk1 = [] - chunk1 << seq1.shift until yield seq1 - chunk2 = [] - chunk2 << seq2.shift until yield seq2 - return [] if chunk1.empty? && chunk2.empty? - return [chunk2] if chunk1.empty? - return [chunk1] if chunk2.empty? - [chunk1 + chunk2, chunk2 + chunk1] - end - */ - template - static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) { - Node chunk1 = Node::createCollection(); - while (seq1.collection()->size() && !chunker(seq1)) { - chunk1.collection()->push_back(seq1.collection()->front()); - seq1.collection()->pop_front(); - } - - Node chunk2 = Node::createCollection(); - while (!seq2.collection()->empty() && !chunker(seq2)) { - chunk2.collection()->push_back(seq2.collection()->front()); - seq2.collection()->pop_front(); - } - - if (chunk1.collection()->empty() && chunk2.collection()->empty()) { - DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY") - return Node::createCollection(); - } - - if (chunk1.collection()->empty()) { - Node chunk2Wrapper = Node::createCollection(); - chunk2Wrapper.collection()->push_back(chunk2); - DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY") - return chunk2Wrapper; - } - - if (chunk2.collection()->empty()) { - Node chunk1Wrapper = Node::createCollection(); - chunk1Wrapper.collection()->push_back(chunk1); - DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY") - return chunk1Wrapper; - } - - Node perms = Node::createCollection(); - - Node firstPermutation = Node::createCollection(); - firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); - firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); - perms.collection()->push_back(firstPermutation); - - Node secondPermutation = Node::createCollection(); - secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); - secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); - perms.collection()->push_back(secondPermutation); - - DEBUG_PRINTLN(CHUNKS, "RETURNING PERM") - - return perms; - } - - - static Node groupSelectors(Node& seq) { - Node newSeq = Node::createCollection(); - - Node tail = Node::createCollection(); - tail.plus(seq); - - while (!tail.collection()->empty()) { - Node head = Node::createCollection(); - - do { - head.collection()->push_back(tail.collection()->front()); - tail.collection()->pop_front(); - } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator())); - - newSeq.collection()->push_back(head); - } - - return newSeq; - } - - - static void getAndRemoveInitialOps(Node& seq, Node& ops) { - NodeDeque& seqCollection = *(seq.collection()); - NodeDeque& opsCollection = *(ops.collection()); - - while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) { - opsCollection.push_back(seqCollection.front()); - seqCollection.pop_front(); - } - } - - - static void getAndRemoveFinalOps(Node& seq, Node& ops) { - NodeDeque& seqCollection = *(seq.collection()); - NodeDeque& opsCollection = *(ops.collection()); - - while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) { - opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code - seqCollection.pop_back(); - } - } - - - /* - def merge_initial_ops(seq1, seq2) - ops1, ops2 = [], [] - ops1 << seq1.shift while seq1.first.is_a?(String) - ops2 << seq2.shift while seq2.first.is_a?(String) - - newline = false - newline ||= !!ops1.shift if ops1.first == "\n" - newline ||= !!ops2.shift if ops2.first == "\n" - - # If neither sequence is a subsequence of the other, they cannot be - # merged successfully - lcs = Sass::Util.lcs(ops1, ops2) - return unless lcs == ops1 || lcs == ops2 - return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) - end - */ - static Node mergeInitialOps(Node& seq1, Node& seq2) { - Node ops1 = Node::createCollection(); - Node ops2 = Node::createCollection(); - - getAndRemoveInitialOps(seq1, ops1); - getAndRemoveInitialOps(seq2, ops2); - - // TODO: Do we have this information available to us? - // newline = false - // newline ||= !!ops1.shift if ops1.first == "\n" - // newline ||= !!ops2.shift if ops2.first == "\n" - - // If neither sequence is a subsequence of the other, they cannot be merged successfully - DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); - - if (!(opsLcs == ops1 || opsLcs == ops2)) { - return Node::createNil(); - } - - // TODO: more newline logic - // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) - - return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2); - } - - - /* - def merge_final_ops(seq1, seq2, res = []) - - - # This code looks complicated, but it's actually just a bunch of special - # cases for interactions between different combinators. - op1, op2 = ops1.first, ops2.first - if op1 && op2 - sel1 = seq1.pop - sel2 = seq2.pop - if op1 == '~' && op2 == '~' - if sel1.superselector?(sel2) - res.unshift sel2, '~' - elsif sel2.superselector?(sel1) - res.unshift sel1, '~' - else - merged = sel1.unify(sel2.members, sel2.subject?) - res.unshift [ - [sel1, '~', sel2, '~'], - [sel2, '~', sel1, '~'], - ([merged, '~'] if merged) - ].compact - end - elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~') - if op1 == '~' - tilde_sel, plus_sel = sel1, sel2 - else - tilde_sel, plus_sel = sel2, sel1 - end - - if tilde_sel.superselector?(plus_sel) - res.unshift plus_sel, '+' - else - merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - res.unshift [ - [tilde_sel, '~', plus_sel, '+'], - ([merged, '+'] if merged) - ].compact - end - elsif op1 == '>' && %w[~ +].include?(op2) - res.unshift sel2, op2 - seq1.push sel1, op1 - elsif op2 == '>' && %w[~ +].include?(op1) - res.unshift sel1, op1 - seq2.push sel2, op2 - elsif op1 == op2 - return unless merged = sel1.unify(sel2.members, sel2.subject?) - res.unshift merged, op1 - else - # Unknown selector combinators can't be unified - return - end - return merge_final_ops(seq1, seq2, res) - elsif op1 - seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last) - res.unshift seq1.pop, op1 - return merge_final_ops(seq1, seq2, res) - else # op2 - seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last) - res.unshift seq2.pop, op2 - return merge_final_ops(seq1, seq2, res) - end - end - */ - static Node mergeFinalOps(Node& seq1, Node& seq2, Node& res) { - - Node ops1 = Node::createCollection(); - Node ops2 = Node::createCollection(); - - getAndRemoveFinalOps(seq1, ops1); - getAndRemoveFinalOps(seq2, ops2); - - // TODO: do we have newlines to remove? - // ops1.reject! {|o| o == "\n"} - // ops2.reject! {|o| o == "\n"} - - if (ops1.collection()->empty() && ops2.collection()->empty()) { - return res; - } - - if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) { - DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); - - // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. - - if (!(opsLcs == ops1 || opsLcs == ops2)) { - return Node::createNil(); - } - - if (ops1.collection()->size() > ops2.collection()->size()) { - res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend()); - } else { - res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend()); - } - - return res; - } - - if (!ops1.collection()->empty() && !ops2.collection()->empty()) { - - Node op1 = ops1.collection()->front(); - Node op2 = ops2.collection()->front(); - - Node sel1 = seq1.collection()->back(); - seq1.collection()->pop_back(); - - Node sel2 = seq2.collection()->back(); - seq2.collection()->pop_back(); - - if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) { - - if (sel1.selector()->is_superselector_of(sel2.selector())) { - - res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); - res.collection()->push_front(sel2); - - } else if (sel2.selector()->is_superselector_of(sel1.selector())) { - - res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); - res.collection()->push_front(sel1); - - } else { - - DEBUG_PRINTLN(ALL, "sel1: " << sel1) - DEBUG_PRINTLN(ALL, "sel2: " << sel2) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - Node newRes = Node::createCollection(); - - Node firstPerm = Node::createCollection(); - firstPerm.collection()->push_back(sel1); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - firstPerm.collection()->push_back(sel2); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(firstPerm); - - Node secondPerm = Node::createCollection(); - secondPerm.collection()->push_back(sel2); - secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - secondPerm.collection()->push_back(sel1); - secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(secondPerm); - - if (pMerged) { - Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); - mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(mergedPerm); - } - - res.collection()->push_front(newRes); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } - - } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) { - - Node tildeSel = sel1; - Node plusSel = sel2; - Node plusOp = op2; - if (op1.combinator() != Complex_Selector::PRECEDES) { - tildeSel = sel2; - plusSel = sel1; - plusOp = op1; - } - - if (tildeSel.selector()->is_superselector_of(plusSel.selector())) { - - res.collection()->push_front(plusOp); - res.collection()->push_front(plusSel); - - } else { - - DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel) - DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - Node newRes = Node::createCollection(); - - Node firstPerm = Node::createCollection(); - firstPerm.collection()->push_back(tildeSel); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - firstPerm.collection()->push_back(plusSel); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); - newRes.collection()->push_back(firstPerm); - - if (pMerged) { - Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); - mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); - newRes.collection()->push_back(mergedPerm); - } - - res.collection()->push_front(newRes); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } - } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) { - - res.collection()->push_front(op2); - res.collection()->push_front(sel2); - - seq1.collection()->push_back(sel1); - seq1.collection()->push_back(op1); - - } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) { - - res.collection()->push_front(op1); - res.collection()->push_front(sel1); - - seq2.collection()->push_back(sel2); - seq2.collection()->push_back(op2); - - } else if (op1.combinator() == op2.combinator()) { - - DEBUG_PRINTLN(ALL, "sel1: " << sel1) - DEBUG_PRINTLN(ALL, "sel2: " << sel2) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - if (!pMerged) { - return Node::createNil(); - } - - res.collection()->push_front(op1); - res.collection()->push_front(Node::createSelector(pMergedWrapper)); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } else { - return Node::createNil(); - } - - return mergeFinalOps(seq1, seq2, res); - - } else if (!ops1.collection()->empty()) { - - Node op1 = ops1.collection()->front(); - - if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) { - seq2.collection()->pop_back(); - } - - // TODO: consider unshift(NodeCollection, Node) - res.collection()->push_front(op1); - res.collection()->push_front(seq1.collection()->back()); - seq1.collection()->pop_back(); - - return mergeFinalOps(seq1, seq2, res); - - } else { // !ops2.collection()->empty() - - Node op2 = ops2.collection()->front(); - - if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) { - seq1.collection()->pop_back(); - } - - res.collection()->push_front(op2); - res.collection()->push_front(seq2.collection()->back()); - seq2.collection()->pop_back(); - - return mergeFinalOps(seq1, seq2, res); - - } - - } - - - /* - This is the equivalent of ruby's Sequence.subweave. - - Here is the original subweave code for reference during porting. - - def subweave(seq1, seq2) - return [seq2] if seq1.empty? - return [seq1] if seq2.empty? - - seq1, seq2 = seq1.dup, seq2.dup - return unless init = merge_initial_ops(seq1, seq2) - return unless fin = merge_final_ops(seq1, seq2) - seq1 = group_selectors(seq1) - seq2 = group_selectors(seq2) - lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2| - next s1 if s1 == s2 - next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) - next s2 if parent_superselector?(s1, s2) - next s1 if parent_superselector?(s2, s1) - end - - diff = [[init]] - until lcs.empty? - diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift] - seq1.shift - seq2.shift - end - diff << chunks(seq1, seq2) {|s| s.empty?} - diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} - diff.reject! {|c| c.empty?} - - result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)} - - result - end - */ - Node subweave(Node& one, Node& two) { - // Check for the simple cases - if (one.collection()->size() == 0) { - Node out = Node::createCollection(); - out.collection()->push_back(two); - return out; - } - if (two.collection()->size() == 0) { - Node out = Node::createCollection(); - out.collection()->push_back(one); - return out; - } - - Node seq1 = Node::createCollection(); - seq1.plus(one); - Node seq2 = Node::createCollection(); - seq2.plus(two); - - DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1) - DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) - - Node init = mergeInitialOps(seq1, seq2); - if (init.isNil()) { - return Node::createNil(); - } - - DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init) - - Node res = Node::createCollection(); - Node fin = mergeFinalOps(seq1, seq2, res); - if (fin.isNil()) { - return Node::createNil(); - } - - DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin) - - - // Moving this line up since fin isn't modified between now and when it happened before - // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} - - for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end(); - finIter != finEndIter; ++finIter) { - - Node& childNode = *finIter; - - if (!childNode.isCollection()) { - Node wrapper = Node::createCollection(); - wrapper.collection()->push_back(childNode); - childNode = wrapper; - } - - } - - DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin) - - - - Node groupSeq1 = groupSelectors(seq1); - DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) - - Node groupSeq2 = groupSelectors(seq2); - DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2) - - - ComplexSelectorDeque groupSeq1Converted; - nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted); - - ComplexSelectorDeque groupSeq2Converted; - nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted); - - ComplexSelectorDeque out; - LcsCollectionComparator collectionComparator; - lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, out); - Node seqLcs = complexSelectorDequeToNode(out); - - DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) - - - Node initWrapper = Node::createCollection(); - initWrapper.collection()->push_back(init); - Node diff = Node::createCollection(); - diff.collection()->push_back(initWrapper); - - DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff) - - - while (!seqLcs.collection()->empty()) { - ParentSuperselectorChunker superselectorChunker(seqLcs); - Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker); - diff.collection()->push_back(chunksResult); - - Node lcsWrapper = Node::createCollection(); - lcsWrapper.collection()->push_back(seqLcs.collection()->front()); - seqLcs.collection()->pop_front(); - diff.collection()->push_back(lcsWrapper); - - if (groupSeq1.collection()->size()) groupSeq1.collection()->pop_front(); - if (groupSeq2.collection()->size()) groupSeq2.collection()->pop_front(); - } - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff) - - - DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2) - - - SubweaveEmptyChunker emptyChunker; - Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker); - diff.collection()->push_back(chunksResult); - - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff) - - - diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end()); - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff) - - // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection) - Node diffFiltered = Node::createCollection(); - for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end(); - diffIter != diffEndIter; ++diffIter) { - Node& node = *diffIter; - if (node.collection() && !node.collection()->empty()) { - diffFiltered.collection()->push_back(node); - } - } - diff = diffFiltered; - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff) - - - Node pathsResult = paths(diff); - - DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) - - - // We're flattening in place - for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end(); - pathsIter != pathsEndIter; ++pathsIter) { - - Node& child = *pathsIter; - child = flatten(child); - } - - DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult) - - - /* - TODO: implement - rejected = mapped.reject {|p| path_has_two_subjects?(p)} - $stderr.puts "REJECTED: #{rejected}" - */ - - - return pathsResult; - - } - /* - // disabled to avoid clang warning [-Wunused-function] - static Node subweaveNaive(const Node& one, const Node& two) { - Node out = Node::createCollection(); - - // Check for the simple cases - if (one.isNil()) { - out.collection()->push_back(two.klone()); - } else if (two.isNil()) { - out.collection()->push_back(one.klone()); - } else { - // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B - // See https://gist.github.com/nex3/7609394 for details. - - Node firstPerm = one.klone(); - Node twoCloned = two.klone(); - firstPerm.plus(twoCloned); - out.collection()->push_back(firstPerm); - - Node secondPerm = two.klone(); - Node oneCloned = one.klone(); - secondPerm.plus(oneCloned ); - out.collection()->push_back(secondPerm); - } - - return out; - } - */ - - - /* - This is the equivalent of ruby's Sequence.weave. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def weave(path) - # This function works by moving through the selector path left-to-right, - # building all possible prefixes simultaneously. These prefixes are - # `befores`, while the remaining parenthesized suffixes is `afters`. - befores = [[]] - afters = path.dup - - until afters.empty? - current = afters.shift.dup - last_current = [current.pop] - - tempResult = [] - - for before in befores do - sub = subweave(before, current) - if sub.nil? - next - end - - for seqs in sub do - tempResult.push(seqs + last_current) - end - end - - befores = tempResult - - end - - return befores - end - */ - /* - def weave(path) - befores = [[]] - afters = path.dup - - until afters.empty? - current = afters.shift.dup - - last_current = [current.pop] - - - tempResult = [] - - for before in befores do - sub = subweave(before, current) - - if sub.nil? - next [] - end - - - for seqs in sub do - toPush = seqs + last_current - - tempResult.push(seqs + last_current) - end - - end - - befores = tempResult - - end - - return befores - end - */ - Node Extend::weave(Node& path) { - - DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) - - Node befores = Node::createCollection(); - befores.collection()->push_back(Node::createCollection()); - - Node afters = Node::createCollection(); - afters.plus(path); - - while (!afters.collection()->empty()) { - Node current = afters.collection()->front().klone(); - afters.collection()->pop_front(); - DEBUG_PRINTLN(WEAVE, "CURRENT: " << current) - if (current.collection()->size() == 0) continue; - - Node last_current = Node::createCollection(); - last_current.collection()->push_back(current.collection()->back()); - current.collection()->pop_back(); - DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current) - DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current) - - Node tempResult = Node::createCollection(); - - for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { - Node& before = *beforesIter; - - Node sub = subweave(before, current); - - DEBUG_PRINTLN(WEAVE, "SUB: " << sub) - - if (sub.isNil()) { - return Node::createCollection(); - } - - for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) { - Node& seqs = *subIter; - - Node toPush = Node::createCollection(); - toPush.plus(seqs); - toPush.plus(last_current); - - // move line feed from inner to outer selector (very hacky indeed) - if (last_current.collection() && last_current.collection()->front().selector()) { - toPush.got_line_feed = last_current.collection()->front().got_line_feed; - last_current.collection()->front().selector()->has_line_feed(false); - last_current.collection()->front().got_line_feed = false; - } - - tempResult.collection()->push_back(toPush); - - } - } - - befores = tempResult; - - } - - return befores; - } - - - - /* - This is the equivalent of ruby's SimpleSequence.do_extend. - - // TODO: I think I have some modified ruby code to put here. Check. - */ - /* - ISSUES: - - Previous TODO: Do we need to group the results by extender? - - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?) - - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything... - - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing... - */ - template - class GroupByToAFunctor { - public: - KeyType operator()(SubSetMapPair& extPair) const { - Complex_Selector_Obj pSelector = extPair.first; - return pSelector; - } - }; - Node Extend::extendCompoundSelector(Compound_Selector_Ptr pSelector, CompoundSelectorSet& seen, bool isReplace) { - - /* this turned out to be too much overhead - probably due to holding a "Node" object - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeCompound.find(pSelector); - if (memoized != memoizeCompound.end()) { - return memoized->second.klone(); - } - */ - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: ")) - // TODO: Ruby has another loop here to skip certain members? - - // let RESULTS be an empty list of complex selectors - Node results = Node::createCollection(); - // extendedSelectors.got_line_feed = true; - - SubSetMapPairs entries = subset_map.get_v(pSelector); - - GroupByToAFunctor extPairKeyFunctor; - SubSetMapResults arr; - group_by_to_a(entries, extPairKeyFunctor, arr); - - SubSetMapLookups holder; - - // for each (EXTENDER, TARGET) in MAP.get(COMPOUND): - for (SubSetMapResult& groupedPair : arr) { - - Complex_Selector_Obj seq = groupedPair.first; - SubSetMapPairs& group = groupedPair.second; - - DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) - - Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - for (SubSetMapPair& pair : group) { - pair.second->extended(true); - pSels->concat(pair.second); - } - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) - - // The selector up to where the @extend is (ie, the thing to merge) - Complex_Selector_Ptr pExtComplexSelector = seq; - - // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? - // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) - Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels); - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) - - Compound_Selector_Obj pInnermostCompoundSelector = pExtComplexSelector->last()->head(); - - if (!pInnermostCompoundSelector) { - pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - } - Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors); - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: ")) - - // RUBY: next unless unified - if (!pUnifiedSelector || pUnifiedSelector->length() == 0) { - continue; - } - - // TODO: implement the parent directive match (if necessary based on test failures) - // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none? - - // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just - // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more - // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered - // out and aren't operated on. - Complex_Selector_Obj pNewSelector = SASS_MEMORY_CLONE(pExtComplexSelector); // ->first(); - - Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); - - Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); - pNewSelector->set_innermost(pNewInnerMost, combinator); - -#ifdef DEBUG - ComplexSelectorSet debugSet; - debugSet = pNewSelector->sources(); - if (debugSet.size() > 0) { - throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this."); - } - debugSet = pExtComplexSelector->sources(); - if (debugSet.size() > 0) { - throw std::runtime_error("The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this."); - } -#endif - - - // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true); - // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. - DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector)) - - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, "SOURCES NEW SEQ BEGIN: ")) - - // I actually want to create a copy here (performance!) - ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES THIS EXTEND: ")) - - newSourcesSet.insert(pExtComplexSelector); - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES WITH NEW SOURCE: ")) - - // RUBY: new_seq.add_sources!(sources + [seq]) - pNewSelector->addSources(newSourcesSet); - - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, "SOURCES ON NEW SELECTOR AFTER ADD: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) - - - if (pSels->has_line_feed()) pNewSelector->has_line_feed(true); - - holder.push_back(std::make_pair(pSels, pNewSelector)); - } - - - for (SubSetMapLookup& pair : holder) { - - Compound_Selector_Obj pSels = pair.first; - Complex_Selector_Obj pNewSelector = pair.second; - - - // RUBY??: next [] if seen.include?(sels) - if (seen.find(pSels) != seen.end()) { - continue; - } - - - CompoundSelectorSet recurseSeen(seen); - recurseSeen.insert(pSels); - - - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector)) - Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, recurseSeen, isReplace, false); // !:isOriginal - - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) - - for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end(); - iterator != endIterator; ++iterator) { - Node newSelector = *iterator; - -// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << results) -// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << results.contains(newSelector, false /*simpleSelectorOrderDependent*/)); - - if (!results.contains(newSelector)) { -// DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR") - results.collection()->push_back(newSelector); - } - } - } - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) - - // this turned out to be too much overhead - // memory results in a map table - since extending is very expensive - // memoizeCompound.insert(std::pair(pSelector, results)); - - return results; - } - - - // check if selector has something to be extended by subset_map - bool Extend::complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen) { - - bool hasExtension = false; - - Complex_Selector_Obj pIter = selector; - - while (!hasExtension && pIter) { - Compound_Selector_Obj pHead = pIter->head(); - - if (pHead) { - SubSetMapPairs entries = subset_map.get_v(pHead); - for (SubSetMapPair ext : entries) { - // check if both selectors have the same media block parent - // if (ext.first->media_block() == pComplexSelector->media_block()) continue; - if (ext.second->media_block() == 0) continue; - if (pHead->media_block() && - ext.second->media_block()->media_queries() && - pHead->media_block()->media_queries() - ) { - std::string query_left(ext.second->media_block()->media_queries()->to_string()); - std::string query_right(pHead->media_block()->media_queries()->to_string()); - if (query_left == query_right) continue; - } - - // fail if one goes across media block boundaries - std::stringstream err; - std::string cwd(Sass::File::get_cwd()); - ParserState pstate(ext.second->pstate()); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - err << "You may not @extend an outer selector from within @media.\n"; - err << "You may only @extend selectors within the same directive.\n"; - err << "From \"@extend " << ext.second->to_string() << "\""; - err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; - error(err.str(), selector->pstate(), eval->exp.traces); - } - if (entries.size() > 0) hasExtension = true; - } - - pIter = pIter->tail(); - } - - return hasExtension; - } - - - /* - This is the equivalent of ruby's Sequence.do_extend. - - // TODO: I think I have some modified ruby code to put here. Check. - */ - /* - ISSUES: - - check to automatically include combinators doesn't transfer over to libsass' data model where - the combinator and compound selector are one unit - next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) - */ - Node Extend::extendComplexSelector(Complex_Selector_Ptr selector, CompoundSelectorSet& seen, bool isReplace, bool isOriginal) { - - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeComplex.find(selector); - if (memoized != memoizeComplex.end()) { - return memoized->second; - } - - // convert the input selector to extend node format - Node complexSelector = complexSelectorToNode(selector); - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) - - // let CHOICES be an empty list of selector-lists - // create new collection to hold the results - Node choices = Node::createCollection(); - - // for each compound selector COMPOUND in COMPLEX: - for (Node& sseqOrOp : *complexSelector.collection()) { - - DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) - - // If it's not a selector (meaning it's a combinator), just include it automatically - // RUBY: next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) - if (!sseqOrOp.isSelector()) { - // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node - // with one collection child. The collection child represents a Complex_Selector that is only a combinator. - Node outer = Node::createCollection(); - Node inner = Node::createCollection(); - outer.collection()->push_back(inner); - inner.collection()->push_back(sseqOrOp); - choices.collection()->push_back(outer); - continue; - } - - // verified now that node is a valid selector - Complex_Selector_Obj sseqSel = sseqOrOp.selector(); - Compound_Selector_Obj sseqHead = sseqSel->head(); - - // let EXTENDED be extend_compound(COMPOUND, SEEN) - // extend the compound selector against the given subset_map - // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) - Node extended = extendCompoundSelector(sseqHead, seen, isReplace); // slow(17%)! - if (sseqOrOp.got_line_feed) extended.got_line_feed = true; - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) - - // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with a ruby - // Array instead of a Sequence due to the member mapping: choices = extended.map {|seq| seq.members} - // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? - if (isOriginal && !selector->has_placeholder()) { - ComplexSelectorSet srcset; - srcset.insert(selector); - sseqSel->addSources(srcset); - // DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) - } - - bool isSuperselector = false; - // if no complex selector in EXTENDED is a superselector of COMPOUND: - for (Node& childNode : *extended.collection()) { - Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode); - if (pExtensionSelector->is_superselector_of(sseqSel)) { - isSuperselector = true; - break; - } - } - - if (!isSuperselector) { - // add a complex selector composed only of COMPOUND to EXTENDED - if (sseqOrOp.got_line_feed) sseqSel->has_line_feed(sseqOrOp.got_line_feed); - extended.collection()->push_front(complexSelectorToNode(sseqSel)); - } - - DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) - - // add EXTENDED to CHOICES - // Aggregate our current extensions - choices.collection()->push_back(extended); - } - - - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << choices) - - - - // Ruby Equivalent: paths - Node paths = Sass::paths(choices); - - DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) - - // let WEAVES be an empty list of selector lists - Node weaves = Node::createCollection(); - - // for each list of complex selectors PATH in paths(CHOICES): - for (Node& path : *paths.collection()) { - // add weave(PATH) to WEAVES - Node weaved = weave(path); // slow(12%)! - weaved.got_line_feed = path.got_line_feed; - weaves.collection()->push_back(weaved); - } - - DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) - - // Ruby Equivalent: trim - Node trimmed(trim(weaves, isReplace)); // slow(19%)! - - DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) - - // Ruby Equivalent: flatten - Node flattened(flatten(trimmed, 1)); - - DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors) - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) - - // memory results in a map table - since extending is very expensive - memoizeComplex.insert(std::pair(selector, flattened)); - - // return trim(WEAVES) - return flattened; - } - - - - /* - This is the equivalent of ruby's CommaSequence.do_extend. - */ - // We get a selector list with has something to extend and a subset_map with - // all extenders. Pick the ones that match our selectors in the list. - Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen) { - - Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); - - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeList.find(pSelectorList); - if (memoized != memoizeList.end()) { - extendedSomething = true; - return memoized->second; - } - - extendedSomething = false; - // process each comlplex selector in the selector list. - // Find the ones that can be extended by given subset_map. - for (size_t index = 0, length = pSelectorList->length(); index < length; index++) { - Complex_Selector_Obj pSelector = (*pSelectorList)[index]; - - // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that. - // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to - // run through the extend code (which does a data model transformation), check if there is anything to extend before doing - // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps - // when debugging). - if (!complexSelectorHasExtension(pSelector, seen)) { - pNewSelectors->append(pSelector); - continue; - } - - // complexSelectorHasExtension was true! - extendedSomething = true; - - // now do the actual extension of the complex selector - Node extendedSelectors = extendComplexSelector(pSelector, seen, isReplace, true); - - if (!pSelector->has_placeholder()) { - Node nSelector(complexSelectorToNode(pSelector)); - if (!extendedSelectors.contains(nSelector)) { - pNewSelectors->append(pSelector); - continue; - } - } - - bool doReplace = isReplace; - for (Node& childNode : *extendedSelectors.collection()) { - // When it is a replace, skip the first one, unless there is only one - if(doReplace && extendedSelectors.collection()->size() > 1 ) { - doReplace = false; - continue; - } - pNewSelectors->append(nodeToComplexSelector(childNode)); - } - } - - Remove_Placeholders remove_placeholders; - // it seems that we have to remove the place holders early here - // normally we do this as the very last step (compare to ruby sass) - pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors); - - // unwrap all wrapped selectors with inner lists - for (Complex_Selector_Obj cur : pNewSelectors->elements()) { - // process tails - while (cur) { - // process header - if (cur->head() && seen.find(cur->head()) == seen.end()) { - CompoundSelectorSet recseen(seen); - recseen.insert(cur->head()); - // create a copy since we add multiple items if stuff get unwrapped - Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); - for (Simple_Selector_Obj hs : *cur->head()) { - if (Wrapped_Selector_Obj ws = Cast(hs)) { - ws->selector(SASS_MEMORY_CLONE(ws->selector())); - if (Selector_List_Obj sl = Cast(ws->selector())) { - // special case for ruby ass - if (sl->empty()) { - // this seems inconsistent but it is how ruby sass seems to remove parentheses - cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name())); - } - // has wrapped not selectors - else if (ws->name() == ":not") { - // extend the inner list of wrapped selector - bool extended = false; - Selector_List_Obj ext_sl = extendSelectorList(sl, false, extended, recseen); - for (size_t i = 0; i < ext_sl->length(); i += 1) { - if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { - // create clones for wrapped selector and the inner list - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); - Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); - // remove parent selectors from inner selector - Compound_Selector_Obj ext_head = NULL; - if (ext_cs->first()) ext_head = ext_cs->first()->head(); - if (ext_head && ext_head && ext_head->length() > 0) { - cpy_ws_sl->append(ext_cs->first()); - } - // assign list to clone - cpy_ws->selector(cpy_ws_sl); - // append the clone - cpy_head->append(cpy_ws); - } - } - if (eval && extended) { - eval->exp.selector_stack.push_back(pNewSelectors); - cpy_head->perform(eval); - eval->exp.selector_stack.pop_back(); - } - } - // has wrapped selectors - else { - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); - Selector_List_Obj ext_sl = extendSelectorList(sl, recseen); - cpy_ws->selector(ext_sl); - cpy_head->append(cpy_ws); - } - } else { - cpy_head->append(hs); - } - } else { - cpy_head->append(hs); - } - } - // replace header - cur->head(cpy_head); - } - // process tail - cur = cur->tail(); - } - } - - // memory results in a map table - since extending is very expensive - memoizeList.insert(std::pair(pSelectorList, pNewSelectors)); - - return pNewSelectors.detach(); - - } - - - bool shouldExtendBlock(Block_Obj b) { - - // If a block is empty, there's no reason to extend it since any rules placed on this block - // won't have any output. The main benefit of this is for structures like: - // - // .a { - // .b { - // x: y; - // } - // } - // - // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b). - // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since - // there are no child statements. However .a .b should have extensions applied. - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - - if (Cast(stm)) { - // Do nothing. This doesn't count as a statement that causes extension since we'll - // iterate over this rule set in a future visit and try to extend it. - } - else { - return true; - } - } - - return false; - - } - - - // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. - // Every Ruleset in the whole tree is calling this function. We decide if there - // was is @extend that matches our selector. If we find one, we will go further - // and call the extend magic for our selector. The subset_map contains all blocks - // where @extend was found. Pick the ones that match our selector! - void Extend::extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject) { - - DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string()) - - // Ruby sass seems to filter nodes that don't have any content well before we get here. - // I'm not sure the repercussions of doing so, so for now, let's just not extend things - // that won't be output later. Profiling shows this may us 0.2% or so. - if (!shouldExtendBlock(pObject->block())) { - DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT") - return; - } - - bool extendedSomething = false; - - CompoundSelectorSet seen; - Selector_List_Obj pNewSelectorList = extendSelectorList(pObject->selector(), false, extendedSomething, seen); - - if (extendedSomething && pNewSelectorList) { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << pObject->selector()->to_string()) - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string()) - pNewSelectorList->remove_parent_selectors(); - pObject->selector(pNewSelectorList); - } else { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING") - } - } - - Extend::Extend(Subset_Map& ssm) - : subset_map(ssm), eval(NULL) - { } - - void Extend::setEval(Eval& e) { - eval = &e; - } - - void Extend::operator()(Block_Ptr b) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - stm->perform(this); - } - // do final check if everything was extended - // we set `extended` flag on extended selectors - if (b->is_root()) { - // debug_subset_map(subset_map); - for(auto const &it : subset_map.values()) { - Complex_Selector_Ptr sel = NULL; - Compound_Selector_Ptr ext = NULL; - if (it.first) sel = it.first->first(); - if (it.second) ext = it.second; - if (ext && (ext->extended() || ext->is_optional())) continue; - std::string str_sel(sel ? sel->to_string({ NESTED, 5 }) : "NULL"); - std::string str_ext(ext ? ext->to_string({ NESTED, 5 }) : "NULL"); - // debug_ast(sel, "sel: "); - // debug_ast(ext, "ext: "); - error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" - "The selector \"" + str_ext + "\" was not found.\n" - "Use \"@extend " + str_ext + " !optional\" if the" - " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces); - } - } - - } - - void Extend::operator()(Ruleset_Ptr pRuleset) - { - extendObjectWithSelectorAndBlock( pRuleset ); - pRuleset->block()->perform(this); - } - - void Extend::operator()(Supports_Block_Ptr pFeatureBlock) - { - pFeatureBlock->block()->perform(this); - } - - void Extend::operator()(Media_Block_Ptr pMediaBlock) - { - pMediaBlock->block()->perform(this); - } - - void Extend::operator()(Directive_Ptr a) - { - // Selector_List_Ptr ls = Cast(a->selector()); - // selector_stack.push_back(ls); - if (a->block()) a->block()->perform(this); - // exp.selector_stack.pop_back(); - } -} diff --git a/src/libsass/src/extend.hpp b/src/libsass/src/extend.hpp deleted file mode 100644 index 03042f3e2..000000000 --- a/src/libsass/src/extend.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef SASS_EXTEND_H -#define SASS_EXTEND_H - -#include -#include - -#include "ast.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "operation.hpp" -#include "subset_map.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - Node subweave(Node& one, Node& two); - - class Extend : public Operation_CRTP { - - Subset_Map& subset_map; - Eval* eval; - - void fallback_impl(AST_Node_Ptr n) { } - - private: - - std::unordered_map< - Selector_List_Obj, // key - Selector_List_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeList; - - std::unordered_map< - Complex_Selector_Obj, // key - Node, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeComplex; - - /* this turned out to be too much overhead - re-evaluate once we store an ast selector - std::unordered_map< - Compound_Selector_Obj, // key - Node, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeCompound; - */ - - void extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject); - Node extendComplexSelector(Complex_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace, bool isOriginal); - Node extendCompoundSelector(Compound_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace); - bool complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen); - Node trim(Node& seqses, bool isReplace); - Node weave(Node& path); - - public: - void setEval(Eval& eval); - Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen); - Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace = false) { - bool extendedSomething = false; - CompoundSelectorSet seen; - return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); - } - Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, CompoundSelectorSet& seen) { - bool isReplace = false; - bool extendedSomething = false; - return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); - } - Extend(Subset_Map&); - ~Extend() { } - - void operator()(Block_Ptr); - void operator()(Ruleset_Ptr); - void operator()(Supports_Block_Ptr); - void operator()(Media_Block_Ptr); - void operator()(Directive_Ptr); - - template - void fallback(U x) { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp deleted file mode 100644 index ab2065194..000000000 --- a/src/libsass/src/file.cpp +++ /dev/null @@ -1,485 +0,0 @@ -#include "sass.hpp" -#ifdef _WIN32 -# ifdef __MINGW32__ -# ifndef off64_t -# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ -# endif -# endif -# include -# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#else -# include -#endif -#include -#include -#include -#include -#include -#include -#include "file.hpp" -#include "context.hpp" -#include "prelexer.hpp" -#include "utf8_string.hpp" -#include "sass_functions.hpp" -#include "sass2scss.h" - -#ifdef _WIN32 -# include - -# ifdef _MSC_VER -# include -inline static std::string wstring_to_string(const std::wstring& wstr) -{ - std::wstring_convert, wchar_t> wchar_converter; - return wchar_converter.to_bytes(wstr); -} -# else // mingw(/gcc) does not support C++11's codecvt yet. -inline static std::string wstring_to_string(const std::wstring &wstr) -{ - int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); - std::string strTo(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); - return strTo; -} -# endif -#endif - -namespace Sass { - namespace File { - - // return the current directory - // always with forward slashes - // always with trailing slash - std::string get_cwd() - { - const size_t wd_len = 4096; - #ifndef _WIN32 - char wd[wd_len]; - char* pwd = getcwd(wd, wd_len); - // we should check error for more detailed info (e.g. ENOENT) - // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS - if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); - std::string cwd = pwd; - #else - wchar_t wd[wd_len]; - wchar_t* pwd = _wgetcwd(wd, wd_len); - if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); - std::string cwd = wstring_to_string(pwd); - //convert backslashes to forward slashes - replace(cwd.begin(), cwd.end(), '\\', '/'); - #endif - if (cwd[cwd.length() - 1] != '/') cwd += '/'; - return cwd; - } - - // test if path exists and is a file - bool file_exists(const std::string& path) - { - #ifdef _WIN32 - wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - std::string abspath(join_paths(get_cwd(), path)); - std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); - std::replace(wpath.begin(), wpath.end(), '/', '\\'); - DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); - if (rv > 32767) throw Exception::OperationError("Path is too long"); - if (rv == 0) throw Exception::OperationError("Path could not be resolved"); - DWORD dwAttrib = GetFileAttributesW(resolved); - return (dwAttrib != INVALID_FILE_ATTRIBUTES && - (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); - #else - struct stat st_buf; - return (stat (path.c_str(), &st_buf) == 0) && - (!S_ISDIR (st_buf.st_mode)); - #endif - } - - // return if given path is absolute - // works with *nix and windows paths - bool is_absolute_path(const std::string& path) - { - #ifdef _WIN32 - if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; - #endif - size_t i = 0; - // check if we have a protocol - if (path[i] && Prelexer::is_alpha(path[i])) { - // skip over all alphanumeric characters - while (path[i] && Prelexer::is_alnum(path[i])) ++i; - i = i && path[i] == ':' ? i + 1 : 0; - } - return path[i] == '/'; - } - - // helper function to find the last directory seperator - inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) - { - size_t pos; - size_t pos_p = path.find_last_of('/', limit); - #ifdef _WIN32 - size_t pos_w = path.find_last_of('\\', limit); - #else - size_t pos_w = std::string::npos; - #endif - if (pos_p != std::string::npos && pos_w != std::string::npos) { - pos = std::max(pos_p, pos_w); - } - else if (pos_p != std::string::npos) { - pos = pos_p; - } - else { - pos = pos_w; - } - return pos; - } - - // return only the directory part of path - std::string dir_name(const std::string& path) - { - size_t pos = find_last_folder_separator(path); - if (pos == std::string::npos) return ""; - else return path.substr(0, pos+1); - } - - // return only the filename part of path - std::string base_name(const std::string& path) - { - size_t pos = find_last_folder_separator(path); - if (pos == std::string::npos) return path; - else return path.substr(pos+1); - } - - // do a logical clean up of the path - // no physical check on the filesystem - std::string make_canonical_path (std::string path) - { - - // declarations - size_t pos; - - #ifdef _WIN32 - //convert backslashes to forward slashes - replace(path.begin(), path.end(), '\\', '/'); - #endif - - pos = 0; // remove all self references inside the path string - while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); - - // remove all leading and trailing self references - while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); - while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); - - - size_t proto = 0; - // check if we have a protocol - if (path[proto] && Prelexer::is_alpha(path[proto])) { - // skip over all alphanumeric characters - while (path[proto] && Prelexer::is_alnum(path[proto++])) {} - // then skip over the mandatory colon - if (proto && path[proto] == ':') ++ proto; - } - - // then skip over start slashes - while (path[proto++] == '/') {} - - pos = proto; // collapse multiple delimiters into a single one - while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1); - - return path; - - } - - // join two path segments cleanly together - // but only if right side is not absolute yet - std::string join_paths(std::string l, std::string r) - { - - #ifdef _WIN32 - // convert Windows backslashes to URL forward slashes - replace(l.begin(), l.end(), '\\', '/'); - replace(r.begin(), r.end(), '\\', '/'); - #endif - - if (l.empty()) return r; - if (r.empty()) return l; - - if (is_absolute_path(r)) return r; - if (l[l.length()-1] != '/') l += '/'; - - // this does a logical cleanup of the right hand path - // Note that this does collapse x/../y sections into y. - // This is by design. If /foo on your system is a symlink - // to /bar/baz, then /foo/../cd is actually /bar/cd, - // not /cd as a naive ../ removal would give you. - // will only work on leading double dot dirs on rhs - // therefore it is safe if lhs is already resolved cwd - while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { - size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); - bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); - bool is_self = pos + 3 == L && (l[pos+1] == '.'); - if (!is_self && !is_slash) r = r.substr(3); - else if (pos == std::string::npos) break; - l = l.substr(0, pos == std::string::npos ? pos : pos + 1); - } - - return l + r; - } - - std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path) - { - // magic algorith goes here!! - - // if the file is outside this directory show the absolute path - if (rel_path.substr(0, 3) == "../") { - return orig_path; - } - // this seems to work most of the time - return abs_path == orig_path ? abs_path : rel_path; - } - - // create an absolute path by resolving relative paths with cwd - std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd) - { - return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); - } - - // create a path that is relative to the given base directory - // path and base will first be resolved against cwd to make them absolute - std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd) - { - - std::string abs_path = rel2abs(path, cwd); - std::string abs_base = rel2abs(base, cwd); - - size_t proto = 0; - // check if we have a protocol - if (path[proto] && Prelexer::is_alpha(path[proto])) { - // skip over all alphanumeric characters - while (path[proto] && Prelexer::is_alnum(path[proto++])) {} - // then skip over the mandatory colon - if (proto && path[proto] == ':') ++ proto; - } - - // distinguish between windows absolute paths and valid protocols - // we assume that protocols must at least have two chars to be valid - if (proto && path[proto++] == '/' && proto > 3) return path; - - #ifdef _WIN32 - // absolute link must have a drive letter, and we know that we - // can only create relative links if both are on the same drive - if (abs_base[0] != abs_path[0]) return abs_path; - #endif - - std::string stripped_uri = ""; - std::string stripped_base = ""; - - size_t index = 0; - size_t minSize = std::min(abs_path.size(), abs_base.size()); - for (size_t i = 0; i < minSize; ++i) { - #ifdef FS_CASE_SENSITIVE - if (abs_path[i] != abs_base[i]) break; - #else - // compare the charactes in a case insensitive manner - // windows fs is only case insensitive in ascii ranges - if (tolower(abs_path[i]) != tolower(abs_base[i])) break; - #endif - if (abs_path[i] == '/') index = i + 1; - } - for (size_t i = index; i < abs_path.size(); ++i) { - stripped_uri += abs_path[i]; - } - for (size_t i = index; i < abs_base.size(); ++i) { - stripped_base += abs_base[i]; - } - - size_t left = 0; - size_t directories = 0; - for (size_t right = 0; right < stripped_base.size(); ++right) { - if (stripped_base[right] == '/') { - if (stripped_base.substr(left, 2) != "..") { - ++directories; - } - else if (directories > 1) { - --directories; - } - else { - directories = 0; - } - left = right + 1; - } - } - - std::string result = ""; - for (size_t i = 0; i < directories; ++i) { - result += "../"; - } - result += stripped_uri; - - return result; - } - - // Resolution order for ambiguous imports: - // (1) filename as given - // (2) underscore + given - // (3) underscore + given + extension - // (4) given + extension - std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) - { - std::string filename = join_paths(root, file); - // split the filename - std::string base(dir_name(file)); - std::string name(base_name(file)); - std::vector includes; - // create full path (maybe relative) - std::string rel_path(join_paths(base, name)); - std::string abs_path(join_paths(root, rel_path)); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test variation with underscore - rel_path = join_paths(base, "_" + name); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test exts plus underscore - for(auto ext : exts) { - rel_path = join_paths(base, "_" + name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); - } - // next test plain name with exts - for(auto ext : exts) { - rel_path = join_paths(base, name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); - } - // nothing found - return includes; - } - - std::vector find_files(const std::string& file, const std::vector paths) - { - std::vector includes; - for (std::string path : paths) { - std::string abs_path(join_paths(path, file)); - if (file_exists(abs_path)) includes.push_back(abs_path); - } - return includes; - } - - std::vector find_files(const std::string& file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - // struct Sass_Options* options = sass_compiler_get_options(compiler); - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const std::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - std::vector paths(1 + incs.size()); - paths.push_back(dir_name(import->abs_path)); - paths.insert(paths.end(), incs.begin(), incs.end()); - // dispatch to find files in paths - return find_files(file, paths); - } - - // helper function to search one file in all include paths - // this is normally not used internally by libsass (C-API sugar) - std::string find_file(const std::string& file, const std::vector paths) - { - if (file.empty()) return file; - auto res = find_files(file, paths); - return res.empty() ? "" : res.front(); - } - - // helper function to resolve a filename - std::string find_include(const std::string& file, const std::vector paths) - { - // search in every include path for a match - for (size_t i = 0, S = paths.size(); i < S; ++i) - { - std::vector resolved(resolve_includes(paths[i], file)); - if (resolved.size()) return resolved[0].abs_path; - } - // nothing found - return std::string(""); - } - - // try to load the given filename - // returned memory must be freed - // will auto convert .sass files - char* read_file(const std::string& path) - { - #ifdef _WIN32 - BYTE* pBuffer; - DWORD dwBytes; - wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - std::string abspath(join_paths(get_cwd(), path)); - std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); - std::replace(wpath.begin(), wpath.end(), '/', '\\'); - DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); - if (rv > 32767) throw Exception::OperationError("Path is too long"); - if (rv == 0) throw Exception::OperationError("Path could not be resolved"); - HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hFile == INVALID_HANDLE_VALUE) return 0; - DWORD dwFileLength = GetFileSize(hFile, NULL); - if (dwFileLength == INVALID_FILE_SIZE) return 0; - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); - ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); - pBuffer[dwFileLength+0] = '\0'; - pBuffer[dwFileLength+1] = '\0'; - CloseHandle(hFile); - // just convert from unsigned char* - char* contents = (char*) pBuffer; - #else - struct stat st; - if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0; - std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - char* contents = 0; - if (file.is_open()) { - size_t size = file.tellg(); - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - contents = (char*) malloc((size+2)*sizeof(char)); - file.seekg(0, std::ios::beg); - file.read(contents, size); - contents[size+0] = '\0'; - contents[size+1] = '\0'; - file.close(); - } - #endif - std::string extension; - if (path.length() > 5) { - extension = path.substr(path.length() - 5, 5); - } - for(size_t i=0; i split_path_list(const char* str) - { - std::vector paths; - if (str == NULL) return paths; - // find delimiter via prelexer (return zero at end) - const char* end = Prelexer::find_first(str); - // search until null delimiter - while (end) { - // add path from current position to delimiter - paths.push_back(std::string(str, end - str)); - str = end + 1; // skip delimiter - end = Prelexer::find_first(str); - } - // add path from current position to end - paths.push_back(std::string(str)); - // return back - return paths; - } - - } -} diff --git a/src/libsass/src/file.hpp b/src/libsass/src/file.hpp deleted file mode 100644 index a043bea7a..000000000 --- a/src/libsass/src/file.hpp +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef SASS_FILE_H -#define SASS_FILE_H - -#include -#include - -#include "sass/context.h" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - namespace File { - - // return the current directory - // always with forward slashes - std::string get_cwd(); - - // test if path exists and is a file - bool file_exists(const std::string& file); - - // return if given path is absolute - // works with *nix and windows paths - bool is_absolute_path(const std::string& path); - - // return only the directory part of path - std::string dir_name(const std::string& path); - - // return only the filename part of path - std::string base_name(const std::string&); - - // do a locigal clean up of the path - // no physical check on the filesystem - std::string make_canonical_path (std::string path); - - // join two path segments cleanly together - // but only if right side is not absolute yet - std::string join_paths(std::string root, std::string name); - - // if the relative path is outside of the cwd we want want to - // show the absolute path in console messages - std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path); - - // create an absolute path by resolving relative paths with cwd - std::string rel2abs(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); - - // create a path that is relative to the given base directory - // path and base will first be resolved against cwd to make them absolute - std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); - - // helper function to resolve a filename - // searching without variations in all paths - std::string find_file(const std::string& file, struct Sass_Compiler* options); - std::string find_file(const std::string& file, const std::vector paths); - - // helper function to resolve a include filename - // this has the original resolve logic for sass include - std::string find_include(const std::string& file, const std::vector paths); - - // split a path string delimited by semicolons or colons (OS dependent) - std::vector split_path_list(const char* paths); - - // try to load the given filename - // returned memory must be freed - // will auto convert .sass files - char* read_file(const std::string& file); - - } - - // requested import - class Importer { - public: - // requested import path - std::string imp_path; - // parent context path - std::string ctx_path; - // base derived from context path - // this really just acts as a cache - std::string base_path; - public: - Importer(std::string imp_path, std::string ctx_path) - : imp_path(File::make_canonical_path(imp_path)), - ctx_path(File::make_canonical_path(ctx_path)), - base_path(File::dir_name(ctx_path)) - { } - }; - - // a resolved include (final import) - class Include : public Importer { - public: - // resolved absolute path - std::string abs_path; - // is a deprecated file type - bool deprecated; - public: - Include(const Importer& imp, std::string abs_path, bool deprecated) - : Importer(imp), abs_path(abs_path), deprecated(deprecated) - { } - Include(const Importer& imp, std::string abs_path) - : Importer(imp), abs_path(abs_path), deprecated(false) - { } - }; - - // a loaded resource - class Resource { - public: - // the file contents - char* contents; - // conected sourcemap - char* srcmap; - public: - Resource(char* contents, char* srcmap) - : contents(contents), srcmap(srcmap) - { } - }; - - // parsed stylesheet from loaded resource - class StyleSheet : public Resource { - public: - // parsed root block - Block_Obj root; - public: - StyleSheet(const Resource& res, Block_Obj root) - : Resource(res), root(root) - { } - }; - - namespace File { - - static std::vector defaultExtensions = { ".scss", ".sass" }; - - std::vector resolve_includes(const std::string& root, const std::string& file, - const std::vector& exts = defaultExtensions); - - - } - -} - -#endif diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp deleted file mode 100644 index c9999fc3a..000000000 --- a/src/libsass/src/functions.cpp +++ /dev/null @@ -1,2234 +0,0 @@ -#include "sass.hpp" -#include "functions.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "parser.hpp" -#include "constants.hpp" -#include "inspect.hpp" -#include "extend.hpp" -#include "eval.hpp" -#include "util.hpp" -#include "expand.hpp" -#include "operators.hpp" -#include "utf8_string.hpp" -#include "sass/base.h" -#include "utf8.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __MINGW32__ -#include "windows.h" -#include "wincrypt.h" -#endif - -#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) -#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) - -// return a number object (copied since we want to have reduced units) -#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy - -// special function for weird hsla percent (10px == 10% == 10 != 0.1) -#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double - -// macros for common ranges (u mean unsigned or upper, r for full range) -#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double -#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double -#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double -#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double -#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double -#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double - -// macros for color related inputs (rbg and alpha/opacity values) -#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double -#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double - -namespace Sass { - using std::stringstream; - using std::endl; - - Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) - { - Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); - sig_parser.lex(); - std::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - ParserState("[built-in function]"), - sig, - name, - params, - func, - false); - } - - Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) - { - using namespace Prelexer; - - const char* sig = sass_function_get_signature(c_func); - Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); - // allow to overload generic callback plus @warn, @error and @debug with custom functions - sig_parser.lex < alternatives < identifier, exactly <'*'>, - exactly < Constants::warn_kwd >, - exactly < Constants::error_kwd >, - exactly < Constants::debug_kwd > - > >(); - std::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - ParserState("[c function]"), - sig, - name, - params, - c_func, - false, true); - } - - std::string function_name(Signature sig) - { - std::string str(sig); - return str.substr(0, str.find('(')); - } - - namespace Functions { - - inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) - { - try { - throw; - } - catch (utf8::invalid_code_point) { - std::string msg("utf8::invalid_code_point"); - error(msg, pstate, traces); - } - catch (utf8::not_enough_room) { - std::string msg("utf8::not_enough_room"); - error(msg, pstate, traces); - } - catch (utf8::invalid_utf8) { - std::string msg("utf8::invalid_utf8"); - error(msg, pstate, traces); - } - catch (...) { throw; } - } - - template - T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - T* val = Cast(env[argname]); - if (!val) { - std::string msg("argument `"); - msg += argname; - msg += "` of `"; - msg += sig; - msg += "` must be a "; - msg += T::type_name(); - error(msg, pstate, traces); - } - return val; - } - - Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Map_Ptr val = Cast(env[argname]); - if (val) return val; - - List_Ptr lval = Cast(env[argname]); - if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); - - // fallback on get_arg for error handling - val = get_arg(argname, env, sig, pstate, traces); - return val; - } - - double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - double v = tmpnr.value(); - if (!(lo <= v && v <= hi)) { - std::stringstream msg; - msg << "argument `" << argname << "` of `" << sig << "` must be between "; - msg << lo << " and " << hi; - error(msg.str(), pstate, traces); - } - return v; - } - - Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - val = SASS_MEMORY_COPY(val); - val->reduce(); - return val; - } - - double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - /* - if (tmpnr.unit() == "%") { - tmpnr.value(tmpnr.value() / 100); - tmpnr.numerators.clear(); - } else { - if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); - } - */ - return tmpnr.value(); - } - - double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr.value(); - } - - double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 255.0); - } - } - - - inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value(), 0.0), 100.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 1.0); - } - } - - #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) - - template - T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); - - template <> - Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { - Expression_Obj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << argname << ": null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - return Parser::parse_selector(exp_src.c_str(), ctx, traces); - } - - template <> - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { - Expression_Obj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); - if (sel_list->length() == 0) return NULL; - Complex_Selector_Obj first = sel_list->first(); - if (!first->tail()) return first->head(); - return first->tail()->head(); - } - - #ifdef __MINGW32__ - uint64_t GetSeed() - { - HCRYPTPROV hp = 0; - BYTE rb[8]; - CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); - CryptGenRandom(hp, sizeof(rb), rb); - CryptReleaseContext(hp, 0); - - uint64_t seed; - memcpy(&seed, &rb[0], sizeof(seed)); - - return seed; - } - #else - uint64_t GetSeed() - { - std::random_device rd; - return rd(); - } - #endif - - // note: the performance of many implementations of - // random_device degrades sharply once the entropy pool - // is exhausted. For practical use, random_device is - // generally only used to seed a PRNG such as mt19937. - static std::mt19937 rand(static_cast(GetSeed())); - - // features - static std::set features { - "global-variable-shadowing", - "extend-selector-pseudoclass", - "at-error", - "units-level-3", - "custom-property" - }; - - //////////////// - // RGB FUNCTIONS - //////////////// - - inline bool special_number(String_Constant_Ptr s) { - if (s) { - std::string calc("calc("); - std::string var("var("); - std::string ss(s->value()); - return std::equal(calc.begin(), calc.end(), ss.begin()) || - std::equal(var.begin(), var.end(), ss.begin()); - } - return false; - } - - Signature rgb_sig = "rgb($red, $green, $blue)"; - BUILT_IN(rgb) - { - if ( - special_number(Cast(env["$red"])) || - special_number(Cast(env["$green"])) || - special_number(Cast(env["$blue"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue")); - } - - Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; - BUILT_IN(rgba_4) - { - if ( - special_number(Cast(env["$red"])) || - special_number(Cast(env["$green"])) || - special_number(Cast(env["$blue"])) || - special_number(Cast(env["$alpha"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue"), - ALPHA_NUM("$alpha")); - } - - Signature rgba_2_sig = "rgba($color, $alpha)"; - BUILT_IN(rgba_2) - { - if ( - special_number(Cast(env["$color"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$color"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - Color_Ptr c_arg = ARG("$color", Color); - - if ( - special_number(Cast(env["$alpha"])) - ) { - std::stringstream strm; - strm << "rgba(" - << (int)c_arg->r() << ", " - << (int)c_arg->g() << ", " - << (int)c_arg->b() << ", " - << env["$alpha"]->to_string() - << ")"; - return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); - } - - Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); - new_c->a(ALPHA_NUM("$alpha")); - new_c->disp(""); - return new_c; - } - - Signature red_sig = "red($color)"; - BUILT_IN(red) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } - - Signature green_sig = "green($color)"; - BUILT_IN(green) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } - - Signature blue_sig = "blue($color)"; - BUILT_IN(blue) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } - - Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { - double p = weight/100; - double w = 2*p - 1; - double a = color1->a() - color2->a(); - - double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; - double w2 = 1 - w1; - - return SASS_MEMORY_NEW(Color, - pstate, - Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), - Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), - Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), - color1->a()*p + color2->a()*(1-p)); - } - - Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; - BUILT_IN(mix) - { - Color_Obj color1 = ARG("$color-1", Color); - Color_Obj color2 = ARG("$color-2", Color); - double weight = DARG_U_PRCT("$weight"); - return colormix(ctx, pstate, color1, color2, weight); - - } - - //////////////// - // HSL FUNCTIONS - //////////////// - - // RGB to HSL helper function - struct HSL { double h; double s; double l; }; - HSL rgb_to_hsl(double r, double g, double b) - { - - // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV - r /= 255.0; g /= 255.0; b /= 255.0; - - double max = std::max(r, std::max(g, b)); - double min = std::min(r, std::min(g, b)); - double delta = max - min; - - double h = 0; - double s; - double l = (max + min) / 2.0; - - if (NEAR_EQUAL(max, min)) { - h = s = 0; // achromatic - } - else { - if (l < 0.5) s = delta / (max + min); - else s = delta / (2.0 - max - min); - - if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); - else if (g == max) h = (b - r) / delta + 2; - else if (b == max) h = (r - g) / delta + 4; - } - - HSL hsl_struct; - hsl_struct.h = h / 6 * 360; - hsl_struct.s = s * 100; - hsl_struct.l = l * 100; - - return hsl_struct; - } - - // hue to RGB helper function - double h_to_rgb(double m1, double m2, double h) { - while (h < 0) h += 1; - while (h > 1) h -= 1; - if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; - if (h*2.0 < 1) return m2; - if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; - return m1; - } - - Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) - { - h /= 360.0; - s /= 100.0; - l /= 100.0; - - if (l < 0) l = 0; - if (s < 0) s = 0; - if (l > 1) l = 1; - if (s > 1) s = 1; - while (h < 0) h += 1; - while (h > 1) h -= 1; - - // if saturation is exacly zero, we loose - // information for hue, since it will evaluate - // to zero if converted back from rgb. Setting - // saturation to a very tiny number solves this. - if (s == 0) s = 1e-10; - - // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. - double m2; - if (l <= 0.5) m2 = l*(s+1.0); - else m2 = (l+s)-(l*s); - double m1 = (l*2.0)-m2; - // round the results -- consider moving this into the Color constructor - double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); - double g = (h_to_rgb(m1, m2, h) * 255.0); - double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); - - return SASS_MEMORY_NEW(Color, pstate, r, g, b, a); - } - - Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; - BUILT_IN(hsl) - { - if ( - special_number(Cast(env["$hue"])) || - special_number(Cast(env["$saturation"])) || - special_number(Cast(env["$lightness"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ")" - ); - } - - return hsla_impl(ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - 1.0, - ctx, - pstate); - } - - Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; - BUILT_IN(hsla) - { - if ( - special_number(Cast(env["$hue"])) || - special_number(Cast(env["$saturation"])) || - special_number(Cast(env["$lightness"])) || - special_number(Cast(env["$alpha"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - return hsla_impl(ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - ARGVAL("$alpha"), - ctx, - pstate); - } - - Signature hue_sig = "hue($color)"; - BUILT_IN(hue) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); - } - - Signature saturation_sig = "saturation($color)"; - BUILT_IN(saturation) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); - } - - Signature lightness_sig = "lightness($color)"; - BUILT_IN(lightness) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); - } - - Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; - BUILT_IN(adjust_hue) - { - Color_Ptr rgb_color = ARG("$color", Color); - double degrees = ARGVAL("$degrees"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h + degrees, - hsl_color.s, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature lighten_sig = "lighten($color, $amount)"; - BUILT_IN(lighten) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - //Check lightness is not negative before lighten it - double hslcolorL = hsl_color.l; - if (hslcolorL < 0) { - hslcolorL = 0; - } - - return hsla_impl(hsl_color.h, - hsl_color.s, - hslcolorL + amount, - rgb_color->a(), - ctx, - pstate); - } - - Signature darken_sig = "darken($color, $amount)"; - BUILT_IN(darken) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - //Check lightness if not over 100, before darken it - double hslcolorL = hsl_color.l; - if (hslcolorL > 100) { - hslcolorL = 100; - } - - return hsla_impl(hsl_color.h, - hsl_color.s, - hslcolorL - amount, - rgb_color->a(), - ctx, - pstate); - } - - Signature saturate_sig = "saturate($color, $amount: false)"; - BUILT_IN(saturate) - { - // CSS3 filter function overload: pass literal through directly - if (!Cast(env["$amount"])) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); - } - - double amount = DARG_U_PRCT("$amount"); - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - double hslcolorS = hsl_color.s + amount; - - // Saturation cannot be below 0 or above 100 - if (hslcolorS < 0) { - hslcolorS = 0; - } - if (hslcolorS > 100) { - hslcolorS = 100; - } - - return hsla_impl(hsl_color.h, - hslcolorS, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature desaturate_sig = "desaturate($color, $amount)"; - BUILT_IN(desaturate) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - double hslcolorS = hsl_color.s - amount; - - // Saturation cannot be below 0 or above 100 - if (hslcolorS <= 0) { - hslcolorS = 0; - } - if (hslcolorS > 100) { - hslcolorS = 100; - } - - return hsla_impl(hsl_color.h, - hslcolorS, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature grayscale_sig = "grayscale($color)"; - BUILT_IN(grayscale) - { - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); - } - - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h, - 0.0, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature complement_sig = "complement($color)"; - BUILT_IN(complement) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h - 180.0, - hsl_color.s, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature invert_sig = "invert($color, $weight: 100%)"; - BUILT_IN(invert) - { - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); - } - - double weight = DARG_U_PRCT("$weight"); - Color_Ptr rgb_color = ARG("$color", Color); - Color_Obj inv = SASS_MEMORY_NEW(Color, - pstate, - 255 - rgb_color->r(), - 255 - rgb_color->g(), - 255 - rgb_color->b(), - rgb_color->a()); - return colormix(ctx, pstate, inv, rgb_color, weight); - } - - //////////////////// - // OPACITY FUNCTIONS - //////////////////// - Signature alpha_sig = "alpha($color)"; - Signature opacity_sig = "opacity($color)"; - BUILT_IN(alpha) - { - String_Constant_Ptr ie_kwd = Cast(env["$color"]); - if (ie_kwd) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); - } - - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); - } - - return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); - } - - Signature opacify_sig = "opacify($color, $amount)"; - Signature fade_in_sig = "fade-in($color, $amount)"; - BUILT_IN(opacify) - { - Color_Ptr color = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - double alpha = std::min(color->a() + amount, 1.0); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - - Signature transparentize_sig = "transparentize($color, $amount)"; - Signature fade_out_sig = "fade-out($color, $amount)"; - BUILT_IN(transparentize) - { - Color_Ptr color = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - double alpha = std::max(color->a() - amount, 0.0); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - - //////////////////////// - // OTHER COLOR FUNCTIONS - //////////////////////// - - Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(adjust_color) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); - } - if (rgb) { - double rr = r ? DARG_R_BYTE("$red") : 0; - double gg = g ? DARG_R_BYTE("$green") : 0; - double bb = b ? DARG_R_BYTE("$blue") : 0; - double aa = a ? DARG_R_FACT("$alpha") : 0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r() + rr, - color->g() + gg, - color->b() + bb, - color->a() + aa); - } - if (hsl) { - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - double ss = s ? DARG_R_PRCT("$saturation") : 0; - double ll = l ? DARG_R_PRCT("$lightness") : 0; - double aa = a ? DARG_R_FACT("$alpha") : 0; - return hsla_impl(hsl_struct.h + (h ? h->value() : 0), - hsl_struct.s + ss, - hsl_struct.l + ll, - color->a() + aa, - ctx, - pstate); - } - if (a) { - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - color->a() + (a ? a->value() : 0)); - } - error("not enough arguments for `adjust-color'", pstate, traces); - // unreachable - return color; - } - - Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(scale_color) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); - } - if (rgb) { - double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; - double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; - double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), - color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), - color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), - color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); - } - if (hsl) { - double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; - double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; - double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); - hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); - hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); - double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); - return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); - } - if (a) { - double ascale = (DARG_R_PRCT("$alpha")) / 100.0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); - } - error("not enough arguments for `scale-color'", pstate, traces); - // unreachable - return color; - } - - Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(change_color) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); - } - if (rgb) { - return SASS_MEMORY_NEW(Color, - pstate, - r ? DARG_U_BYTE("$red") : color->r(), - g ? DARG_U_BYTE("$green") : color->g(), - b ? DARG_U_BYTE("$blue") : color->b(), - a ? DARG_U_BYTE("$alpha") : color->a()); - } - if (hsl) { - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - if (h) hsl_struct.h = std::fmod(h->value(), 360.0); - if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); - if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); - double alpha = a ? DARG_U_FACT("$alpha") : color->a(); - return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); - } - if (a) { - double alpha = DARG_U_FACT("$alpha"); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - error("not enough arguments for `change-color'", pstate, traces); - // unreachable - return color; - } - - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - Signature ie_hex_str_sig = "ie-hex-str($color)"; - BUILT_IN(ie_hex_str) - { - Color_Ptr c = ARG("$color", Color); - double r = cap_channel<0xff>(c->r()); - double g = cap_channel<0xff>(c->g()); - double b = cap_channel<0xff>(c->b()); - double a = cap_channel<1> (c->a()) * 255; - - std::stringstream ss; - ss << '#' << std::setw(2) << std::setfill('0'); - ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); - - std::string result(ss.str()); - for (size_t i = 0, L = result.length(); i < L; ++i) { - result[i] = std::toupper(result[i]); - } - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - /////////////////// - // STRING FUNCTIONS - /////////////////// - - Signature unquote_sig = "unquote($string)"; - BUILT_IN(sass_unquote) - { - AST_Node_Obj arg = env["$string"]; - if (String_Quoted_Ptr string_quoted = Cast(arg)) { - String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); - // remember if the string was quoted (color tokens) - result->is_delayed(true); // delay colors - return result; - } - else if (String_Constant_Ptr str = Cast(arg)) { - return str; - } - else if (Expression_Ptr ex = Cast(arg)) { - Sass_Output_Style oldstyle = ctx.c_options.output_style; - ctx.c_options.output_style = SASS_STYLE_NESTED; - std::string val(arg->to_string(ctx.c_options)); - val = Cast(arg) ? "null" : val; - ctx.c_options.output_style = oldstyle; - - deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); - return ex; - } - throw std::runtime_error("Invalid Data Type for unquote"); - } - - Signature quote_sig = "quote($string)"; - BUILT_IN(sass_quote) - { - AST_Node_Obj arg = env["$string"]; - // only set quote mark to true if already a string - if (String_Quoted_Ptr qstr = Cast(arg)) { - qstr->quote_mark('*'); - return qstr; - } - // all other nodes must be converted to a string node - std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); - String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); - result->quote_mark('*'); - return result; - } - - - Signature str_length_sig = "str-length($string)"; - BUILT_IN(str_length) - { - size_t len = std::string::npos; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - len = UTF_8::code_point_count(s->value(), 0, s->value().size()); - - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)len); - } - - Signature str_insert_sig = "str-insert($string, $insert, $index)"; - BUILT_IN(str_insert) - { - std::string str; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - str = s->value(); - str = unquote(str); - String_Constant_Ptr i = ARG("$insert", String_Constant); - std::string ins = i->value(); - ins = unquote(ins); - double index = ARGVAL("$index"); - size_t len = UTF_8::code_point_count(str, 0, str.size()); - - if (index > 0 && index <= len) { - // positive and within string length - str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); - } - else if (index > len) { - // positive and past string length - str += ins; - } - else if (index == 0) { - str = ins + str; - } - else if (std::abs(index) <= len) { - // negative and within string length - index += len + 1; - str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); - } - else { - // negative and past string length - str = ins + str; - } - - if (String_Quoted_Ptr ss = Cast(s)) { - if (ss->quote_mark()) str = quote(str); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - Signature str_index_sig = "str-index($string, $substring)"; - BUILT_IN(str_index) - { - size_t index = std::string::npos; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - String_Constant_Ptr t = ARG("$substring", String_Constant); - std::string str = s->value(); - str = unquote(str); - std::string substr = t->value(); - substr = unquote(substr); - - size_t c_index = str.find(substr); - if(c_index == std::string::npos) { - return SASS_MEMORY_NEW(Null, pstate); - } - index = UTF_8::code_point_count(str, 0, c_index) + 1; - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)index); - } - - Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; - BUILT_IN(str_slice) - { - std::string newstr; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - double start_at = ARGVAL("$start-at"); - double end_at = ARGVAL("$end-at"); - String_Quoted_Ptr ss = Cast(s); - - std::string str = unquote(s->value()); - - size_t size = utf8::distance(str.begin(), str.end()); - - if (!Cast(env["$end-at"])) { - end_at = -1; - } - - if (end_at == 0 || (end_at + size) < 0) { - if (ss && ss->quote_mark()) newstr = quote(""); - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - if (end_at < 0) { - end_at += size + 1; - if (end_at == 0) end_at = 1; - } - if (end_at > size) { end_at = (double)size; } - if (start_at < 0) { - start_at += size + 1; - if (start_at < 0) start_at = 0; - } - else if (start_at == 0) { ++ start_at; } - - if (start_at <= end_at) - { - std::string::iterator start = str.begin(); - utf8::advance(start, start_at - 1, str.end()); - std::string::iterator end = start; - utf8::advance(end, end_at - start_at + 1, str.end()); - newstr = std::string(start, end); - } - if (ss) { - if(ss->quote_mark()) newstr = quote(newstr); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - Signature to_upper_case_sig = "to-upper-case($string)"; - BUILT_IN(to_upper_case) - { - String_Constant_Ptr s = ARG("$string", String_Constant); - std::string str = s->value(); - - for (size_t i = 0, L = str.length(); i < L; ++i) { - if (Sass::Util::isAscii(str[i])) { - str[i] = std::toupper(str[i]); - } - } - - if (String_Quoted_Ptr ss = Cast(s)) { - String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - Signature to_lower_case_sig = "to-lower-case($string)"; - BUILT_IN(to_lower_case) - { - String_Constant_Ptr s = ARG("$string", String_Constant); - std::string str = s->value(); - - for (size_t i = 0, L = str.length(); i < L; ++i) { - if (Sass::Util::isAscii(str[i])) { - str[i] = std::tolower(str[i]); - } - } - - if (String_Quoted_Ptr ss = Cast(s)) { - String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - /////////////////// - // NUMBER FUNCTIONS - /////////////////// - - Signature percentage_sig = "percentage($number)"; - BUILT_IN(percentage) - { - Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); - return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); - } - - Signature round_sig = "round($number)"; - BUILT_IN(round) - { - Number_Obj r = ARGN("$number"); - r->value(Sass::round(r->value(), ctx.c_options.precision)); - r->pstate(pstate); - return r.detach(); - } - - Signature ceil_sig = "ceil($number)"; - BUILT_IN(ceil) - { - Number_Obj r = ARGN("$number"); - r->value(std::ceil(r->value())); - r->pstate(pstate); - return r.detach(); - } - - Signature floor_sig = "floor($number)"; - BUILT_IN(floor) - { - Number_Obj r = ARGN("$number"); - r->value(std::floor(r->value())); - r->pstate(pstate); - return r.detach(); - } - - Signature abs_sig = "abs($number)"; - BUILT_IN(abs) - { - Number_Obj r = ARGN("$number"); - r->value(std::abs(r->value())); - r->pstate(pstate); - return r.detach(); - } - - Signature min_sig = "min($numbers...)"; - BUILT_IN(min) - { - List_Ptr arglist = ARG("$numbers", List); - Number_Obj least = NULL; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); - } - if (least) { - if (*xi < *least) least = xi; - } else least = xi; - } - return least.detach(); - } - - Signature max_sig = "max($numbers...)"; - BUILT_IN(max) - { - List_Ptr arglist = ARG("$numbers", List); - Number_Obj greatest = NULL; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); - } - if (greatest) { - if (*greatest < *xi) greatest = xi; - } else greatest = xi; - } - return greatest.detach(); - } - - Signature random_sig = "random($limit:false)"; - BUILT_IN(random) - { - AST_Node_Obj arg = env["$limit"]; - Value_Ptr v = Cast(arg); - Number_Ptr l = Cast(arg); - Boolean_Ptr b = Cast(arg); - if (l) { - double lv = l->value(); - if (lv < 1) { - stringstream err; - err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; - error(err.str(), pstate, traces); - } - bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; - if (!eq_int) { - stringstream err; - err << "Expected $limit to be an integer but got " << lv << " for `random'"; - error(err.str(), pstate, traces); - } - std::uniform_real_distribution<> distributor(1, lv + 1); - uint_fast32_t distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, (double)distributed); - } - else if (b) { - std::uniform_real_distribution<> distributor(0, 1); - double distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, distributed); - } else if (v) { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); - } else { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); - } - } - - ///////////////// - // LIST FUNCTIONS - ///////////////// - - Signature length_sig = "length($list)"; - BUILT_IN(length) - { - if (Selector_List_Ptr sl = Cast(env["$list"])) { - return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); - } - Expression_Ptr v = ARG("$list", Expression); - if (v->concrete_type() == Expression::MAP) { - Map_Ptr map = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); - } - if (v->concrete_type() == Expression::SELECTOR) { - if (Compound_Selector_Ptr h = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (Selector_List_Ptr ls = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); - } else { - return SASS_MEMORY_NEW(Number, pstate, 1); - } - } - - List_Ptr list = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, - pstate, - (double)(list ? list->size() : 1)); - } - - Signature nth_sig = "nth($list, $n)"; - BUILT_IN(nth) - { - double nr = ARGVAL("$n"); - Map_Ptr m = Cast(env["$list"]); - if (Selector_List_Ptr sl = Cast(env["$list"])) { - size_t len = m ? m->length() : sl->length(); - bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - // return (*sl)[static_cast(index)]; - Listize listize; - return (*sl)[static_cast(index)]->perform(&listize); - } - List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); - // if the argument isn't a list, then wrap it in a singleton list - if (!m && !l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - size_t len = m ? m->length() : l->length(); - bool empty = m ? m->empty() : l->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - - if (m) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(m->keys()[static_cast(index)]); - l->append(m->at(m->keys()[static_cast(index)])); - return l.detach(); - } - else { - Expression_Obj rv = l->value_at_index(static_cast(index)); - rv->set_delayed(false); - return rv.detach(); - } - } - - Signature set_nth_sig = "set-nth($list, $n, $value)"; - BUILT_IN(set_nth) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Number_Obj n = ARG("$n", Number); - Expression_Obj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); - if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - result->append(((i == index) ? v : (*l)[i])); - } - return result; - } - - Signature index_sig = "index($list, $value)"; - BUILT_IN(index) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Expression_Obj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - for (size_t i = 0, L = l->length(); i < L; ++i) { - if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); - } - return SASS_MEMORY_NEW(Null, pstate); - } - - Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; - BUILT_IN(join) - { - Map_Obj m1 = Cast(env["$list1"]); - Map_Obj m2 = Cast(env["$list2"]); - List_Obj l1 = Cast(env["$list1"]); - List_Obj l2 = Cast(env["$list2"]); - String_Constant_Obj sep = ARG("$separator", String_Constant); - enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); - Value* bracketed = ARG("$bracketed", Value); - bool is_bracketed = (l1 ? l1->is_bracketed() : false); - if (!l1) { - l1 = SASS_MEMORY_NEW(List, pstate, 1); - l1->append(ARG("$list1", Expression)); - sep_val = (l2 ? l2->separator() : SASS_SPACE); - is_bracketed = (l2 ? l2->is_bracketed() : false); - } - if (!l2) { - l2 = SASS_MEMORY_NEW(List, pstate, 1); - l2->append(ARG("$list2", Expression)); - } - if (m1) { - l1 = m1->to_list(pstate); - sep_val = SASS_COMMA; - } - if (m2) { - l2 = m2->to_list(pstate); - } - size_t len = l1->length() + l2->length(); - std::string sep_str = unquote(sep->value()); - if (sep_str == "space") sep_val = SASS_SPACE; - else if (sep_str == "comma") sep_val = SASS_COMMA; - else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - String_Constant_Obj bracketed_as_str = Cast(bracketed); - bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; - if (!bracketed_is_auto) { - is_bracketed = !bracketed->is_false(); - } - List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); - result->concat(l1); - result->concat(l2); - return result.detach(); - } - - Signature append_sig = "append($list, $val, $separator: auto)"; - BUILT_IN(append) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Expression_Obj v = ARG("$val", Expression); - if (Selector_List_Ptr sl = Cast(env["$list"])) { - Listize listize; - l = Cast(sl->perform(&listize)); - } - String_Constant_Obj sep = ARG("$separator", String_Constant); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - List_Ptr result = SASS_MEMORY_COPY(l); - std::string sep_str(unquote(sep->value())); - if (sep_str != "auto") { // check default first - if (sep_str == "space") result->separator(SASS_SPACE); - else if (sep_str == "comma") result->separator(SASS_COMMA); - else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - } - if (l->is_arglist()) { - result->append(SASS_MEMORY_NEW(Argument, - v->pstate(), - v, - "", - false, - false)); - - } else { - result->append(v); - } - return result; - } - - Signature zip_sig = "zip($lists...)"; - BUILT_IN(zip) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); - size_t shortest = 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - List_Obj ith = Cast(arglist->value_at_index(i)); - Map_Obj mith = Cast(arglist->value_at_index(i)); - if (!ith) { - if (mith) { - ith = mith->to_list(pstate); - } else { - ith = SASS_MEMORY_NEW(List, pstate, 1); - ith->append(arglist->value_at_index(i)); - } - if (arglist->is_arglist()) { - Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX - arg->value(ith); - } else { - (*arglist)[i] = ith; - } - } - shortest = (i ? std::min(shortest, ith->length()) : ith->length()); - } - List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); - size_t L = arglist->length(); - for (size_t i = 0; i < shortest; ++i) { - List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); - for (size_t j = 0; j < L; ++j) { - zipper->append(Cast(arglist->value_at_index(j))->at(i)); - } - zippers->append(zipper); - } - return zippers; - } - - Signature list_separator_sig = "list_separator($list)"; - BUILT_IN(list_separator) - { - List_Obj l = Cast(env["$list"]); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l->separator() == SASS_COMMA ? "comma" : "space"); - } - - ///////////////// - // MAP FUNCTIONS - ///////////////// - - Signature map_get_sig = "map-get($map, $key)"; - BUILT_IN(map_get) - { - // leaks for "map-get((), foo)" if not Obj - // investigate why this is (unexpected) - Map_Obj m = ARGM("$map", Map, ctx); - Expression_Obj v = ARG("$key", Expression); - try { - Expression_Obj val = m->at(v); - if (!val) return SASS_MEMORY_NEW(Null, pstate); - val->set_delayed(false); - return val.detach(); - } catch (const std::out_of_range&) { - return SASS_MEMORY_NEW(Null, pstate); - } - catch (...) { throw; } - } - - Signature map_has_key_sig = "map-has-key($map, $key)"; - BUILT_IN(map_has_key) - { - Map_Obj m = ARGM("$map", Map, ctx); - Expression_Obj v = ARG("$key", Expression); - return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); - } - - Signature map_keys_sig = "map-keys($map)"; - BUILT_IN(map_keys) - { - Map_Obj m = ARGM("$map", Map, ctx); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(key); - } - return result; - } - - Signature map_values_sig = "map-values($map)"; - BUILT_IN(map_values) - { - Map_Obj m = ARGM("$map", Map, ctx); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(m->at(key)); - } - return result; - } - - Signature map_merge_sig = "map-merge($map1, $map2)"; - BUILT_IN(map_merge) - { - Map_Obj m1 = ARGM("$map1", Map, ctx); - Map_Obj m2 = ARGM("$map2", Map, ctx); - - size_t len = m1->length() + m2->length(); - Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); - // concat not implemented for maps - *result += m1; - *result += m2; - return result; - } - - Signature map_remove_sig = "map-remove($map, $keys...)"; - BUILT_IN(map_remove) - { - bool remove; - Map_Obj m = ARGM("$map", Map, ctx); - List_Obj arglist = ARG("$keys", List); - Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, 1); - for (auto key : m->keys()) { - remove = false; - for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { - remove = Operators::eq(key, arglist->value_at_index(j)); - } - if (!remove) *result << std::make_pair(key, m->at(key)); - } - return result; - } - - Signature keywords_sig = "keywords($args)"; - BUILT_IN(keywords) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy - Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); - for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { - Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX - std::string name = std::string(arg->name()); - name = name.erase(0, 1); // sanitize name (remove dollar sign) - *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, - pstate, name), - arg->value()); - } - return result.detach(); - } - - ////////////////////////// - // INTROSPECTION FUNCTIONS - ////////////////////////// - - Signature type_of_sig = "type-of($value)"; - BUILT_IN(type_of) - { - Expression_Ptr v = ARG("$value", Expression); - return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); - } - - Signature unit_sig = "unit($number)"; - BUILT_IN(unit) - { - Number_Obj arg = ARGN("$number"); - std::string str(quote(arg->unit(), '"')); - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - Signature unitless_sig = "unitless($number)"; - BUILT_IN(unitless) - { - Number_Obj arg = ARGN("$number"); - bool unitless = arg->is_unitless(); - return SASS_MEMORY_NEW(Boolean, pstate, unitless); - } - - Signature comparable_sig = "comparable($number-1, $number-2)"; - BUILT_IN(comparable) - { - Number_Obj n1 = ARGN("$number-1"); - Number_Obj n2 = ARGN("$number-2"); - if (n1->is_unitless() || n2->is_unitless()) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - // normalize into main units - n1->normalize(); n2->normalize(); - Units &lhs_unit = *n1, &rhs_unit = *n2; - bool is_comparable = (lhs_unit == rhs_unit); - return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); - } - - Signature variable_exists_sig = "variable-exists($name)"; - BUILT_IN(variable_exists) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature global_variable_exists_sig = "global-variable-exists($name)"; - BUILT_IN(global_variable_exists) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature function_exists_sig = "function-exists($name)"; - BUILT_IN(function_exists) - { - String_Constant_Ptr ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); - } - - std::string name = Util::normalize_underscores(unquote(ss->value())); - - if(d_env.has_global(name+"[f]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature mixin_exists_sig = "mixin-exists($name)"; - BUILT_IN(mixin_exists) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global(s+"[m]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature feature_exists_sig = "feature-exists($name)"; - BUILT_IN(feature_exists) - { - std::string s = unquote(ARG("$name", String_Constant)->value()); - - if(features.find(s) == features.end()) { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - } - - Signature call_sig = "call($name, $args...)"; - BUILT_IN(call) - { - std::string name; - Function_Ptr ff = Cast(env["$name"]); - String_Constant_Ptr ss = Cast(env["$name"]); - - if (ss) { - name = Util::normalize_underscores(unquote(ss->value())); - std::cerr << "DEPRECATION WARNING: "; - std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; - std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl; - std::cerr << std::endl; - } else if (ff) { - name = ff->name(); - } - - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); - - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - // std::string full_name(name + "[f]"); - // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; - // Parameters_Ptr params = def ? def->parameters() : 0; - // size_t param_size = params ? params->length() : 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj expr = arglist->value_at_index(i); - // if (params && params->has_rest_parameter()) { - // Parameter_Obj p = param_size > i ? (*params)[i] : 0; - // List_Ptr list = Cast(expr); - // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; - // } - if (arglist->is_arglist()) { - Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX - args->append(SASS_MEMORY_NEW(Argument, - pstate, - expr, - arg ? arg->name() : "", - arg ? arg->is_rest_argument() : false, - arg ? arg->is_keyword_argument() : false)); - } else { - args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); - } - } - Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); - Expand expand(ctx, &d_env, &selector_stack); - func->via_call(true); // calc invoke is allowed - if (ff) func->func(ff); - return func->perform(&expand.eval); - } - - //////////////////// - // BOOLEAN FUNCTIONS - //////////////////// - - Signature not_sig = "not($value)"; - BUILT_IN(sass_not) - { - return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); - } - - Signature if_sig = "if($condition, $if-true, $if-false)"; - // BUILT_IN(sass_if) - // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } - BUILT_IN(sass_if) - { - Expand expand(ctx, &d_env, &selector_stack); - Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); - bool is_true = !cond->is_false(); - Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); - res = res->perform(&expand.eval); - res->set_delayed(false); // clone? - return res.detach(); - } - - ////////////////////////// - // MISCELLANEOUS FUNCTIONS - ////////////////////////// - - // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) - // unquoted_string(value.to_sass) - - Signature inspect_sig = "inspect($value)"; - BUILT_IN(inspect) - { - Expression_Ptr v = ARG("$value", Expression); - if (v->concrete_type() == Expression::NULL_VAL) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); - } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); - } else if (v->concrete_type() == Expression::STRING) { - return v; - } else { - // ToDo: fix to_sass for nested parentheses - Sass_Output_Style old_style; - old_style = ctx.c_options.output_style; - ctx.c_options.output_style = TO_SASS; - Emitter emitter(ctx.c_options); - Inspect i(emitter); - i.in_declaration = false; - v->perform(&i); - ctx.c_options.output_style = old_style; - return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); - } - // return v; - } - Signature selector_nest_sig = "selector-nest($selectors...)"; - BUILT_IN(selector_nest) - { - List_Ptr arglist = ARG("$selectors", List); - - // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); - - // Parse args into vector of selectors - std::vector parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Obj str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - std::vector::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector exploded; - selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); - selector_stack.pop_back(); - for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { - exploded.push_back((*rv)[m]); - } - result->elements(exploded); - } - - Listize listize; - return result->perform(&listize); - } - - Signature selector_append_sig = "selector-append($selectors...)"; - BUILT_IN(selector_append) - { - List_Ptr arglist = ARG("$selectors", List); - - // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); - - // Parse args into vector of selectors - std::vector parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-append'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - std::vector::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector newElements; - - // For every COMPLEX_SELECTOR in `result` - // For every COMPLEX_SELECTOR in `child` - // let parentSeqClone equal a copy of result->elements[i] - // let childSeq equal child->elements[j] - // Append all of childSeq head elements into parentSeqClone - // Set the innermost tail of parentSeqClone, to childSeq's tail - // Replace result->elements with newElements - for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { - for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { - Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); - Complex_Selector_Obj childSeq = (*child)[j]; - Complex_Selector_Obj base = childSeq->tail(); - - // Must be a simple sequence - if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { - std::string msg("Can't append \""); - msg += childSeq->to_string(); - msg += "\" to \""; - msg += parentSeqClone->to_string(); - msg += "\" for `selector-append'"; - error(msg, pstate, traces); - } - - // Cannot be a Universal selector - Element_Selector_Obj pType = Cast(childSeq->head()->first()); - if(pType && pType->name() == "*") { - std::string msg("Can't append \""); - msg += childSeq->to_string(); - msg += "\" to \""; - msg += parentSeqClone->to_string(); - msg += "\" for `selector-append'"; - error(msg, pstate, traces); - } - - // TODO: Add check for namespace stuff - - // append any selectors in childSeq's head - parentSeqClone->innermost()->head()->concat(base->head()); - - // Set parentSeqClone new tail - parentSeqClone->innermost()->tail( base->tail() ); - - newElements.push_back(parentSeqClone); - } - } - - result->elements(newElements); - } - - Listize listize; - return result->perform(&listize); - } - - Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; - BUILT_IN(selector_unify) - { - Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); - Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); - - Selector_List_Obj result = selector1->unify_with(selector2); - Listize listize; - return result->perform(&listize); - } - - Signature simple_selectors_sig = "simple-selectors($selector)"; - BUILT_IN(simple_selectors) - { - Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); - - List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); - - for (size_t i = 0, L = sel->length(); i < L; ++i) { - Simple_Selector_Obj ss = (*sel)[i]; - std::string ss_string = ss->to_string() ; - - l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); - } - - return l; - } - - Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; - BUILT_IN(selector_extend) - { - Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); - Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); - - Subset_Map subset_map; - extender->populate_extends(extendee, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, false); - - Listize listize; - return result->perform(&listize); - } - - Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; - BUILT_IN(selector_replace) - { - Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); - Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); - Subset_Map subset_map; - replacement->populate_extends(original, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, true); - - Listize listize; - return result->perform(&listize); - } - - Signature selector_parse_sig = "selector-parse($selector)"; - BUILT_IN(selector_parse) - { - Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - - Listize listize; - return sel->perform(&listize); - } - - Signature is_superselector_sig = "is-superselector($super, $sub)"; - BUILT_IN(is_superselector) - { - Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); - Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); - bool result = sel_sup->is_superselector_of(sel_sub); - return SASS_MEMORY_NEW(Boolean, pstate, result); - } - - Signature unique_id_sig = "unique-id()"; - BUILT_IN(unique_id) - { - std::stringstream ss; - std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 - uint_fast32_t distributed = static_cast(distributor(rand)); - ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; - return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); - } - - Signature is_bracketed_sig = "is-bracketed($list)"; - BUILT_IN(is_bracketed) - { - Value_Obj value = ARG("$list", Value); - List_Obj list = Cast(value); - return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); - } - - Signature content_exists_sig = "content-exists()"; - BUILT_IN(content_exists) - { - if (!d_env.has_global("is_in_mixin")) { - error("Cannot call content-exists() except within a mixin.", pstate, traces); - } - return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); - } - - Signature get_function_sig = "get-function($name, $css: false)"; - BUILT_IN(get_function) - { - String_Constant_Ptr ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); - } - - std::string name = Util::normalize_underscores(unquote(ss->value())); - std::string full_name = name + "[f]"; - - Boolean_Obj css = ARG("$css", Boolean); - if (!css->is_false()) { - Definition_Ptr def = SASS_MEMORY_NEW(Definition, - pstate, - name, - SASS_MEMORY_NEW(Parameters, pstate), - SASS_MEMORY_NEW(Block, pstate, 0, false), - Definition::FUNCTION); - return SASS_MEMORY_NEW(Function, pstate, def, true); - } - - - if (!d_env.has_global(full_name)) { - error("Function not found: " + name, pstate, traces); - } - - Definition_Ptr def = Cast(d_env[full_name]); - return SASS_MEMORY_NEW(Function, pstate, def, false); - } - } -} diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp deleted file mode 100644 index 7019be934..000000000 --- a/src/libsass/src/functions.hpp +++ /dev/null @@ -1,198 +0,0 @@ -#ifndef SASS_FUNCTIONS_H -#define SASS_FUNCTIONS_H - -#include "listize.hpp" -#include "position.hpp" -#include "environment.hpp" -#include "ast_fwd_decl.hpp" -#include "sass/functions.h" - -#define BUILT_IN(name) Expression_Ptr \ -name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtraces traces, std::vector selector_stack) - -namespace Sass { - struct Backtrace; - typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); - - Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx); - Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx); - - std::string function_name(Signature); - - namespace Functions { - - extern Signature rgb_sig; - extern Signature rgba_4_sig; - extern Signature rgba_2_sig; - extern Signature red_sig; - extern Signature green_sig; - extern Signature blue_sig; - extern Signature mix_sig; - extern Signature hsl_sig; - extern Signature hsla_sig; - extern Signature hue_sig; - extern Signature saturation_sig; - extern Signature lightness_sig; - extern Signature adjust_hue_sig; - extern Signature lighten_sig; - extern Signature darken_sig; - extern Signature saturate_sig; - extern Signature desaturate_sig; - extern Signature grayscale_sig; - extern Signature complement_sig; - extern Signature invert_sig; - extern Signature alpha_sig; - extern Signature opacity_sig; - extern Signature opacify_sig; - extern Signature fade_in_sig; - extern Signature transparentize_sig; - extern Signature fade_out_sig; - extern Signature adjust_color_sig; - extern Signature scale_color_sig; - extern Signature change_color_sig; - extern Signature ie_hex_str_sig; - extern Signature unquote_sig; - extern Signature quote_sig; - extern Signature str_length_sig; - extern Signature str_insert_sig; - extern Signature str_index_sig; - extern Signature str_slice_sig; - extern Signature to_upper_case_sig; - extern Signature to_lower_case_sig; - extern Signature percentage_sig; - extern Signature round_sig; - extern Signature ceil_sig; - extern Signature floor_sig; - extern Signature abs_sig; - extern Signature min_sig; - extern Signature max_sig; - extern Signature inspect_sig; - extern Signature random_sig; - extern Signature length_sig; - extern Signature nth_sig; - extern Signature index_sig; - extern Signature join_sig; - extern Signature append_sig; - extern Signature zip_sig; - extern Signature list_separator_sig; - extern Signature type_of_sig; - extern Signature unit_sig; - extern Signature unitless_sig; - extern Signature comparable_sig; - extern Signature variable_exists_sig; - extern Signature global_variable_exists_sig; - extern Signature function_exists_sig; - extern Signature mixin_exists_sig; - extern Signature feature_exists_sig; - extern Signature call_sig; - extern Signature not_sig; - extern Signature if_sig; - extern Signature map_get_sig; - extern Signature map_merge_sig; - extern Signature map_remove_sig; - extern Signature map_keys_sig; - extern Signature map_values_sig; - extern Signature map_has_key_sig; - extern Signature keywords_sig; - extern Signature set_nth_sig; - extern Signature unique_id_sig; - extern Signature selector_nest_sig; - extern Signature selector_append_sig; - extern Signature selector_extend_sig; - extern Signature selector_replace_sig; - extern Signature selector_unify_sig; - extern Signature is_superselector_sig; - extern Signature simple_selectors_sig; - extern Signature selector_parse_sig; - extern Signature is_bracketed_sig; - extern Signature content_exists_sig; - extern Signature get_function_sig; - - BUILT_IN(rgb); - BUILT_IN(rgba_4); - BUILT_IN(rgba_2); - BUILT_IN(red); - BUILT_IN(green); - BUILT_IN(blue); - BUILT_IN(mix); - BUILT_IN(hsl); - BUILT_IN(hsla); - BUILT_IN(hue); - BUILT_IN(saturation); - BUILT_IN(lightness); - BUILT_IN(adjust_hue); - BUILT_IN(lighten); - BUILT_IN(darken); - BUILT_IN(saturate); - BUILT_IN(desaturate); - BUILT_IN(grayscale); - BUILT_IN(complement); - BUILT_IN(invert); - BUILT_IN(alpha); - BUILT_IN(opacify); - BUILT_IN(transparentize); - BUILT_IN(adjust_color); - BUILT_IN(scale_color); - BUILT_IN(change_color); - BUILT_IN(ie_hex_str); - BUILT_IN(sass_unquote); - BUILT_IN(sass_quote); - BUILT_IN(str_length); - BUILT_IN(str_insert); - BUILT_IN(str_index); - BUILT_IN(str_slice); - BUILT_IN(to_upper_case); - BUILT_IN(to_lower_case); - BUILT_IN(percentage); - BUILT_IN(round); - BUILT_IN(ceil); - BUILT_IN(floor); - BUILT_IN(abs); - BUILT_IN(min); - BUILT_IN(max); - BUILT_IN(inspect); - BUILT_IN(random); - BUILT_IN(length); - BUILT_IN(nth); - BUILT_IN(index); - BUILT_IN(join); - BUILT_IN(append); - BUILT_IN(zip); - BUILT_IN(list_separator); - BUILT_IN(type_of); - BUILT_IN(unit); - BUILT_IN(unitless); - BUILT_IN(comparable); - BUILT_IN(variable_exists); - BUILT_IN(global_variable_exists); - BUILT_IN(function_exists); - BUILT_IN(mixin_exists); - BUILT_IN(feature_exists); - BUILT_IN(call); - BUILT_IN(sass_not); - BUILT_IN(sass_if); - BUILT_IN(map_get); - BUILT_IN(map_merge); - BUILT_IN(map_remove); - BUILT_IN(map_keys); - BUILT_IN(map_values); - BUILT_IN(map_has_key); - BUILT_IN(keywords); - BUILT_IN(set_nth); - BUILT_IN(unique_id); - BUILT_IN(selector_nest); - BUILT_IN(selector_append); - BUILT_IN(selector_extend); - BUILT_IN(selector_replace); - BUILT_IN(selector_unify); - BUILT_IN(is_superselector); - BUILT_IN(simple_selectors); - BUILT_IN(selector_parse); - BUILT_IN(is_bracketed); - BUILT_IN(content_exists); - BUILT_IN(get_function); - } -} - -#endif diff --git a/src/libsass/src/inspect.cpp b/src/libsass/src/inspect.cpp deleted file mode 100644 index b4a66fab8..000000000 --- a/src/libsass/src/inspect.cpp +++ /dev/null @@ -1,1138 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include -#include - -#include "ast.hpp" -#include "inspect.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "color_maps.hpp" -#include "utf8/checked.h" - -namespace Sass { - - Inspect::Inspect(const Emitter& emi) - : Emitter(emi) - { } - Inspect::~Inspect() { } - - // statements - void Inspect::operator()(Block_Ptr block) - { - if (!block->is_root()) { - add_open_mapping(block); - append_scope_opener(); - } - if (output_style() == NESTED) indentation += block->tabs(); - for (size_t i = 0, L = block->length(); i < L; ++i) { - (*block)[i]->perform(this); - } - if (output_style() == NESTED) indentation -= block->tabs(); - if (!block->is_root()) { - append_scope_closer(); - add_close_mapping(block); - } - - } - - void Inspect::operator()(Ruleset_Ptr ruleset) - { - if (ruleset->selector()) { - opt.in_selector = true; - ruleset->selector()->perform(this); - opt.in_selector = false; - } - if (ruleset->block()) { - ruleset->block()->perform(this); - } - } - - void Inspect::operator()(Keyframe_Rule_Ptr rule) - { - if (rule->name()) rule->name()->perform(this); - if (rule->block()) rule->block()->perform(this); - } - - void Inspect::operator()(Bubble_Ptr bubble) - { - append_indentation(); - append_token("::BUBBLE", bubble); - append_scope_opener(); - bubble->node()->perform(this); - append_scope_closer(); - } - - void Inspect::operator()(Media_Block_Ptr media_block) - { - append_indentation(); - append_token("@media", media_block); - append_mandatory_space(); - in_media_block = true; - media_block->media_queries()->perform(this); - in_media_block = false; - media_block->block()->perform(this); - } - - void Inspect::operator()(Supports_Block_Ptr feature_block) - { - append_indentation(); - append_token("@supports", feature_block); - append_mandatory_space(); - feature_block->condition()->perform(this); - feature_block->block()->perform(this); - } - - void Inspect::operator()(At_Root_Block_Ptr at_root_block) - { - append_indentation(); - append_token("@at-root ", at_root_block); - append_mandatory_space(); - if(at_root_block->expression()) at_root_block->expression()->perform(this); - if(at_root_block->block()) at_root_block->block()->perform(this); - } - - void Inspect::operator()(Directive_Ptr at_rule) - { - append_indentation(); - append_token(at_rule->keyword(), at_rule); - if (at_rule->selector()) { - append_mandatory_space(); - bool was_wrapped = in_wrapped; - in_wrapped = true; - at_rule->selector()->perform(this); - in_wrapped = was_wrapped; - } - if (at_rule->value()) { - append_mandatory_space(); - at_rule->value()->perform(this); - } - if (at_rule->block()) { - at_rule->block()->perform(this); - } - else { - append_delimiter(); - } - } - - void Inspect::operator()(Declaration_Ptr dec) - { - if (dec->value()->concrete_type() == Expression::NULL_VAL) return; - bool was_decl = in_declaration; - in_declaration = true; - LOCAL_FLAG(in_custom_property, dec->is_custom_property()); - - if (output_style() == NESTED) - indentation += dec->tabs(); - append_indentation(); - if (dec->property()) - dec->property()->perform(this); - append_colon_separator(); - - if (dec->value()->concrete_type() == Expression::SELECTOR) { - Listize listize; - Expression_Obj ls = dec->value()->perform(&listize); - ls->perform(this); - } else { - dec->value()->perform(this); - } - - if (dec->is_important()) { - append_optional_space(); - append_string("!important"); - } - append_delimiter(); - if (output_style() == NESTED) - indentation -= dec->tabs(); - in_declaration = was_decl; - } - - void Inspect::operator()(Assignment_Ptr assn) - { - append_token(assn->variable(), assn); - append_colon_separator(); - assn->value()->perform(this); - if (assn->is_default()) { - append_optional_space(); - append_string("!default"); - } - append_delimiter(); - } - - void Inspect::operator()(Import_Ptr import) - { - if (!import->urls().empty()) { - append_token("@import", import); - append_mandatory_space(); - - import->urls().front()->perform(this); - if (import->urls().size() == 1) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); - } - } - append_delimiter(); - for (size_t i = 1, S = import->urls().size(); i < S; ++i) { - append_mandatory_linefeed(); - append_token("@import", import); - append_mandatory_space(); - - import->urls()[i]->perform(this); - if (import->urls().size() - 1 == i) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); - } - } - append_delimiter(); - } - } - } - - void Inspect::operator()(Import_Stub_Ptr import) - { - append_indentation(); - append_token("@import", import); - append_mandatory_space(); - append_string(import->imp_path()); - append_delimiter(); - } - - void Inspect::operator()(Warning_Ptr warning) - { - append_indentation(); - append_token("@warn", warning); - append_mandatory_space(); - warning->message()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Error_Ptr error) - { - append_indentation(); - append_token("@error", error); - append_mandatory_space(); - error->message()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Debug_Ptr debug) - { - append_indentation(); - append_token("@debug", debug); - append_mandatory_space(); - debug->value()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Comment_Ptr comment) - { - in_comment = true; - comment->text()->perform(this); - in_comment = false; - } - - void Inspect::operator()(If_Ptr cond) - { - append_indentation(); - append_token("@if", cond); - append_mandatory_space(); - cond->predicate()->perform(this); - cond->block()->perform(this); - if (cond->alternative()) { - append_optional_linefeed(); - append_indentation(); - append_string("else"); - cond->alternative()->perform(this); - } - } - - void Inspect::operator()(For_Ptr loop) - { - append_indentation(); - append_token("@for", loop); - append_mandatory_space(); - append_string(loop->variable()); - append_string(" from "); - loop->lower_bound()->perform(this); - append_string(loop->is_inclusive() ? " through " : " to "); - loop->upper_bound()->perform(this); - loop->block()->perform(this); - } - - void Inspect::operator()(Each_Ptr loop) - { - append_indentation(); - append_token("@each", loop); - append_mandatory_space(); - append_string(loop->variables()[0]); - for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { - append_comma_separator(); - append_string(loop->variables()[i]); - } - append_string(" in "); - loop->list()->perform(this); - loop->block()->perform(this); - } - - void Inspect::operator()(While_Ptr loop) - { - append_indentation(); - append_token("@while", loop); - append_mandatory_space(); - loop->predicate()->perform(this); - loop->block()->perform(this); - } - - void Inspect::operator()(Return_Ptr ret) - { - append_indentation(); - append_token("@return", ret); - append_mandatory_space(); - ret->value()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Extension_Ptr extend) - { - append_indentation(); - append_token("@extend", extend); - append_mandatory_space(); - extend->selector()->perform(this); - append_delimiter(); - } - - void Inspect::operator()(Definition_Ptr def) - { - append_indentation(); - if (def->type() == Definition::MIXIN) { - append_token("@mixin", def); - append_mandatory_space(); - } else { - append_token("@function", def); - append_mandatory_space(); - } - append_string(def->name()); - def->parameters()->perform(this); - def->block()->perform(this); - } - - void Inspect::operator()(Mixin_Call_Ptr call) - { - append_indentation(); - append_token("@include", call); - append_mandatory_space(); - append_string(call->name()); - if (call->arguments()) { - call->arguments()->perform(this); - } - if (call->block()) { - append_optional_space(); - call->block()->perform(this); - } - if (!call->block()) append_delimiter(); - } - - void Inspect::operator()(Content_Ptr content) - { - append_indentation(); - append_token("@content", content); - append_delimiter(); - } - - void Inspect::operator()(Map_Ptr map) - { - if (output_style() == TO_SASS && map->empty()) { - append_string("()"); - return; - } - if (map->empty()) return; - if (map->is_invisible()) return; - bool items_output = false; - append_string("("); - for (auto key : map->keys()) { - if (items_output) append_comma_separator(); - key->perform(this); - append_colon_separator(); - LOCAL_FLAG(in_space_array, true); - LOCAL_FLAG(in_comma_array, true); - map->at(key)->perform(this); - items_output = true; - } - append_string(")"); - } - - std::string Inspect::lbracket(List_Ptr list) { - return list->is_bracketed() ? "[" : "("; - } - - std::string Inspect::rbracket(List_Ptr list) { - return list->is_bracketed() ? "]" : ")"; - } - - void Inspect::operator()(List_Ptr list) - { - if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { - append_string(lbracket(list)); - append_string(rbracket(list)); - return; - } - std::string sep(list->separator() == SASS_SPACE ? " " : ","); - if ((output_style() != COMPRESSED) && sep == ",") sep += " "; - else if (in_media_block && sep != " ") sep += " "; // verified - if (list->empty()) return; - bool items_output = false; - - bool was_space_array = in_space_array; - bool was_comma_array = in_comma_array; - // if the list is bracketed, always include the left bracket - if (list->is_bracketed()) { - append_string(lbracket(list)); - } - // probably ruby sass eqivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(lbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(lbracket(list)); - } - - if (list->separator() == SASS_SPACE) in_space_array = true; - else if (list->separator() == SASS_COMMA) in_comma_array = true; - - for (size_t i = 0, L = list->size(); i < L; ++i) { - if (list->separator() == SASS_HASH) - { sep[0] = i % 2 ? ':' : ','; } - Expression_Obj list_item = list->at(i); - if (output_style() != TO_SASS) { - if (list_item->is_invisible()) { - // this fixes an issue with "" in a list - if (!Cast(list_item)) { - continue; - } - } - } - if (items_output) { - append_string(sep); - } - if (items_output && sep != " ") - append_optional_space(); - list_item->perform(this); - items_output = true; - } - - in_comma_array = was_comma_array; - in_space_array = was_space_array; - - // if the list is bracketed, always include the right bracket - if (list->is_bracketed()) { - if (list->separator() == SASS_COMMA && list->size() == 1) { - append_string(","); - } - append_string(rbracket(list)); - } - // probably ruby sass eqivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(","); - append_string(rbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(rbracket(list)); - } - - } - - void Inspect::operator()(Binary_Expression_Ptr expr) - { - expr->left()->perform(this); - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_before - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - - )) append_string(" "); - switch (expr->optype()) { - case Sass_OP::AND: append_string("&&"); break; - case Sass_OP::OR: append_string("||"); break; - case Sass_OP::EQ: append_string("=="); break; - case Sass_OP::NEQ: append_string("!="); break; - case Sass_OP::GT: append_string(">"); break; - case Sass_OP::GTE: append_string(">="); break; - case Sass_OP::LT: append_string("<"); break; - case Sass_OP::LTE: append_string("<="); break; - case Sass_OP::ADD: append_string("+"); break; - case Sass_OP::SUB: append_string("-"); break; - case Sass_OP::MUL: append_string("*"); break; - case Sass_OP::DIV: append_string("/"); break; - case Sass_OP::MOD: append_string("%"); break; - default: break; // shouldn't get here - } - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_after - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - )) append_string(" "); - expr->right()->perform(this); - } - - void Inspect::operator()(Unary_Expression_Ptr expr) - { - if (expr->optype() == Unary_Expression::PLUS) append_string("+"); - else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); - else append_string("-"); - expr->operand()->perform(this); - } - - void Inspect::operator()(Function_Call_Ptr call) - { - append_token(call->name(), call); - call->arguments()->perform(this); - } - - void Inspect::operator()(Function_Call_Schema_Ptr call) - { - call->name()->perform(this); - call->arguments()->perform(this); - } - - void Inspect::operator()(Variable_Ptr var) - { - append_token(var->name(), var); - } - - void Inspect::operator()(Number_Ptr n) - { - - std::string res; - - // reduce units - n->reduce(); - - // check if the fractional part of the value equals to zero - // neat trick from http://stackoverflow.com/a/1521682/1550314 - // double int_part; bool is_int = modf(value, &int_part) == 0.0; - - // this all cannot be done with one run only, since fixed - // output differs from normal output and regular output - // can contain scientific notation which we do not want! - - // first sample - std::stringstream ss; - ss.precision(12); - ss << n->value(); - - // check if we got scientific notation in result - if (ss.str().find_first_of("e") != std::string::npos) { - ss.clear(); ss.str(std::string()); - ss.precision(std::max(12, opt.precision)); - ss << std::fixed << n->value(); - } - - std::string tmp = ss.str(); - size_t pos_point = tmp.find_first_of(".,"); - size_t pos_fract = tmp.find_last_not_of("0"); - bool is_int = pos_point == pos_fract || - pos_point == std::string::npos; - - // reset stream for another run - ss.clear(); ss.str(std::string()); - - // take a shortcut for integers - if (is_int) - { - ss.precision(0); - ss << std::fixed << n->value(); - res = std::string(ss.str()); - } - // process floats - else - { - // do we have have too much precision? - if (pos_fract < opt.precision + pos_point) - { ss.precision((int)(pos_fract - pos_point)); } - else { ss.precision(opt.precision); } - // round value again - ss << std::fixed << n->value(); - res = std::string(ss.str()); - // maybe we truncated up to decimal point - size_t pos = res.find_last_not_of("0"); - // handle case where we have a "0" - if (pos == std::string::npos) { - res = "0.0"; - } else { - bool at_dec_point = res[pos] == '.' || - res[pos] == ','; - // don't leave a blank point - if (at_dec_point) ++ pos; - res.resize (pos + 1); - } - } - - // some final cosmetics - if (res == "0.0") res = "0"; - else if (res == "") res = "0"; - else if (res == "-0") res = "0"; - else if (res == "-0.0") res = "0"; - else if (opt.output_style == COMPRESSED) - { - // check if handling negative nr - size_t off = res[0] == '-' ? 1 : 0; - // remove leading zero from floating point in compressed mode - if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); - } - - // add unit now - res += n->unit(); - - // output the final token - append_token(res, n); - } - - // helper function for serializing colors - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - void Inspect::operator()(Color_Ptr c) - { - // output the final token - std::stringstream ss; - - // original color name - // maybe an unknown token - std::string name = c->disp(); - - if (opt.in_selector && name != "") { - append_token(name, c); - return; - } - - // resolved color - std::string res_name = name; - - double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); - double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); - double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); - double a = cap_channel<1> (c->a()); - - // get color from given name (if one was given at all) - if (name != "" && name_to_color(name)) { - Color_Ptr_Const n = name_to_color(name); - r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); - g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); - b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); - a = cap_channel<1> (n->a()); - } - // otherwise get the possible resolved color name - else { - double numval = r * 0x10000 + g * 0x100 + b; - if (color_to_name(numval)) - res_name = color_to_name(numval); - } - - std::stringstream hexlet; - // dart sass compressed all colors in regular css always - // ruby sass and libsass does it only when not delayed - // since color math is going to be removed, this can go too - bool compressed = opt.output_style == COMPRESSED; - hexlet << '#' << std::setw(1) << std::setfill('0'); - // create a short color hexlet if there is any need for it - if (compressed && is_color_doublet(r, g, b) && a == 1) { - hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); - hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); - hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); - } else { - hexlet << std::hex << std::setw(2) << static_cast(r); - hexlet << std::hex << std::setw(2) << static_cast(g); - hexlet << std::hex << std::setw(2) << static_cast(b); - } - - if (compressed && !c->is_delayed()) name = ""; - if (opt.output_style == INSPECT && a >= 1) { - append_token(hexlet.str(), c); - return; - } - - // retain the originally specified color definition if unchanged - if (name != "") { - ss << name; - } - else if (a >= 1) { - if (res_name != "") { - if (compressed && hexlet.str().size() < res_name.size()) { - ss << hexlet.str(); - } else { - ss << res_name; - } - } - else { - ss << hexlet.str(); - } - } - else { - ss << "rgba("; - ss << static_cast(r) << ","; - if (!compressed) ss << " "; - ss << static_cast(g) << ","; - if (!compressed) ss << " "; - ss << static_cast(b) << ","; - if (!compressed) ss << " "; - ss << a << ')'; - } - - append_token(ss.str(), c); - - } - - void Inspect::operator()(Boolean_Ptr b) - { - // output the final token - append_token(b->value() ? "true" : "false", b); - } - - void Inspect::operator()(String_Schema_Ptr ss) - { - // Evaluation should turn these into String_Constants, - // so this method is only for inspection purposes. - for (size_t i = 0, L = ss->length(); i < L; ++i) { - if ((*ss)[i]->is_interpolant()) append_string("#{"); - (*ss)[i]->perform(this); - if ((*ss)[i]->is_interpolant()) append_string("}"); - } - } - - void Inspect::operator()(String_Constant_Ptr s) - { - append_token(s->value(), s); - } - - void Inspect::operator()(String_Quoted_Ptr s) - { - if (const char q = s->quote_mark()) { - append_token(quote(s->value(), q), s); - } else { - append_token(s->value(), s); - } - } - - void Inspect::operator()(Custom_Error_Ptr e) - { - append_token(e->message(), e); - } - - void Inspect::operator()(Custom_Warning_Ptr w) - { - append_token(w->message(), w); - } - - void Inspect::operator()(Supports_Operator_Ptr so) - { - - if (so->needs_parens(so->left())) append_string("("); - so->left()->perform(this); - if (so->needs_parens(so->left())) append_string(")"); - - if (so->operand() == Supports_Operator::AND) { - append_mandatory_space(); - append_token("and", so); - append_mandatory_space(); - } else if (so->operand() == Supports_Operator::OR) { - append_mandatory_space(); - append_token("or", so); - append_mandatory_space(); - } - - if (so->needs_parens(so->right())) append_string("("); - so->right()->perform(this); - if (so->needs_parens(so->right())) append_string(")"); - } - - void Inspect::operator()(Supports_Negation_Ptr sn) - { - append_token("not", sn); - append_mandatory_space(); - if (sn->needs_parens(sn->condition())) append_string("("); - sn->condition()->perform(this); - if (sn->needs_parens(sn->condition())) append_string(")"); - } - - void Inspect::operator()(Supports_Declaration_Ptr sd) - { - append_string("("); - sd->feature()->perform(this); - append_string(": "); - sd->value()->perform(this); - append_string(")"); - } - - void Inspect::operator()(Supports_Interpolation_Ptr sd) - { - sd->value()->perform(this); - } - - void Inspect::operator()(Media_Query_Ptr mq) - { - size_t i = 0; - if (mq->media_type()) { - if (mq->is_negated()) append_string("not "); - else if (mq->is_restricted()) append_string("only "); - mq->media_type()->perform(this); - } - else { - (*mq)[i++]->perform(this); - } - for (size_t L = mq->length(); i < L; ++i) { - append_string(" and "); - (*mq)[i]->perform(this); - } - } - - void Inspect::operator()(Media_Query_Expression_Ptr mqe) - { - if (mqe->is_interpolated()) { - mqe->feature()->perform(this); - } - else { - append_string("("); - mqe->feature()->perform(this); - if (mqe->value()) { - append_string(": "); // verified - mqe->value()->perform(this); - } - append_string(")"); - } - } - - void Inspect::operator()(At_Root_Query_Ptr ae) - { - if (ae->feature()) { - append_string("("); - ae->feature()->perform(this); - if (ae->value()) { - append_colon_separator(); - ae->value()->perform(this); - } - append_string(")"); - } - } - - void Inspect::operator()(Function_Ptr f) - { - append_token("get-function", f); - append_string("("); - append_string(quote(f->name())); - append_string(")"); - } - - void Inspect::operator()(Null_Ptr n) - { - // output the final token - append_token("null", n); - } - - // parameters and arguments - void Inspect::operator()(Parameter_Ptr p) - { - append_token(p->name(), p); - if (p->default_value()) { - append_colon_separator(); - p->default_value()->perform(this); - } - else if (p->is_rest_parameter()) { - append_string("..."); - } - } - - void Inspect::operator()(Parameters_Ptr p) - { - append_string("("); - if (!p->empty()) { - (*p)[0]->perform(this); - for (size_t i = 1, L = p->length(); i < L; ++i) { - append_comma_separator(); - (*p)[i]->perform(this); - } - } - append_string(")"); - } - - void Inspect::operator()(Argument_Ptr a) - { - if (!a->name().empty()) { - append_token(a->name(), a); - append_colon_separator(); - } - if (!a->value()) return; - // Special case: argument nulls can be ignored - if (a->value()->concrete_type() == Expression::NULL_VAL) { - return; - } - if (a->value()->concrete_type() == Expression::STRING) { - String_Constant_Ptr s = Cast(a->value()); - if (s) s->perform(this); - } else { - a->value()->perform(this); - } - if (a->is_rest_argument()) { - append_string("..."); - } - } - - void Inspect::operator()(Arguments_Ptr a) - { - append_string("("); - if (!a->empty()) { - (*a)[0]->perform(this); - for (size_t i = 1, L = a->length(); i < L; ++i) { - append_string(", "); // verified - // Sass Bug? append_comma_separator(); - (*a)[i]->perform(this); - } - } - append_string(")"); - } - - void Inspect::operator()(Selector_Schema_Ptr s) - { - opt.in_selector = true; - s->contents()->perform(this); - opt.in_selector = false; - } - - void Inspect::operator()(Parent_Selector_Ptr p) - { - if (p->is_real_parent_ref()) append_string("&"); - } - - void Inspect::operator()(Placeholder_Selector_Ptr s) - { - append_token(s->name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); - - } - - void Inspect::operator()(Element_Selector_Ptr s) - { - append_token(s->ns_name(), s); - } - - void Inspect::operator()(Class_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); - } - - void Inspect::operator()(Id_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); - } - - void Inspect::operator()(Attribute_Selector_Ptr s) - { - append_string("["); - add_open_mapping(s); - append_token(s->ns_name(), s); - if (!s->matcher().empty()) { - append_string(s->matcher()); - if (s->value() && *s->value()) { - s->value()->perform(this); - } - } - add_close_mapping(s); - if (s->modifier() != 0) { - append_mandatory_space(); - append_char(s->modifier()); - } - append_string("]"); - } - - void Inspect::operator()(Pseudo_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->expression()) { - append_string("("); - s->expression()->perform(this); - append_string(")"); - } - } - - void Inspect::operator()(Wrapped_Selector_Ptr s) - { - if (s->name() == " ") { - append_string(""); - } else { - bool was = in_wrapped; - in_wrapped = true; - append_token(s->name(), s); - append_string("("); - bool was_comma_array = in_comma_array; - in_comma_array = false; - s->selector()->perform(this); - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; - } - } - - void Inspect::operator()(Compound_Selector_Ptr s) - { - for (size_t i = 0, L = s->length(); i < L; ++i) { - (*s)[i]->perform(this); - } - if (s->has_line_break()) { - if (output_style() != COMPACT) { - append_optional_linefeed(); - } - } - } - - void Inspect::operator()(Complex_Selector_Ptr c) - { - Compound_Selector_Obj head = c->head(); - Complex_Selector_Obj tail = c->tail(); - Complex_Selector::Combinator comb = c->combinator(); - - if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { - if (tail) tail->perform(this); - return; - } - - if (c->has_line_feed()) { - if (!(c->has_parent_ref())) { - append_optional_linefeed(); - append_indentation(); - } - } - - if (head && head->length() != 0) head->perform(this); - bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); - bool is_tail = head && !head->is_empty_reference() && tail; - if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; - - switch (comb) { - case Complex_Selector::ANCESTOR_OF: - if (is_tail) append_mandatory_space(); - break; - case Complex_Selector::PARENT_OF: - append_optional_space(); - append_string(">"); - append_optional_space(); - break; - case Complex_Selector::ADJACENT_TO: - append_optional_space(); - append_string("+"); - append_optional_space(); - break; - case Complex_Selector::REFERENCE: - append_mandatory_space(); - append_string("/"); - c->reference()->perform(this); - append_string("/"); - append_mandatory_space(); - break; - case Complex_Selector::PRECEDES: - if (is_empty) append_optional_space(); - else append_mandatory_space(); - append_string("~"); - if (tail) append_mandatory_space(); - else append_optional_space(); - break; - default: break; - } - if (tail && comb != Complex_Selector::ANCESTOR_OF) { - if (c->has_line_break()) append_optional_linefeed(); - } - if (tail) tail->perform(this); - if (!tail && c->has_line_break()) { - if (output_style() == COMPACT) { - append_mandatory_space(); - } - } - } - - void Inspect::operator()(Selector_List_Ptr g) - { - - if (g->empty()) { - if (output_style() == TO_SASS) { - append_token("()", g); - } - return; - } - - - bool was_comma_array = in_comma_array; - // probably ruby sass eqivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string("("); - } - else if (!in_declaration && in_comma_array) { - append_string("("); - } - - if (in_declaration) in_comma_array = true; - - for (size_t i = 0, L = g->length(); i < L; ++i) { - if (!in_wrapped && i == 0) append_indentation(); - if ((*g)[i] == 0) continue; - schedule_mapping(g->at(i)->last()); - // add_open_mapping((*g)[i]->last()); - (*g)[i]->perform(this); - // add_close_mapping((*g)[i]->last()); - if (i < L - 1) { - scheduled_space = 0; - append_comma_separator(); - } - } - - in_comma_array = was_comma_array; - // probably ruby sass eqivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string(",)"); - } - else if (!in_declaration && in_comma_array) { - append_string(")"); - } - - } - - void Inspect::fallback_impl(AST_Node_Ptr n) - { - } - -} diff --git a/src/libsass/src/inspect.hpp b/src/libsass/src/inspect.hpp deleted file mode 100644 index c36790b80..000000000 --- a/src/libsass/src/inspect.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef SASS_INSPECT_H -#define SASS_INSPECT_H - -#include "position.hpp" -#include "operation.hpp" -#include "emitter.hpp" - -namespace Sass { - class Context; - - class Inspect : public Operation_CRTP, public Emitter { - protected: - // import all the class-specific methods and override as desired - using Operation_CRTP::operator(); - - void fallback_impl(AST_Node_Ptr n); - - public: - - Inspect(const Emitter& emi); - virtual ~Inspect(); - - // statements - virtual void operator()(Block_Ptr); - virtual void operator()(Ruleset_Ptr); - virtual void operator()(Bubble_Ptr); - virtual void operator()(Supports_Block_Ptr); - virtual void operator()(Media_Block_Ptr); - virtual void operator()(At_Root_Block_Ptr); - virtual void operator()(Directive_Ptr); - virtual void operator()(Keyframe_Rule_Ptr); - virtual void operator()(Declaration_Ptr); - virtual void operator()(Assignment_Ptr); - virtual void operator()(Import_Ptr); - virtual void operator()(Import_Stub_Ptr); - virtual void operator()(Warning_Ptr); - virtual void operator()(Error_Ptr); - virtual void operator()(Debug_Ptr); - virtual void operator()(Comment_Ptr); - virtual void operator()(If_Ptr); - virtual void operator()(For_Ptr); - virtual void operator()(Each_Ptr); - virtual void operator()(While_Ptr); - virtual void operator()(Return_Ptr); - virtual void operator()(Extension_Ptr); - virtual void operator()(Definition_Ptr); - virtual void operator()(Mixin_Call_Ptr); - virtual void operator()(Content_Ptr); - // expressions - virtual void operator()(Map_Ptr); - virtual void operator()(Function_Ptr); - virtual void operator()(List_Ptr); - virtual void operator()(Binary_Expression_Ptr); - virtual void operator()(Unary_Expression_Ptr); - virtual void operator()(Function_Call_Ptr); - virtual void operator()(Function_Call_Schema_Ptr); - // virtual void operator()(Custom_Warning_Ptr); - // virtual void operator()(Custom_Error_Ptr); - virtual void operator()(Variable_Ptr); - virtual void operator()(Number_Ptr); - virtual void operator()(Color_Ptr); - virtual void operator()(Boolean_Ptr); - virtual void operator()(String_Schema_Ptr); - virtual void operator()(String_Constant_Ptr); - virtual void operator()(String_Quoted_Ptr); - virtual void operator()(Custom_Error_Ptr); - virtual void operator()(Custom_Warning_Ptr); - virtual void operator()(Supports_Operator_Ptr); - virtual void operator()(Supports_Negation_Ptr); - virtual void operator()(Supports_Declaration_Ptr); - virtual void operator()(Supports_Interpolation_Ptr); - virtual void operator()(Media_Query_Ptr); - virtual void operator()(Media_Query_Expression_Ptr); - virtual void operator()(At_Root_Query_Ptr); - virtual void operator()(Null_Ptr); - virtual void operator()(Parent_Selector_Ptr p); - // parameters and arguments - virtual void operator()(Parameter_Ptr); - virtual void operator()(Parameters_Ptr); - virtual void operator()(Argument_Ptr); - virtual void operator()(Arguments_Ptr); - // selectors - virtual void operator()(Selector_Schema_Ptr); - virtual void operator()(Placeholder_Selector_Ptr); - virtual void operator()(Element_Selector_Ptr); - virtual void operator()(Class_Selector_Ptr); - virtual void operator()(Id_Selector_Ptr); - virtual void operator()(Attribute_Selector_Ptr); - virtual void operator()(Pseudo_Selector_Ptr); - virtual void operator()(Wrapped_Selector_Ptr); - virtual void operator()(Compound_Selector_Ptr); - virtual void operator()(Complex_Selector_Ptr); - virtual void operator()(Selector_List_Ptr); - - virtual std::string lbracket(List_Ptr); - virtual std::string rbracket(List_Ptr); - - // template - // void fallback(U x) { fallback_impl(reinterpret_cast(x)); } - }; - -} -#endif diff --git a/src/libsass/src/json.cpp b/src/libsass/src/json.cpp deleted file mode 100644 index 8f433f5d0..000000000 --- a/src/libsass/src/json.cpp +++ /dev/null @@ -1,1436 +0,0 @@ -/* - Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#define _CRT_NONSTDC_NO_DEPRECATE -#endif - -#include "json.hpp" - -// include utf8 library used by libsass -// ToDo: replace internal json utf8 code -#include "utf8.h" - -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) && _MSC_VER < 1900 -#include -#ifdef snprintf -#undef snprintf -#endif -extern "C" int snprintf(char *, size_t, const char *, ...); -#endif - -#define out_of_memory() do { \ - fprintf(stderr, "Out of memory.\n"); \ - exit(EXIT_FAILURE); \ - } while (0) - -/* Sadly, strdup is not portable. */ -static char *json_strdup(const char *str) -{ - char *ret = (char*) malloc(strlen(str) + 1); - if (ret == NULL) - out_of_memory(); - strcpy(ret, str); - return ret; -} - -/* String buffer */ - -typedef struct -{ - char *cur; - char *end; - char *start; -} SB; - -static void sb_init(SB *sb) -{ - sb->start = (char*) malloc(17); - if (sb->start == NULL) - out_of_memory(); - sb->cur = sb->start; - sb->end = sb->start + 16; -} - -/* sb and need may be evaluated multiple times. */ -#define sb_need(sb, need) do { \ - if ((sb)->end - (sb)->cur < (need)) \ - sb_grow(sb, need); \ - } while (0) - -static void sb_grow(SB *sb, int need) -{ - size_t length = sb->cur - sb->start; - size_t alloc = sb->end - sb->start; - - do { - alloc *= 2; - } while (alloc < length + need); - - sb->start = (char*) realloc(sb->start, alloc + 1); - if (sb->start == NULL) - out_of_memory(); - sb->cur = sb->start + length; - sb->end = sb->start + alloc; -} - -static void sb_put(SB *sb, const char *bytes, int count) -{ - sb_need(sb, count); - memcpy(sb->cur, bytes, count); - sb->cur += count; -} - -#define sb_putc(sb, c) do { \ - if ((sb)->cur >= (sb)->end) \ - sb_grow(sb, 1); \ - *(sb)->cur++ = (c); \ - } while (0) - -static void sb_puts(SB *sb, const char *str) -{ - sb_put(sb, str, (int)strlen(str)); -} - -static char *sb_finish(SB *sb) -{ - *sb->cur = 0; - assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start)); - return sb->start; -} - -static void sb_free(SB *sb) -{ - free(sb->start); -} - -/* - * Unicode helper functions - * - * These are taken from the ccan/charset module and customized a bit. - * Putting them here means the compiler can (choose to) inline them, - * and it keeps ccan/json from having a dependency. - * - * We use uint32_t Type for Unicode codepoints. - * We need our own because wchar_t might be 16 bits. - */ - -/* - * Validate a single UTF-8 character starting at @s. - * The string must be null-terminated. - * - * If it's valid, return its length (1 thru 4). - * If it's invalid or clipped, return 0. - * - * This function implements the syntax given in RFC3629, which is - * the same as that given in The Unicode Standard, Version 6.0. - * - * It has the following properties: - * - * * All codepoints U+0000..U+10FFFF may be encoded, - * except for U+D800..U+DFFF, which are reserved - * for UTF-16 surrogate pair encoding. - * * UTF-8 byte sequences longer than 4 bytes are not permitted, - * as they exceed the range of Unicode. - * * The sixty-six Unicode "non-characters" are permitted - * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF). - */ -static int utf8_validate_cz(const char *s) -{ - unsigned char c = *s++; - - if (c <= 0x7F) { /* 00..7F */ - return 1; - } else if (c <= 0xC1) { /* 80..C1 */ - /* Disallow overlong 2-byte sequence. */ - return 0; - } else if (c <= 0xDF) { /* C2..DF */ - /* Make sure subsequent byte is in the range 0x80..0xBF. */ - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - - return 2; - } else if (c <= 0xEF) { /* E0..EF */ - /* Disallow overlong 3-byte sequence. */ - if (c == 0xE0 && (unsigned char)*s < 0xA0) - return 0; - - /* Disallow U+D800..U+DFFF. */ - if (c == 0xED && (unsigned char)*s > 0x9F) - return 0; - - /* Make sure subsequent bytes are in the range 0x80..0xBF. */ - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - - return 3; - } else if (c <= 0xF4) { /* F0..F4 */ - /* Disallow overlong 4-byte sequence. */ - if (c == 0xF0 && (unsigned char)*s < 0x90) - return 0; - - /* Disallow codepoints beyond U+10FFFF. */ - if (c == 0xF4 && (unsigned char)*s > 0x8F) - return 0; - - /* Make sure subsequent bytes are in the range 0x80..0xBF. */ - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - if (((unsigned char)*s++ & 0xC0) != 0x80) - return 0; - - return 4; - } else { /* F5..FF */ - return 0; - } -} - -/* Validate a null-terminated UTF-8 string. */ -static bool utf8_validate(const char *s) -{ - int len; - - for (; *s != 0; s += len) { - len = utf8_validate_cz(s); - if (len == 0) - return false; - } - - return true; -} - -/* - * Read a single UTF-8 character starting at @s, - * returning the length, in bytes, of the character read. - * - * This function assumes input is valid UTF-8, - * and that there are enough characters in front of @s. - */ -static int utf8_read_char(const char *s, uint32_t *out) -{ - const unsigned char *c = (const unsigned char*) s; - - assert(utf8_validate_cz(s)); - - if (c[0] <= 0x7F) { - /* 00..7F */ - *out = c[0]; - return 1; - } else if (c[0] <= 0xDF) { - /* C2..DF (unless input is invalid) */ - *out = ((uint32_t)c[0] & 0x1F) << 6 | - ((uint32_t)c[1] & 0x3F); - return 2; - } else if (c[0] <= 0xEF) { - /* E0..EF */ - *out = ((uint32_t)c[0] & 0xF) << 12 | - ((uint32_t)c[1] & 0x3F) << 6 | - ((uint32_t)c[2] & 0x3F); - return 3; - } else { - /* F0..F4 (unless input is invalid) */ - *out = ((uint32_t)c[0] & 0x7) << 18 | - ((uint32_t)c[1] & 0x3F) << 12 | - ((uint32_t)c[2] & 0x3F) << 6 | - ((uint32_t)c[3] & 0x3F); - return 4; - } -} - -/* - * Write a single UTF-8 character to @s, - * returning the length, in bytes, of the character written. - * - * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF. - * - * This function will write up to 4 bytes to @out. - */ -static int utf8_write_char(uint32_t unicode, char *out) -{ - unsigned char *o = (unsigned char*) out; - - assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF)); - - if (unicode <= 0x7F) { - /* U+0000..U+007F */ - *o++ = unicode; - return 1; - } else if (unicode <= 0x7FF) { - /* U+0080..U+07FF */ - *o++ = 0xC0 | unicode >> 6; - *o++ = 0x80 | (unicode & 0x3F); - return 2; - } else if (unicode <= 0xFFFF) { - /* U+0800..U+FFFF */ - *o++ = 0xE0 | unicode >> 12; - *o++ = 0x80 | (unicode >> 6 & 0x3F); - *o++ = 0x80 | (unicode & 0x3F); - return 3; - } else { - /* U+10000..U+10FFFF */ - *o++ = 0xF0 | unicode >> 18; - *o++ = 0x80 | (unicode >> 12 & 0x3F); - *o++ = 0x80 | (unicode >> 6 & 0x3F); - *o++ = 0x80 | (unicode & 0x3F); - return 4; - } -} - -/* - * Compute the Unicode codepoint of a UTF-16 surrogate pair. - * - * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF. - * If they aren't, this function returns false. - */ -static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uint32_t *unicode) -{ - if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) { - *unicode = 0x10000 + ((((uint32_t)uc & 0x3FF) << 10) | (lc & 0x3FF)); - return true; - } else { - return false; - } -} - -/* - * Construct a UTF-16 surrogate pair given a Unicode codepoint. - * - * @unicode must be U+10000..U+10FFFF. - */ -static void to_surrogate_pair(uint32_t unicode, uint16_t *uc, uint16_t *lc) -{ - uint32_t n; - - assert(unicode >= 0x10000 && unicode <= 0x10FFFF); - - n = unicode - 0x10000; - *uc = ((n >> 10) & 0x3FF) | 0xD800; - *lc = (n & 0x3FF) | 0xDC00; -} - -static bool is_space (const char *c); -static bool is_digit (const char *c); -static bool parse_value (const char **sp, JsonNode **out); -static bool parse_string (const char **sp, char **out); -static bool parse_number (const char **sp, double *out); -static bool parse_array (const char **sp, JsonNode **out); -static bool parse_object (const char **sp, JsonNode **out); -static bool parse_hex16 (const char **sp, uint16_t *out); - -static bool expect_literal (const char **sp, const char *str); -static void skip_space (const char **sp); - -static void emit_value (SB *out, const JsonNode *node); -static void emit_value_indented (SB *out, const JsonNode *node, const char *space, int indent_level); -static void emit_string (SB *out, const char *str); -static void emit_number (SB *out, double num); -static void emit_array (SB *out, const JsonNode *array); -static void emit_array_indented (SB *out, const JsonNode *array, const char *space, int indent_level); -static void emit_object (SB *out, const JsonNode *object); -static void emit_object_indented (SB *out, const JsonNode *object, const char *space, int indent_level); - -static int write_hex16(char *out, uint16_t val); - -static JsonNode *mknode(JsonTag tag); -static void append_node(JsonNode *parent, JsonNode *child); -static void prepend_node(JsonNode *parent, JsonNode *child); -static void append_member(JsonNode *object, char *key, JsonNode *value); - -/* Assertion-friendly validity checks */ -static bool tag_is_valid(unsigned int tag); -static bool number_is_valid(const char *num); - -JsonNode *json_decode(const char *json) -{ - const char *s = json; - JsonNode *ret; - - skip_space(&s); - if (!parse_value(&s, &ret)) - return NULL; - - skip_space(&s); - if (*s != 0) { - json_delete(ret); - return NULL; - } - - return ret; -} - -char *json_encode(const JsonNode *node) -{ - return json_stringify(node, NULL); -} - -char *json_encode_string(const char *str) -{ - SB sb; - sb_init(&sb); - - try { - emit_string(&sb, str); - } - catch (std::exception) { - sb_free(&sb); - throw; - } - - return sb_finish(&sb); -} - -char *json_stringify(const JsonNode *node, const char *space) -{ - SB sb; - sb_init(&sb); - - try { - if (space != NULL) - emit_value_indented(&sb, node, space, 0); - else - emit_value(&sb, node); - } - catch (std::exception) { - sb_free(&sb); - throw; - } - - return sb_finish(&sb); -} - -void json_delete(JsonNode *node) -{ - if (node != NULL) { - json_remove_from_parent(node); - - switch (node->tag) { - case JSON_STRING: - free(node->string_); - break; - case JSON_ARRAY: - case JSON_OBJECT: - { - JsonNode *child, *next; - for (child = node->children.head; child != NULL; child = next) { - next = child->next; - json_delete(child); - } - break; - } - default:; - } - - free(node); - } -} - -bool json_validate(const char *json) -{ - const char *s = json; - - skip_space(&s); - if (!parse_value(&s, NULL)) - return false; - - skip_space(&s); - if (*s != 0) - return false; - - return true; -} - -JsonNode *json_find_element(JsonNode *array, int index) -{ - JsonNode *element; - int i = 0; - - if (array == NULL || array->tag != JSON_ARRAY) - return NULL; - - json_foreach(element, array) { - if (i == index) - return element; - i++; - } - - return NULL; -} - -JsonNode *json_find_member(JsonNode *object, const char *name) -{ - JsonNode *member; - - if (object == NULL || object->tag != JSON_OBJECT) - return NULL; - - json_foreach(member, object) - if (strcmp(member->key, name) == 0) - return member; - - return NULL; -} - -JsonNode *json_first_child(const JsonNode *node) -{ - if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT)) - return node->children.head; - return NULL; -} - -static JsonNode *mknode(JsonTag tag) -{ - JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode)); - if (ret == NULL) - out_of_memory(); - ret->tag = tag; - return ret; -} - -JsonNode *json_mknull(void) -{ - return mknode(JSON_NULL); -} - -JsonNode *json_mkbool(bool b) -{ - JsonNode *ret = mknode(JSON_BOOL); - ret->bool_ = b; - return ret; -} - -static JsonNode *mkstring(char *s) -{ - JsonNode *ret = mknode(JSON_STRING); - ret->string_ = s; - return ret; -} - -JsonNode *json_mkstring(const char *s) -{ - return mkstring(json_strdup(s)); -} - -JsonNode *json_mknumber(double n) -{ - JsonNode *node = mknode(JSON_NUMBER); - node->number_ = n; - return node; -} - -JsonNode *json_mkarray(void) -{ - return mknode(JSON_ARRAY); -} - -JsonNode *json_mkobject(void) -{ - return mknode(JSON_OBJECT); -} - -static void append_node(JsonNode *parent, JsonNode *child) -{ - if (child != NULL && parent != NULL) { - child->parent = parent; - child->prev = parent->children.tail; - child->next = NULL; - - if (parent->children.tail != NULL) - parent->children.tail->next = child; - else - parent->children.head = child; - parent->children.tail = child; - } -} - -static void prepend_node(JsonNode *parent, JsonNode *child) -{ - if (child != NULL && parent != NULL) { - child->parent = parent; - child->prev = NULL; - child->next = parent->children.head; - - if (parent->children.head != NULL) - parent->children.head->prev = child; - else - parent->children.tail = child; - parent->children.head = child; - } -} - -static void append_member(JsonNode *object, char *key, JsonNode *value) -{ - if (value != NULL && object != NULL) { - value->key = key; - append_node(object, value); - } -} - -void json_append_element(JsonNode *array, JsonNode *element) -{ - if (array != NULL && element !=NULL) { - assert(array->tag == JSON_ARRAY); - assert(element->parent == NULL); - - append_node(array, element); - } -} - -void json_prepend_element(JsonNode *array, JsonNode *element) -{ - assert(array->tag == JSON_ARRAY); - assert(element->parent == NULL); - - prepend_node(array, element); -} - -void json_append_member(JsonNode *object, const char *key, JsonNode *value) -{ - if (object != NULL && key != NULL && value != NULL) { - assert(object->tag == JSON_OBJECT); - assert(value->parent == NULL); - - append_member(object, json_strdup(key), value); - } -} - -void json_prepend_member(JsonNode *object, const char *key, JsonNode *value) -{ - if (object != NULL && key != NULL && value != NULL) { - assert(object->tag == JSON_OBJECT); - assert(value->parent == NULL); - - value->key = json_strdup(key); - prepend_node(object, value); - } -} - -void json_remove_from_parent(JsonNode *node) -{ - if (node != NULL) { - JsonNode *parent = node->parent; - - if (parent != NULL) { - if (node->prev != NULL) - node->prev->next = node->next; - else - parent->children.head = node->next; - - if (node->next != NULL) - node->next->prev = node->prev; - else - parent->children.tail = node->prev; - - free(node->key); - - node->parent = NULL; - node->prev = node->next = NULL; - node->key = NULL; - } - } -} - -static bool parse_value(const char **sp, JsonNode **out) -{ - const char *s = *sp; - - switch (*s) { - case 'n': - if (expect_literal(&s, "null")) { - if (out) - *out = json_mknull(); - *sp = s; - return true; - } - return false; - - case 'f': - if (expect_literal(&s, "false")) { - if (out) - *out = json_mkbool(false); - *sp = s; - return true; - } - return false; - - case 't': - if (expect_literal(&s, "true")) { - if (out) - *out = json_mkbool(true); - *sp = s; - return true; - } - return false; - - case '"': { - char *str = NULL; - if (parse_string(&s, out ? &str : NULL)) { - if (out) - *out = mkstring(str); - *sp = s; - return true; - } - return false; - } - - case '[': - if (parse_array(&s, out)) { - *sp = s; - return true; - } - return false; - - case '{': - if (parse_object(&s, out)) { - *sp = s; - return true; - } - return false; - - default: { - double num; - if (parse_number(&s, out ? &num : NULL)) { - if (out) - *out = json_mknumber(num); - *sp = s; - return true; - } - return false; - } - } -} - -static bool parse_array(const char **sp, JsonNode **out) -{ - const char *s = *sp; - JsonNode *ret = out ? json_mkarray() : NULL; - JsonNode *element = NULL; - - if (*s++ != '[') - goto failure; - skip_space(&s); - - if (*s == ']') { - s++; - goto success; - } - - for (;;) { - if (!parse_value(&s, out ? &element : NULL)) - goto failure; - skip_space(&s); - - if (out) - json_append_element(ret, element); - - if (*s == ']') { - s++; - goto success; - } - - if (*s++ != ',') - goto failure; - skip_space(&s); - } - -success: - *sp = s; - if (out) - *out = ret; - return true; - -failure: - json_delete(ret); - return false; -} - -static bool parse_object(const char **sp, JsonNode **out) -{ - const char *s = *sp; - JsonNode *ret = out ? json_mkobject() : NULL; - char *key = NULL; - JsonNode *value = NULL; - - if (*s++ != '{') - goto failure; - skip_space(&s); - - if (*s == '}') { - s++; - goto success; - } - - for (;;) { - if (!parse_string(&s, out ? &key : NULL)) - goto failure; - skip_space(&s); - - if (*s++ != ':') - goto failure_free_key; - skip_space(&s); - - if (!parse_value(&s, out ? &value : NULL)) - goto failure_free_key; - skip_space(&s); - - if (out) - append_member(ret, key, value); - - if (*s == '}') { - s++; - goto success; - } - - if (*s++ != ',') - goto failure; - skip_space(&s); - } - -success: - *sp = s; - if (out) - *out = ret; - return true; - -failure_free_key: - if (out) - free(key); -failure: - json_delete(ret); - return false; -} - -bool parse_string(const char **sp, char **out) -{ - const char *s = *sp; - SB sb = { 0, 0, 0 }; - char throwaway_buffer[4]; - /* enough space for a UTF-8 character */ - char *b; - - if (*s++ != '"') - return false; - - if (out) { - sb_init(&sb); - sb_need(&sb, 4); - b = sb.cur; - } else { - b = throwaway_buffer; - } - - while (*s != '"') { - unsigned char c = *s++; - - /* Parse next character, and write it to b. */ - if (c == '\\') { - c = *s++; - switch (c) { - case '"': - case '\\': - case '/': - *b++ = c; - break; - case 'b': - *b++ = '\b'; - break; - case 'f': - *b++ = '\f'; - break; - case 'n': - *b++ = '\n'; - break; - case 'r': - *b++ = '\r'; - break; - case 't': - *b++ = '\t'; - break; - case 'u': - { - uint16_t uc, lc; - uint32_t unicode; - - if (!parse_hex16(&s, &uc)) - goto failed; - - if (uc >= 0xD800 && uc <= 0xDFFF) { - /* Handle UTF-16 surrogate pair. */ - if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc)) - goto failed; /* Incomplete surrogate pair. */ - if (!from_surrogate_pair(uc, lc, &unicode)) - goto failed; /* Invalid surrogate pair. */ - } else if (uc == 0) { - /* Disallow "\u0000". */ - goto failed; - } else { - unicode = uc; - } - - b += utf8_write_char(unicode, b); - break; - } - default: - /* Invalid escape */ - goto failed; - } - } else if (c <= 0x1F) { - /* Control characters are not allowed in string literals. */ - goto failed; - } else { - /* Validate and echo a UTF-8 character. */ - int len; - - s--; - len = utf8_validate_cz(s); - if (len == 0) - goto failed; /* Invalid UTF-8 character. */ - - while (len--) - *b++ = *s++; - } - - /* - * Update sb to know about the new bytes, - * and set up b to write another character. - */ - if (out) { - sb.cur = b; - sb_need(&sb, 4); - b = sb.cur; - } else { - b = throwaway_buffer; - } - } - s++; - - if (out) - *out = sb_finish(&sb); - *sp = s; - return true; - -failed: - if (out) - sb_free(&sb); - return false; -} - -bool is_space(const char *c) { - return ((*c) == '\t' || (*c) == '\n' || (*c) == '\r' || (*c) == ' '); -} - -bool is_digit(const char *c){ - return ((*c) >= '0' && (*c) <= '9'); -} - -/* - * The JSON spec says that a number shall follow this precise pattern - * (spaces and quotes added for readability): - * '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)? - * - * However, some JSON parsers are more liberal. For instance, PHP accepts - * '.5' and '1.'. JSON.parse accepts '+3'. - * - * This function takes the strict approach. - */ -bool parse_number(const char **sp, double *out) -{ - const char *s = *sp; - - /* '-'? */ - if (*s == '-') - s++; - - /* (0 | [1-9][0-9]*) */ - if (*s == '0') { - s++; - } else { - if (!is_digit(s)) - return false; - do { - s++; - } while (is_digit(s)); - } - - /* ('.' [0-9]+)? */ - if (*s == '.') { - s++; - if (!is_digit(s)) - return false; - do { - s++; - } while (is_digit(s)); - } - - /* ([Ee] [+-]? [0-9]+)? */ - if (*s == 'E' || *s == 'e') { - s++; - if (*s == '+' || *s == '-') - s++; - if (!is_digit(s)) - return false; - do { - s++; - } while (is_digit(s)); - } - - if (out) - *out = strtod(*sp, NULL); - - *sp = s; - return true; -} - -static void skip_space(const char **sp) -{ - const char *s = *sp; - while (is_space(s)) - s++; - *sp = s; -} - -static void emit_value(SB *out, const JsonNode *node) -{ - assert(tag_is_valid(node->tag)); - switch (node->tag) { - case JSON_NULL: - sb_puts(out, "null"); - break; - case JSON_BOOL: - sb_puts(out, node->bool_ ? "true" : "false"); - break; - case JSON_STRING: - emit_string(out, node->string_); - break; - case JSON_NUMBER: - emit_number(out, node->number_); - break; - case JSON_ARRAY: - emit_array(out, node); - break; - case JSON_OBJECT: - emit_object(out, node); - break; - default: - assert(false); - } -} - -void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level) -{ - assert(tag_is_valid(node->tag)); - switch (node->tag) { - case JSON_NULL: - sb_puts(out, "null"); - break; - case JSON_BOOL: - sb_puts(out, node->bool_ ? "true" : "false"); - break; - case JSON_STRING: - emit_string(out, node->string_); - break; - case JSON_NUMBER: - emit_number(out, node->number_); - break; - case JSON_ARRAY: - emit_array_indented(out, node, space, indent_level); - break; - case JSON_OBJECT: - emit_object_indented(out, node, space, indent_level); - break; - default: - assert(false); - } -} - -static void emit_array(SB *out, const JsonNode *array) -{ - const JsonNode *element; - - sb_putc(out, '['); - json_foreach(element, array) { - emit_value(out, element); - if (element->next != NULL) - sb_putc(out, ','); - } - sb_putc(out, ']'); -} - -static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level) -{ - const JsonNode *element = array->children.head; - int i; - - if (element == NULL) { - sb_puts(out, "[]"); - return; - } - - sb_puts(out, "[\n"); - while (element != NULL) { - for (i = 0; i < indent_level + 1; i++) - sb_puts(out, space); - emit_value_indented(out, element, space, indent_level + 1); - - element = element->next; - sb_puts(out, element != NULL ? ",\n" : "\n"); - } - for (i = 0; i < indent_level; i++) - sb_puts(out, space); - sb_putc(out, ']'); -} - -static void emit_object(SB *out, const JsonNode *object) -{ - const JsonNode *member; - - sb_putc(out, '{'); - json_foreach(member, object) { - emit_string(out, member->key); - sb_putc(out, ':'); - emit_value(out, member); - if (member->next != NULL) - sb_putc(out, ','); - } - sb_putc(out, '}'); -} - -static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level) -{ - const JsonNode *member = object->children.head; - int i; - - if (member == NULL) { - sb_puts(out, "{}"); - return; - } - - sb_puts(out, "{\n"); - while (member != NULL) { - for (i = 0; i < indent_level + 1; i++) - sb_puts(out, space); - emit_string(out, member->key); - sb_puts(out, ": "); - emit_value_indented(out, member, space, indent_level + 1); - - member = member->next; - sb_puts(out, member != NULL ? ",\n" : "\n"); - } - for (i = 0; i < indent_level; i++) - sb_puts(out, space); - sb_putc(out, '}'); -} - -void emit_string(SB *out, const char *str) -{ - bool escape_unicode = false; - const char *s = str; - char *b; - -// make assertion catchable -#ifndef NDEBUG - if (!utf8_validate(str)) { - throw utf8::invalid_utf8(0); - } -#endif - - assert(utf8_validate(str)); - - /* - * 14 bytes is enough space to write up to two - * \uXXXX escapes and two quotation marks. - */ - sb_need(out, 14); - b = out->cur; - - *b++ = '"'; - while (*s != 0) { - unsigned char c = *s++; - - /* Encode the next character, and write it to b. */ - switch (c) { - case '"': - *b++ = '\\'; - *b++ = '"'; - break; - case '\\': - *b++ = '\\'; - *b++ = '\\'; - break; - case '\b': - *b++ = '\\'; - *b++ = 'b'; - break; - case '\f': - *b++ = '\\'; - *b++ = 'f'; - break; - case '\n': - *b++ = '\\'; - *b++ = 'n'; - break; - case '\r': - *b++ = '\\'; - *b++ = 'r'; - break; - case '\t': - *b++ = '\\'; - *b++ = 't'; - break; - default: { - int len; - - s--; - len = utf8_validate_cz(s); - - if (len == 0) { - /* - * Handle invalid UTF-8 character gracefully in production - * by writing a replacement character (U+FFFD) - * and skipping a single byte. - * - * This should never happen when assertions are enabled - * due to the assertion at the beginning of this function. - */ - assert(false); - if (escape_unicode) { - strcpy(b, "\\uFFFD"); - b += 6; - } else { - *b++ = 0xEFu; - *b++ = 0xBFu; - *b++ = 0xBDu; - } - s++; - } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) { - /* Encode using \u.... */ - uint32_t unicode; - - s += utf8_read_char(s, &unicode); - - if (unicode <= 0xFFFF) { - *b++ = '\\'; - *b++ = 'u'; - b += write_hex16(b, unicode); - } else { - /* Produce a surrogate pair. */ - uint16_t uc, lc; - assert(unicode <= 0x10FFFF); - to_surrogate_pair(unicode, &uc, &lc); - *b++ = '\\'; - *b++ = 'u'; - b += write_hex16(b, uc); - *b++ = '\\'; - *b++ = 'u'; - b += write_hex16(b, lc); - } - } else { - /* Write the character directly. */ - while (len--) - *b++ = *s++; - } - - break; - } - } - - /* - * Update *out to know about the new bytes, - * and set up b to write another encoded character. - */ - out->cur = b; - sb_need(out, 14); - b = out->cur; - } - *b++ = '"'; - - out->cur = b; -} - -static void emit_number(SB *out, double num) -{ - /* - * This isn't exactly how JavaScript renders numbers, - * but it should produce valid JSON for reasonable numbers - * preserve precision well enough, and avoid some oddities - * like 0.3 -> 0.299999999999999988898 . - */ - char buf[64]; - sprintf(buf, "%.16g", num); - - if (number_is_valid(buf)) - sb_puts(out, buf); - else - sb_puts(out, "null"); -} - -static bool tag_is_valid(unsigned int tag) -{ - return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT); -} - -static bool number_is_valid(const char *num) -{ - return (parse_number(&num, NULL) && *num == '\0'); -} - -static bool expect_literal(const char **sp, const char *str) -{ - const char *s = *sp; - - while (*str != '\0') - if (*s++ != *str++) - return false; - - *sp = s; - return true; -} - -/* - * Parses exactly 4 hex characters (capital or lowercase). - * Fails if any input chars are not [0-9A-Fa-f]. - */ -static bool parse_hex16(const char **sp, uint16_t *out) -{ - const char *s = *sp; - uint16_t ret = 0; - uint16_t i; - uint16_t tmp; - char c; - - for (i = 0; i < 4; i++) { - c = *s++; - if (c >= '0' && c <= '9') - tmp = c - '0'; - else if (c >= 'A' && c <= 'F') - tmp = c - 'A' + 10; - else if (c >= 'a' && c <= 'f') - tmp = c - 'a' + 10; - else - return false; - - ret <<= 4; - ret += tmp; - } - - if (out) - *out = ret; - *sp = s; - return true; -} - -/* - * Encodes a 16-bit number into hexadecimal, - * writing exactly 4 hex chars. - */ -static int write_hex16(char *out, uint16_t val) -{ - const char *hex = "0123456789ABCDEF"; - - *out++ = hex[(val >> 12) & 0xF]; - *out++ = hex[(val >> 8) & 0xF]; - *out++ = hex[(val >> 4) & 0xF]; - *out++ = hex[ val & 0xF]; - - return 4; -} - -bool json_check(const JsonNode *node, char errmsg[256]) -{ - #define problem(...) do { \ - if (errmsg != NULL) \ - snprintf(errmsg, 256, __VA_ARGS__); \ - return false; \ - } while (0) - - if (node->key != NULL && !utf8_validate(node->key)) - problem("key contains invalid UTF-8"); - - if (!tag_is_valid(node->tag)) - problem("tag is invalid (%u)", node->tag); - - if (node->tag == JSON_BOOL) { - if (node->bool_ != false && node->bool_ != true) - problem("bool_ is neither false (%d) nor true (%d)", (int)false, (int)true); - } else if (node->tag == JSON_STRING) { - if (node->string_ == NULL) - problem("string_ is NULL"); - if (!utf8_validate(node->string_)) - problem("string_ contains invalid UTF-8"); - } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) { - JsonNode *head = node->children.head; - JsonNode *tail = node->children.tail; - - if (head == NULL || tail == NULL) { - if (head != NULL) - problem("tail is NULL, but head is not"); - if (tail != NULL) - problem("head is NULL, but tail is not"); - } else { - JsonNode *child; - JsonNode *last = NULL; - - if (head->prev != NULL) - problem("First child's prev pointer is not NULL"); - - for (child = head; child != NULL; last = child, child = child->next) { - if (child == node) - problem("node is its own child"); - if (child->next == child) - problem("child->next == child (cycle)"); - if (child->next == head) - problem("child->next == head (cycle)"); - - if (child->parent != node) - problem("child does not point back to parent"); - if (child->next != NULL && child->next->prev != child) - problem("child->next does not point back to child"); - - if (node->tag == JSON_ARRAY && child->key != NULL) - problem("Array element's key is not NULL"); - if (node->tag == JSON_OBJECT && child->key == NULL) - problem("Object member's key is NULL"); - - if (!json_check(child, errmsg)) - return false; - } - - if (last != tail) - problem("tail does not match pointer found by starting at head and following next links"); - } - } - - return true; - - #undef problem -} diff --git a/src/libsass/src/json.hpp b/src/libsass/src/json.hpp deleted file mode 100644 index 05b35cd94..000000000 --- a/src/libsass/src/json.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifndef CCAN_JSON_H -#define CCAN_JSON_H - -#include -#include - -typedef enum { - JSON_NULL, - JSON_BOOL, - JSON_STRING, - JSON_NUMBER, - JSON_ARRAY, - JSON_OBJECT, -} JsonTag; - -typedef struct JsonNode JsonNode; - -struct JsonNode -{ - /* only if parent is an object or array (NULL otherwise) */ - JsonNode *parent; - JsonNode *prev, *next; - - /* only if parent is an object (NULL otherwise) */ - char *key; /* Must be valid UTF-8. */ - - JsonTag tag; - union { - /* JSON_BOOL */ - bool bool_; - - /* JSON_STRING */ - char *string_; /* Must be valid UTF-8. */ - - /* JSON_NUMBER */ - double number_; - - /* JSON_ARRAY */ - /* JSON_OBJECT */ - struct { - JsonNode *head, *tail; - } children; - }; -}; - -/*** Encoding, decoding, and validation ***/ - -JsonNode *json_decode (const char *json); -char *json_encode (const JsonNode *node); -char *json_encode_string (const char *str); -char *json_stringify (const JsonNode *node, const char *space); -void json_delete (JsonNode *node); - -bool json_validate (const char *json); - -/*** Lookup and traversal ***/ - -JsonNode *json_find_element (JsonNode *array, int index); -JsonNode *json_find_member (JsonNode *object, const char *key); - -JsonNode *json_first_child (const JsonNode *node); - -#define json_foreach(i, object_or_array) \ - for ((i) = json_first_child(object_or_array); \ - (i) != NULL; \ - (i) = (i)->next) - -/*** Construction and manipulation ***/ - -JsonNode *json_mknull(void); -JsonNode *json_mkbool(bool b); -JsonNode *json_mkstring(const char *s); -JsonNode *json_mknumber(double n); -JsonNode *json_mkarray(void); -JsonNode *json_mkobject(void); - -void json_append_element(JsonNode *array, JsonNode *element); -void json_prepend_element(JsonNode *array, JsonNode *element); -void json_append_member(JsonNode *object, const char *key, JsonNode *value); -void json_prepend_member(JsonNode *object, const char *key, JsonNode *value); - -void json_remove_from_parent(JsonNode *node); - -/*** Debugging ***/ - -/* - * Look for structure and encoding problems in a JsonNode or its descendents. - * - * If a problem is detected, return false, writing a description of the problem - * to errmsg (unless errmsg is NULL). - */ -bool json_check(const JsonNode *node, char errmsg[256]); - -#endif diff --git a/src/libsass/src/kwd_arg_macros.hpp b/src/libsass/src/kwd_arg_macros.hpp deleted file mode 100644 index e135da7de..000000000 --- a/src/libsass/src/kwd_arg_macros.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SASS_KWD_ARG_MACROS_H -#define SASS_KWD_ARG_MACROS_H - -// Example usage: -// KWD_ARG_SET(Args) { -// KWD_ARG(Args, string, foo); -// KWD_ARG(Args, int, bar); -// ... -// }; -// -// ... and later ... -// -// something(Args().foo("hey").bar(3)); - -#define KWD_ARG_SET(set_name) class set_name - -#define KWD_ARG(set_name, type, name) \ -private: \ - type name##_; \ -public: \ - set_name& name(type name##__) { \ - name##_ = name##__; \ - return *this; \ - } \ - type name() { return name##_; } \ -private: - -#endif diff --git a/src/libsass/src/lexer.cpp b/src/libsass/src/lexer.cpp deleted file mode 100644 index be7f67713..000000000 --- a/src/libsass/src/lexer.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include "lexer.hpp" -#include "constants.hpp" - - -namespace Sass { - using namespace Constants; - - namespace Prelexer { - - //#################################### - // BASIC CHARACTER MATCHERS - //#################################### - - // Match standard control chars - const char* kwd_at(const char* src) { return exactly<'@'>(src); } - const char* kwd_dot(const char* src) { return exactly<'.'>(src); } - const char* kwd_comma(const char* src) { return exactly<','>(src); }; - const char* kwd_colon(const char* src) { return exactly<':'>(src); }; - const char* kwd_star(const char* src) { return exactly<'*'>(src); }; - const char* kwd_plus(const char* src) { return exactly<'+'>(src); }; - const char* kwd_minus(const char* src) { return exactly<'-'>(src); }; - const char* kwd_slash(const char* src) { return exactly<'/'>(src); }; - - //#################################### - // implement some function that do exist in the standard - // but those are locale aware which brought some trouble - // this even seems to improve performance by quite a bit - //#################################### - - bool is_alpha(const char& chr) - { - return unsigned(chr - 'A') <= 'Z' - 'A' || - unsigned(chr - 'a') <= 'z' - 'a'; - } - - bool is_space(const char& chr) - { - // adapted the technique from is_alpha - return chr == ' ' || unsigned(chr - '\t') <= '\r' - '\t'; - } - - bool is_digit(const char& chr) - { - // adapted the technique from is_alpha - return unsigned(chr - '0') <= '9' - '0'; - } - - bool is_number(const char& chr) - { - // adapted the technique from is_alpha - return is_digit(chr) || chr == '-' || chr == '+'; - } - - bool is_xdigit(const char& chr) - { - // adapted the technique from is_alpha - return unsigned(chr - '0') <= '9' - '0' || - unsigned(chr - 'a') <= 'f' - 'a' || - unsigned(chr - 'A') <= 'F' - 'A'; - } - - bool is_punct(const char& chr) - { - // locale independent - return chr == '.'; - } - - bool is_alnum(const char& chr) - { - return is_alpha(chr) || is_digit(chr); - } - - // check if char is outside ascii range - bool is_unicode(const char& chr) - { - // check for unicode range - return unsigned(chr) > 127; - } - - // check if char is outside ascii range - // but with specific ranges (copied from Ruby Sass) - bool is_nonascii(const char& chr) - { - unsigned int cmp = unsigned(chr); - return ( - (cmp >= 128 && cmp <= 15572911) || - (cmp >= 15630464 && cmp <= 15712189) || - (cmp >= 4036001920) - ); - } - - // check if char is within a reduced ascii range - // valid in a uri (copied from Ruby Sass) - bool is_uri_character(const char& chr) - { - unsigned int cmp = unsigned(chr); - return (cmp > 41 && cmp < 127) || - cmp == ':' || cmp == '/'; - } - - // check if char is within a reduced ascii range - // valid for escaping (copied from Ruby Sass) - bool is_escapable_character(const char& chr) - { - unsigned int cmp = unsigned(chr); - return cmp > 31 && cmp < 127; - } - - // Match word character (look ahead) - bool is_character(const char& chr) - { - // valid alpha, numeric or unicode char (plus hyphen) - return is_alnum(chr) || is_unicode(chr) || chr == '-'; - } - - //#################################### - // BASIC CLASS MATCHERS - //#################################### - - // create matchers that advance the position - const char* space(const char* src) { return is_space(*src) ? src + 1 : 0; } - const char* alpha(const char* src) { return is_alpha(*src) ? src + 1 : 0; } - const char* unicode(const char* src) { return is_unicode(*src) ? src + 1 : 0; } - const char* nonascii(const char* src) { return is_nonascii(*src) ? src + 1 : 0; } - const char* digit(const char* src) { return is_digit(*src) ? src + 1 : 0; } - const char* xdigit(const char* src) { return is_xdigit(*src) ? src + 1 : 0; } - const char* alnum(const char* src) { return is_alnum(*src) ? src + 1 : 0; } - const char* punct(const char* src) { return is_punct(*src) ? src + 1 : 0; } - const char* hyphen(const char* src) { return *src && *src == '-' ? src + 1 : 0; } - const char* character(const char* src) { return is_character(*src) ? src + 1 : 0; } - const char* uri_character(const char* src) { return is_uri_character(*src) ? src + 1 : 0; } - const char* escapable_character(const char* src) { return is_escapable_character(*src) ? src + 1 : 0; } - - // Match multiple ctype characters. - const char* spaces(const char* src) { return one_plus(src); } - const char* digits(const char* src) { return one_plus(src); } - const char* hyphens(const char* src) { return one_plus(src); } - - // Whitespace handling. - const char* no_spaces(const char* src) { return negate< space >(src); } - const char* optional_spaces(const char* src) { return zero_plus< space >(src); } - - // Match any single character. - const char* any_char(const char* src) { return *src ? src + 1 : src; } - - // Match word boundary (zero-width lookahead). - const char* word_boundary(const char* src) { return is_character(*src) || *src == '#' ? 0 : src; } - - // Match linefeed /(?:\n|\r\n?)/ - const char* re_linebreak(const char* src) - { - // end of file or unix linefeed return here - if (*src == 0 || *src == '\n') return src + 1; - // a carriage return may optionally be followed by a linefeed - if (*src == '\r') return *(src + 1) == '\n' ? src + 2 : src + 1; - // no linefeed - return 0; - } - - // Assert string boundaries (/\Z|\z|\A/) - // This is a zero-width positive lookahead - const char* end_of_line(const char* src) - { - // end of file or unix linefeed return here - return *src == 0 || *src == '\n' || *src == '\r' ? src : 0; - } - - // Assert end_of_file boundary (/\z/) - // This is a zero-width positive lookahead - const char* end_of_file(const char* src) - { - // end of file or unix linefeed return here - return *src == 0 ? src : 0; - } - - } -} diff --git a/src/libsass/src/lexer.hpp b/src/libsass/src/lexer.hpp deleted file mode 100644 index 5838c291c..000000000 --- a/src/libsass/src/lexer.hpp +++ /dev/null @@ -1,315 +0,0 @@ -#ifndef SASS_LEXER_H -#define SASS_LEXER_H - -#include - -namespace Sass { - namespace Prelexer { - - //#################################### - // BASIC CHARACTER MATCHERS - //#################################### - - // Match standard control chars - const char* kwd_at(const char* src); - const char* kwd_dot(const char* src); - const char* kwd_comma(const char* src); - const char* kwd_colon(const char* src); - const char* kwd_star(const char* src); - const char* kwd_plus(const char* src); - const char* kwd_minus(const char* src); - const char* kwd_slash(const char* src); - - //#################################### - // BASIC CLASS MATCHERS - //#################################### - - // These are locale independant - bool is_space(const char& src); - bool is_alpha(const char& src); - bool is_punct(const char& src); - bool is_digit(const char& src); - bool is_number(const char& src); - bool is_alnum(const char& src); - bool is_xdigit(const char& src); - bool is_unicode(const char& src); - bool is_nonascii(const char& src); - bool is_character(const char& src); - bool is_uri_character(const char& src); - bool escapable_character(const char& src); - - // Match a single ctype predicate. - const char* space(const char* src); - const char* alpha(const char* src); - const char* digit(const char* src); - const char* xdigit(const char* src); - const char* alnum(const char* src); - const char* punct(const char* src); - const char* hyphen(const char* src); - const char* unicode(const char* src); - const char* nonascii(const char* src); - const char* character(const char* src); - const char* uri_character(const char* src); - const char* escapable_character(const char* src); - - // Match multiple ctype characters. - const char* spaces(const char* src); - const char* digits(const char* src); - const char* hyphens(const char* src); - - // Whitespace handling. - const char* no_spaces(const char* src); - const char* optional_spaces(const char* src); - - // Match any single character (/./). - const char* any_char(const char* src); - - // Assert word boundary (/\b/) - // Is a zero-width positive lookaheads - const char* word_boundary(const char* src); - - // Match a single linebreak (/(?:\n|\r\n?)/). - const char* re_linebreak(const char* src); - - // Assert string boundaries (/\Z|\z|\A/) - // There are zero-width positive lookaheads - const char* end_of_line(const char* src); - - // Assert end_of_file boundary (/\z/) - const char* end_of_file(const char* src); - // const char* start_of_string(const char* src); - - // Type definition for prelexer functions - typedef const char* (*prelexer)(const char*); - - //#################################### - // BASIC "REGEX" CONSTRUCTORS - //#################################### - - // Match a single character literal. - // Regex equivalent: /(?:x)/ - template - const char* exactly(const char* src) { - return *src == chr ? src + 1 : 0; - } - - // Match the full string literal. - // Regex equivalent: /(?:literal)/ - template - const char* exactly(const char* src) { - if (str == NULL) return 0; - const char* pre = str; - if (src == NULL) return 0; - // there is a small chance that the search string - // is longer than the rest of the string to look at - while (*pre && *src == *pre) { - ++src, ++pre; - } - // did the matcher finish? - return *pre == 0 ? src : 0; - } - - - // Match a single character literal. - // Regex equivalent: /(?:x)/i - // only define lower case alpha chars - template - const char* insensitive(const char* src) { - return *src == chr || *src+32 == chr ? src + 1 : 0; - } - - // Match the full string literal. - // Regex equivalent: /(?:literal)/i - // only define lower case alpha chars - template - const char* insensitive(const char* src) { - if (str == NULL) return 0; - const char* pre = str; - if (src == NULL) return 0; - // there is a small chance that the search string - // is longer than the rest of the string to look at - while (*pre && (*src == *pre || *src+32 == *pre)) { - ++src, ++pre; - } - // did the matcher finish? - return *pre == 0 ? src : 0; - } - - // Match for members of char class. - // Regex equivalent: /[axy]/ - template - const char* class_char(const char* src) { - const char* cc = char_class; - while (*cc && *src != *cc) ++cc; - return *cc ? src + 1 : 0; - } - - // Match for members of char class. - // Regex equivalent: /[axy]+/ - template - const char* class_chars(const char* src) { - const char* p = src; - while (class_char(p)) ++p; - return p == src ? 0 : p; - } - - // Match for members of char class. - // Regex equivalent: /[^axy]/ - template - const char* neg_class_char(const char* src) { - if (*src == 0) return 0; - const char* cc = neg_char_class; - while (*cc && *src != *cc) ++cc; - return *cc ? 0 : src + 1; - } - - // Match for members of char class. - // Regex equivalent: /[^axy]+/ - template - const char* neg_class_chars(const char* src) { - const char* p = src; - while (neg_class_char(p)) ++p; - return p == src ? 0 : p; - } - - // Match all except the supplied one. - // Regex equivalent: /[^x]/ - template - const char* any_char_but(const char* src) { - return (*src && *src != chr) ? src + 1 : 0; - } - - // Succeeds if the matcher fails. - // Aka. zero-width negative lookahead. - // Regex equivalent: /(?!literal)/ - template - const char* negate(const char* src) { - return mx(src) ? 0 : src; - } - - // Succeeds if the matcher succeeds. - // Aka. zero-width positive lookahead. - // Regex equivalent: /(?=literal)/ - // just hangs around until we need it - template - const char* lookahead(const char* src) { - return mx(src) ? src : 0; - } - - // Tries supplied matchers in order. - // Succeeds if one of them succeeds. - // Regex equivalent: /(?:FOO|BAR)/ - template - const char* alternatives(const char* src) { - const char* rslt; - if ((rslt = mx(src))) return rslt; - return 0; - } - template - const char* alternatives(const char* src) { - const char* rslt; - if ((rslt = mx1(src))) return rslt; - return alternatives(src); - } - - // Tries supplied matchers in order. - // Succeeds if all of them succeeds. - // Regex equivalent: /(?:FOO)(?:BAR)/ - template - const char* sequence(const char* src) { - const char* rslt = src; - if (!(rslt = mx1(rslt))) return 0; - return rslt; - } - template - const char* sequence(const char* src) { - const char* rslt = src; - if (!(rslt = mx1(rslt))) return 0; - return sequence(rslt); - } - - - // Match a pattern or not. Always succeeds. - // Regex equivalent: /(?:literal)?/ - template - const char* optional(const char* src) { - const char* p = mx(src); - return p ? p : src; - } - - // Match zero or more of the patterns. - // Regex equivalent: /(?:literal)*/ - template - const char* zero_plus(const char* src) { - const char* p = mx(src); - while (p) src = p, p = mx(src); - return src; - } - - // Match one or more of the patterns. - // Regex equivalent: /(?:literal)+/ - template - const char* one_plus(const char* src) { - const char* p = mx(src); - if (!p) return 0; - while (p) src = p, p = mx(src); - return src; - } - - // Match mx non-greedy until delimiter. - // Other prelexers are greedy by default. - // Regex equivalent: /(?:$mx)*?(?=$delim)\b/ - template - const char* non_greedy(const char* src) { - while (!delim(src)) { - const char* p = mx(src); - if (p == src) return 0; - if (p == 0) return 0; - src = p; - } - return src; - } - - //#################################### - // ADVANCED "REGEX" CONSTRUCTORS - //#################################### - - // Match with word boundary rule. - // Regex equivalent: /(?:$mx)\b/i - template - const char* keyword(const char* src) { - return sequence < - insensitive < str >, - word_boundary - >(src); - } - - // Match with word boundary rule. - // Regex equivalent: /(?:$mx)\b/ - template - const char* word(const char* src) { - return sequence < - exactly < str >, - word_boundary - >(src); - } - - template - const char* loosely(const char* src) { - return sequence < - optional_spaces, - exactly < chr > - >(src); - } - template - const char* loosely(const char* src) { - return sequence < - optional_spaces, - exactly < str > - >(src); - } - - } -} - -#endif diff --git a/src/libsass/src/listize.cpp b/src/libsass/src/listize.cpp deleted file mode 100644 index cb921ae67..000000000 --- a/src/libsass/src/listize.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "sass.hpp" -#include -#include -#include - -#include "listize.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "error_handling.hpp" - -namespace Sass { - - Listize::Listize() - { } - - Expression_Ptr Listize::operator()(Selector_List_Ptr sel) - { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); - l->from_selector(true); - for (size_t i = 0, L = sel->length(); i < L; ++i) { - if (!sel->at(i)) continue; - l->append(sel->at(i)->perform(this)); - } - if (l->length()) return l.detach(); - return SASS_MEMORY_NEW(Null, l->pstate()); - } - - Expression_Ptr Listize::operator()(Compound_Selector_Ptr sel) - { - std::string str; - for (size_t i = 0, L = sel->length(); i < L; ++i) { - Expression_Ptr e = (*sel)[i]->perform(this); - if (e) str += e->to_string(); - } - return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); - } - - Expression_Ptr Listize::operator()(Complex_Selector_Ptr sel) - { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), 2); - l->from_selector(true); - Compound_Selector_Obj head = sel->head(); - if (head && !head->is_empty_reference()) - { - Expression_Ptr hh = head->perform(this); - if (hh) l->append(hh); - } - - std::string reference = ! sel->reference() ? "" - : sel->reference()->to_string(); - switch(sel->combinator()) - { - case Complex_Selector::PARENT_OF: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), ">")); - break; - case Complex_Selector::ADJACENT_TO: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "+")); - break; - case Complex_Selector::REFERENCE: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "/" + reference + "/")); - break; - case Complex_Selector::PRECEDES: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "~")); - break; - case Complex_Selector::ANCESTOR_OF: - break; - default: break; - } - - Complex_Selector_Obj tail = sel->tail(); - if (tail) - { - Expression_Obj tt = tail->perform(this); - if (List_Ptr ls = Cast(tt)) - { l->concat(ls); } - } - if (l->length() == 0) return 0; - return l.detach(); - } - - Expression_Ptr Listize::fallback_impl(AST_Node_Ptr n) - { - return Cast(n); - } - -} diff --git a/src/libsass/src/listize.hpp b/src/libsass/src/listize.hpp deleted file mode 100644 index 9716ebefc..000000000 --- a/src/libsass/src/listize.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef SASS_LISTIZE_H -#define SASS_LISTIZE_H - -#include -#include - -#include "ast.hpp" -#include "context.hpp" -#include "operation.hpp" -#include "environment.hpp" - -namespace Sass { - - struct Backtrace; - - class Listize : public Operation_CRTP { - - Expression_Ptr fallback_impl(AST_Node_Ptr n); - - public: - Listize(); - ~Listize() { } - - Expression_Ptr operator()(Selector_List_Ptr); - Expression_Ptr operator()(Complex_Selector_Ptr); - Expression_Ptr operator()(Compound_Selector_Ptr); - - template - Expression_Ptr fallback(U x) { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/mapping.hpp b/src/libsass/src/mapping.hpp deleted file mode 100644 index 54fb4a0f7..000000000 --- a/src/libsass/src/mapping.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SASS_MAPPING_H -#define SASS_MAPPING_H - -#include "position.hpp" - -namespace Sass { - - struct Mapping { - Position original_position; - Position generated_position; - - Mapping(const Position& original_position, const Position& generated_position) - : original_position(original_position), generated_position(generated_position) { } - }; - -} - -#endif diff --git a/src/libsass/src/memory/SharedPtr.cpp b/src/libsass/src/memory/SharedPtr.cpp deleted file mode 100644 index 2530360a5..000000000 --- a/src/libsass/src/memory/SharedPtr.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "../sass.hpp" -#include -#include - -#include "SharedPtr.hpp" -#include "../ast_fwd_decl.hpp" - -#ifdef DEBUG_SHARED_PTR -#include "../debugger.hpp" -#endif - -namespace Sass { - - #ifdef DEBUG_SHARED_PTR - void SharedObj::dumpMemLeaks() { - if (!all.empty()) { - std::cerr << "###################################\n"; - std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; - std::cerr << "###################################\n"; - for (SharedObj* var : all) { - if (AST_Node_Ptr ast = dynamic_cast(var)) { - debug_ast(ast); - } else { - std::cerr << "LEAKED " << var << "\n"; - } - } - } - } - std::vector SharedObj::all; - #endif - - bool SharedObj::taint = false; - - SharedObj::SharedObj() - : detached(false) - #ifdef DEBUG_SHARED_PTR - , dbg(false) - #endif - { - refcounter = 0; - #ifdef DEBUG_SHARED_PTR - if (taint) all.push_back(this); - #endif - }; - - SharedObj::~SharedObj() { - #ifdef DEBUG_SHARED_PTR - if (dbg) std::cerr << "Destruct " << this << "\n"; - if(!all.empty()) { // check needed for MSVC (no clue why?) - all.erase(std::remove(all.begin(), all.end(), this), all.end()); - } - #endif - }; - - void SharedPtr::decRefCount() { - if (node) { - -- node->refcounter; - #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "- " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; - #endif - if (node->refcounter == 0) { - #ifdef DEBUG_SHARED_PTR - // AST_Node_Ptr ast = dynamic_cast(node); - if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; - #endif - if (!node->detached) { - delete(node); - } - } - } - } - - void SharedPtr::incRefCount() { - if (node) { - ++ node->refcounter; - node->detached = false; - #ifdef DEBUG_SHARED_PTR - if (node->dbg) { - std::cerr << "+ " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; - } - #endif - } - } - - SharedPtr::~SharedPtr() { - decRefCount(); - } - - - // the create constructor - SharedPtr::SharedPtr(SharedObj* ptr) - : node(ptr) { - incRefCount(); - } - // copy assignment operator - SharedPtr& SharedPtr::operator=(const SharedPtr& rhs) { - void* cur_ptr = (void*) node; - void* rhs_ptr = (void*) rhs.node; - if (cur_ptr == rhs_ptr) { - return *this; - } - decRefCount(); - node = rhs.node; - incRefCount(); - return *this; - } - - // the copy constructor - SharedPtr::SharedPtr(const SharedPtr& obj) - : node(obj.node) { - incRefCount(); - } - -} \ No newline at end of file diff --git a/src/libsass/src/memory/SharedPtr.hpp b/src/libsass/src/memory/SharedPtr.hpp deleted file mode 100644 index f20dfa39b..000000000 --- a/src/libsass/src/memory/SharedPtr.hpp +++ /dev/null @@ -1,206 +0,0 @@ -#ifndef SASS_MEMORY_SHARED_PTR_H -#define SASS_MEMORY_SHARED_PTR_H - -#include "sass/base.h" - -#include - -namespace Sass { - - class SharedPtr; - - /////////////////////////////////////////////////////////////////////////////// - // Use macros for the allocation task, since overloading operator `new` - // has been proven to be flaky under certain compilers (see comment below). - /////////////////////////////////////////////////////////////////////////////// - - #ifdef DEBUG_SHARED_PTR - - #define SASS_MEMORY_NEW(Class, ...) \ - ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ - - #define SASS_MEMORY_COPY(obj) \ - ((obj)->copy(__FILE__, __LINE__)) \ - - #define SASS_MEMORY_CLONE(obj) \ - ((obj)->clone(__FILE__, __LINE__)) \ - - #else - - #define SASS_MEMORY_NEW(Class, ...) \ - new Class(__VA_ARGS__) \ - - #define SASS_MEMORY_COPY(obj) \ - ((obj)->copy()) \ - - #define SASS_MEMORY_CLONE(obj) \ - ((obj)->clone()) \ - - #endif - - class SharedObj { - protected: - friend class SharedPtr; - friend class Memory_Manager; - #ifdef DEBUG_SHARED_PTR - static std::vector all; - std::string file; - size_t line; - #endif - static bool taint; - long refcounter; - // long refcount; - bool detached; - #ifdef DEBUG_SHARED_PTR - bool dbg; - #endif - public: - #ifdef DEBUG_SHARED_PTR - static void dumpMemLeaks(); - SharedObj* trace(std::string file, size_t line) { - this->file = file; - this->line = line; - return this; - } - #endif - SharedObj(); - #ifdef DEBUG_SHARED_PTR - std::string getDbgFile() { - return file; - } - size_t getDbgLine() { - return line; - } - void setDbg(bool dbg) { - this->dbg = dbg; - } - #endif - static void setTaint(bool val) { - taint = val; - } - virtual ~SharedObj(); - long getRefCount() { - return refcounter; - } - }; - - - class SharedPtr { - protected: - SharedObj* node; - protected: - void decRefCount(); - void incRefCount(); - public: - // the empty constructor - SharedPtr() - : node(NULL) {}; - // the create constructor - SharedPtr(SharedObj* ptr); - // the copy constructor - SharedPtr(const SharedPtr& obj); - // the move constructor - SharedPtr(SharedPtr&& obj); - // copy assignment operator - SharedPtr& operator=(const SharedPtr& obj); - // move assignment operator - SharedPtr& operator=(SharedPtr&& obj); - // pure virtual destructor - virtual ~SharedPtr() = 0; - public: - SharedObj* obj () const { - return node; - }; - SharedObj* operator-> () const { - return node; - }; - bool isNull () { - return node == NULL; - }; - bool isNull () const { - return node == NULL; - }; - SharedObj* detach() const { - if (node) { - node->detached = true; - } - return node; - }; - operator bool() const { - return node != NULL; - }; - - }; - - template < class T > - class SharedImpl : private SharedPtr { - public: - SharedImpl() - : SharedPtr(NULL) {}; - SharedImpl(T* node) - : SharedPtr(node) {}; - template < class U > - SharedImpl(SharedImpl obj) - : SharedPtr(static_cast(obj.ptr())) {} - SharedImpl(T&& node) - : SharedPtr(node) {}; - SharedImpl(const T& node) - : SharedPtr(node) {}; - // the copy constructor - SharedImpl(const SharedImpl& impl) - : SharedPtr(impl.node) {}; - // the move constructor - SharedImpl(SharedImpl&& impl) - : SharedPtr(impl.node) {}; - // copy assignment operator - SharedImpl& operator=(const SharedImpl& rhs) { - if (node) decRefCount(); - node = rhs.node; - incRefCount(); - return *this; - } - // move assignment operator - SharedImpl& operator=(SharedImpl&& rhs) { - // don't move our self - if (this != &rhs) { - if (node) decRefCount(); - node = std::move(rhs.node); - rhs.node = NULL; - } - return *this; - } - ~SharedImpl() {}; - public: - operator T*() const { - return static_cast(this->obj()); - } - operator T&() const { - return *static_cast(this->obj()); - } - T& operator* () const { - return *static_cast(this->obj()); - }; - T* operator-> () const { - return static_cast(this->obj()); - }; - T* ptr () const { - return static_cast(this->obj()); - }; - T* detach() const { - if (this->obj() == NULL) return NULL; - return static_cast(SharedPtr::detach()); - } - bool isNull() const { - return this->obj() == NULL; - } - bool operator<(const T& rhs) const { - return *this->ptr() < rhs; - }; - operator bool() const { - return this->obj() != NULL; - }; - }; - -} - -#endif \ No newline at end of file diff --git a/src/libsass/src/node.cpp b/src/libsass/src/node.cpp deleted file mode 100644 index 08eada733..000000000 --- a/src/libsass/src/node.cpp +++ /dev/null @@ -1,319 +0,0 @@ -#include "sass.hpp" -#include - -#include "node.hpp" -#include "context.hpp" -#include "parser.hpp" - -namespace Sass { - - - Node Node::createCombinator(const Complex_Selector::Combinator& combinator) { - NodeDequePtr null; - return Node(COMBINATOR, combinator, NULL /*pSelector*/, null /*pCollection*/); - } - - - Node Node::createSelector(const Complex_Selector& pSelector) { - NodeDequePtr null; - - Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(&pSelector); - pStripped->tail(NULL); - pStripped->combinator(Complex_Selector::ANCESTOR_OF); - - Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/); - n.got_line_feed = pSelector.has_line_feed(); - return n; - } - - - Node Node::createCollection() { - NodeDequePtr pEmptyCollection = std::make_shared(); - return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pEmptyCollection); - } - - - Node Node::createCollection(const NodeDeque& values) { - NodeDequePtr pShallowCopiedCollection = std::make_shared(values); - return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pShallowCopiedCollection); - } - - - Node Node::createNil() { - NodeDequePtr null; - return Node(NIL, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, null /*pCollection*/); - } - - - Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection) - : got_line_feed(false), mType(type), mCombinator(combinator), mpSelector(pSelector), mpCollection(pCollection) - { if (pSelector) got_line_feed = pSelector->has_line_feed(); } - - - Node Node::klone() const { - NodeDequePtr pNewCollection = std::make_shared(); - if (mpCollection) { - for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { - Node& toClone = *iter; - pNewCollection->push_back(toClone.klone()); - } - } - - Node n(mType, mCombinator, mpSelector ? SASS_MEMORY_COPY(mpSelector) : NULL, pNewCollection); - n.got_line_feed = got_line_feed; - return n; - } - - - bool Node::contains(const Node& potentialChild) const { - bool found = false; - - for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { - Node& toTest = *iter; - - if (toTest == potentialChild) { - found = true; - break; - } - } - - return found; - } - - - bool Node::operator==(const Node& rhs) const { - if (this->type() != rhs.type()) { - return false; - } - - if (this->isCombinator()) { - - return this->combinator() == rhs.combinator(); - - } else if (this->isNil()) { - - return true; // no state to check - - } else if (this->isSelector()){ - - return *this->selector() == *rhs.selector(); - - } else if (this->isCollection()) { - - if (this->collection()->size() != rhs.collection()->size()) { - return false; - } - - for (NodeDeque::iterator lhsIter = this->collection()->begin(), lhsIterEnd = this->collection()->end(), - rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { - - if (*lhsIter != *rhsIter) { - return false; - } - - } - - return true; - - } - - // We shouldn't get here. - throw "Comparing unknown node types. A new type was probably added and this method wasn't implemented for it."; - } - - - void Node::plus(Node& rhs) { - if (!this->isCollection() || !rhs.isCollection()) { - throw "Both the current node and rhs must be collections."; - } - this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); - } - -#ifdef DEBUG - std::ostream& operator<<(std::ostream& os, const Node& node) { - - if (node.isCombinator()) { - - switch (node.combinator()) { - case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; - case Complex_Selector::PARENT_OF: os << "\">\""; break; - case Complex_Selector::PRECEDES: os << "\"~\""; break; - case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; - case Complex_Selector::REFERENCE: os << "\"/\""; break; - } - - } else if (node.isNil()) { - - os << "nil"; - - } else if (node.isSelector()){ - - os << node.selector()->head()->to_string(); - - } else if (node.isCollection()) { - - os << "["; - - for (NodeDeque::iterator iter = node.collection()->begin(), iterBegin = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { - if (iter != iterBegin) { - os << ", "; - } - - os << (*iter); - } - - os << "]"; - - } - - return os; - - } -#endif - - - Node complexSelectorToNode(Complex_Selector_Ptr pToConvert) { - if (pToConvert == NULL) { - return Node::createNil(); - } - Node node = Node::createCollection(); - node.got_line_feed = pToConvert->has_line_feed(); - bool has_lf = pToConvert->has_line_feed(); - - // unwrap the selector from parent ref - if (pToConvert->head() && pToConvert->head()->has_parent_ref()) { - Complex_Selector_Obj tail = pToConvert->tail(); - if (tail) tail->has_line_feed(pToConvert->has_line_feed()); - pToConvert = tail; - } - - while (pToConvert) { - - bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); - - // the first Complex_Selector may contain a dummy head pointer, skip it. - if (pToConvert->head() && !empty_parent_ref) { - node.collection()->push_back(Node::createSelector(*pToConvert)); - if (has_lf) node.collection()->back().got_line_feed = has_lf; - if (pToConvert->head() || empty_parent_ref) { - if (pToConvert->tail()) { - pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); - } - } - has_lf = false; - } - - if (pToConvert->combinator() != Complex_Selector::ANCESTOR_OF) { - node.collection()->push_back(Node::createCombinator(pToConvert->combinator())); - if (has_lf) node.collection()->back().got_line_feed = has_lf; - has_lf = false; - } - - if (pToConvert && empty_parent_ref && pToConvert->tail()) { - // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); - } - - pToConvert = pToConvert->tail(); - } - - return node; - } - - - Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert) { - if (toConvert.isNil()) { - return NULL; - } - - - if (!toConvert.isCollection()) { - throw "The node to convert to a Complex_Selector_Ptr must be a collection type or nil."; - } - - - NodeDeque& childNodes = *toConvert.collection(); - - std::string noPath(""); - Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL); - - Complex_Selector_Obj pCurrent = pFirst; - - if (toConvert.isSelector()) pFirst->has_line_feed(toConvert.got_line_feed); - if (toConvert.isCombinator()) pFirst->has_line_feed(toConvert.got_line_feed); - - for (NodeDeque::iterator childIter = childNodes.begin(), childIterEnd = childNodes.end(); childIter != childIterEnd; childIter++) { - - Node& child = *childIter; - - if (child.isSelector()) { - // JMA - need to clone the selector, because they can end up getting shared across Node - // collections, and can result in an infinite loop during the call to parentSuperselector() - pCurrent->tail(SASS_MEMORY_COPY(child.selector())); - // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - pCurrent = pCurrent->tail(); - } else if (child.isCombinator()) { - pCurrent->combinator(child.combinator()); - if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - - // if the next node is also a combinator, create another Complex_Selector to hold it so it doesn't replace the current combinator - if (childIter+1 != childIterEnd) { - Node& nextNode = *(childIter+1); - if (nextNode.isCombinator()) { - pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL)); - if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed); - pCurrent = pCurrent->tail(); - } - } - } else { - throw "The node to convert's children must be only combinators or selectors."; - } - } - - // Put the dummy Compound_Selector in the first position, for consistency with the rest of libsass - Compound_Selector_Ptr fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[NODE]"), 1); - Parent_Selector_Ptr selectorRef = SASS_MEMORY_NEW(Parent_Selector, ParserState("[NODE]")); - fakeHead->elements().push_back(selectorRef); - if (toConvert.got_line_feed) pFirst->has_line_feed(toConvert.got_line_feed); - // pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed); - pFirst->head(fakeHead); - return SASS_MEMORY_COPY(pFirst); - } - - // A very naive trim function, which removes duplicates in a node - // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs - Node Node::naiveTrim(Node& seqses) { - - std::vector res; - std::vector known; - - NodeDeque::reverse_iterator seqsesIter = seqses.collection()->rbegin(), - seqsesIterEnd = seqses.collection()->rend(); - - for (; seqsesIter != seqsesIterEnd; ++seqsesIter) - { - Node& seqs1 = *seqsesIter; - if( seqs1.isSelector() ) { - Complex_Selector_Obj sel = seqs1.selector(); - std::vector::iterator it; - bool found = false; - for (it = known.begin(); it != known.end(); ++it) { - if (**it == *sel) { found = true; break; } - } - if( !found ) { - known.push_back(seqs1.selector()); - res.push_back(&seqs1); - } - } else { - res.push_back(&seqs1); - } - } - - Node result = Node::createCollection(); - - for (size_t i = res.size() - 1; i != std::string::npos; --i) { - result.collection()->push_back(*res[i]); - } - - return result; - } -} diff --git a/src/libsass/src/node.hpp b/src/libsass/src/node.hpp deleted file mode 100644 index 23ba360c3..000000000 --- a/src/libsass/src/node.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef SASS_NODE_H -#define SASS_NODE_H - -#include -#include - -#include "ast.hpp" - - -namespace Sass { - - - - - class Context; - - /* - There are a lot of stumbling blocks when trying to port the ruby extend code to C++. The biggest is the choice of - data type. The ruby code will pretty seamlessly switch types between an Array (libsass' - equivalent is the Complex_Selector) to a Sequence, which contains more metadata about the sequence than just the - selector info. They also have the ability to have arbitrary nestings of arrays like [1, [2]], which is hard to - implement using Array equivalents in C++ (like the deque or vector). They also have the ability to include nil - in the arrays, like [1, nil, 3], which has potential semantic differences than an empty array [1, [], 3]. To be - able to represent all of these as unique cases, we need to create a tree of variant objects. The tree nature allows - the inconsistent nesting levels. The variant nature (while making some of the C++ code uglier) allows the code to - more closely match the ruby code, which is a huge benefit when attempting to implement an complex algorithm like - the Extend operator. - - Note that the current libsass data model also pairs the combinator with the Complex_Selector that follows it, but - ruby sass has no such restriction, so we attempt to create a data structure that can handle them split apart. - */ - - class Node; - typedef std::deque NodeDeque; - typedef std::shared_ptr NodeDequePtr; - - class Node { - public: - enum TYPE { - SELECTOR, - COMBINATOR, - COLLECTION, - NIL - }; - - TYPE type() const { return mType; } - bool isCombinator() const { return mType == COMBINATOR; } - bool isSelector() const { return mType == SELECTOR; } - bool isCollection() const { return mType == COLLECTION; } - bool isNil() const { return mType == NIL; } - bool got_line_feed; - - Complex_Selector::Combinator combinator() const { return mCombinator; } - - Complex_Selector_Obj selector() { return mpSelector; } - Complex_Selector_Obj selector() const { return mpSelector; } - - NodeDequePtr collection() { return mpCollection; } - const NodeDequePtr collection() const { return mpCollection; } - - static Node createCombinator(const Complex_Selector::Combinator& combinator); - - // This method will klone the selector, stripping off the tail and combinator - static Node createSelector(const Complex_Selector& pSelector); - - static Node createCollection(); - static Node createCollection(const NodeDeque& values); - - static Node createNil(); - static Node naiveTrim(Node& seqses); - - Node klone() const; - - bool operator==(const Node& rhs) const; - inline bool operator!=(const Node& rhs) const { return !(*this == rhs); } - - - /* - COLLECTION FUNCTIONS - - Most types don't need any helper methods (nil and combinator due to their simplicity and - selector due to the fact that we leverage the non-node selector code on the Complex_Selector - whereever possible). The following methods are intended to be called on Node objects whose - type is COLLECTION only. - */ - - // rhs and this must be node collections. Shallow copy the nodes from rhs to the end of this. - // This function DOES NOT remove the nodes from rhs. - void plus(Node& rhs); - - // potentialChild must be a node collection of selectors/combinators. this must be a collection - // of collections of nodes/combinators. This method checks if potentialChild is a child of this - // Node. - bool contains(const Node& potentialChild) const; - - private: - // Private constructor; Use the static methods (like createCombinator and createSelector) - // to instantiate this object. This is more expressive, and it allows us to break apart each - // case into separate functions. - Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection); - - TYPE mType; - - // TODO: can we union these to save on memory? - Complex_Selector::Combinator mCombinator; - Complex_Selector_Obj mpSelector; - NodeDequePtr mpCollection; - }; - -#ifdef DEBUG - std::ostream& operator<<(std::ostream& os, const Node& node); -#endif - Node complexSelectorToNode(Complex_Selector_Ptr pToConvert); - Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert); - -} - -#endif diff --git a/src/libsass/src/operation.hpp b/src/libsass/src/operation.hpp deleted file mode 100644 index 2d4fbec9d..000000000 --- a/src/libsass/src/operation.hpp +++ /dev/null @@ -1,173 +0,0 @@ -#ifndef SASS_OPERATION_H -#define SASS_OPERATION_H - -#include "ast_fwd_decl.hpp" - -namespace Sass { - - template - class Operation { - public: - virtual T operator()(AST_Node_Ptr x) = 0; - virtual ~Operation() { } - // statements - virtual T operator()(Block_Ptr x) = 0; - virtual T operator()(Ruleset_Ptr x) = 0; - virtual T operator()(Bubble_Ptr x) = 0; - virtual T operator()(Trace_Ptr x) = 0; - virtual T operator()(Supports_Block_Ptr x) = 0; - virtual T operator()(Media_Block_Ptr x) = 0; - virtual T operator()(At_Root_Block_Ptr x) = 0; - virtual T operator()(Directive_Ptr x) = 0; - virtual T operator()(Keyframe_Rule_Ptr x) = 0; - virtual T operator()(Declaration_Ptr x) = 0; - virtual T operator()(Assignment_Ptr x) = 0; - virtual T operator()(Import_Ptr x) = 0; - virtual T operator()(Import_Stub_Ptr x) = 0; - virtual T operator()(Warning_Ptr x) = 0; - virtual T operator()(Error_Ptr x) = 0; - virtual T operator()(Debug_Ptr x) = 0; - virtual T operator()(Comment_Ptr x) = 0; - virtual T operator()(If_Ptr x) = 0; - virtual T operator()(For_Ptr x) = 0; - virtual T operator()(Each_Ptr x) = 0; - virtual T operator()(While_Ptr x) = 0; - virtual T operator()(Return_Ptr x) = 0; - virtual T operator()(Content_Ptr x) = 0; - virtual T operator()(Extension_Ptr x) = 0; - virtual T operator()(Definition_Ptr x) = 0; - virtual T operator()(Mixin_Call_Ptr x) = 0; - // expressions - virtual T operator()(List_Ptr x) = 0; - virtual T operator()(Map_Ptr x) = 0; - virtual T operator()(Function_Ptr x) = 0; - virtual T operator()(Binary_Expression_Ptr x) = 0; - virtual T operator()(Unary_Expression_Ptr x) = 0; - virtual T operator()(Function_Call_Ptr x) = 0; - virtual T operator()(Function_Call_Schema_Ptr x) = 0; - virtual T operator()(Custom_Warning_Ptr x) = 0; - virtual T operator()(Custom_Error_Ptr x) = 0; - virtual T operator()(Variable_Ptr x) = 0; - virtual T operator()(Number_Ptr x) = 0; - virtual T operator()(Color_Ptr x) = 0; - virtual T operator()(Boolean_Ptr x) = 0; - virtual T operator()(String_Schema_Ptr x) = 0; - virtual T operator()(String_Quoted_Ptr x) = 0; - virtual T operator()(String_Constant_Ptr x) = 0; - virtual T operator()(Supports_Condition_Ptr x) = 0; - virtual T operator()(Supports_Operator_Ptr x) = 0; - virtual T operator()(Supports_Negation_Ptr x) = 0; - virtual T operator()(Supports_Declaration_Ptr x) = 0; - virtual T operator()(Supports_Interpolation_Ptr x) = 0; - virtual T operator()(Media_Query_Ptr x) = 0; - virtual T operator()(Media_Query_Expression_Ptr x) = 0; - virtual T operator()(At_Root_Query_Ptr x) = 0; - virtual T operator()(Null_Ptr x) = 0; - virtual T operator()(Parent_Selector_Ptr x) = 0; - // parameters and arguments - virtual T operator()(Parameter_Ptr x) = 0; - virtual T operator()(Parameters_Ptr x) = 0; - virtual T operator()(Argument_Ptr x) = 0; - virtual T operator()(Arguments_Ptr x) = 0; - // selectors - virtual T operator()(Selector_Schema_Ptr x) = 0; - virtual T operator()(Placeholder_Selector_Ptr x) = 0; - virtual T operator()(Element_Selector_Ptr x) = 0; - virtual T operator()(Class_Selector_Ptr x) = 0; - virtual T operator()(Id_Selector_Ptr x) = 0; - virtual T operator()(Attribute_Selector_Ptr x) = 0; - virtual T operator()(Pseudo_Selector_Ptr x) = 0; - virtual T operator()(Wrapped_Selector_Ptr x) = 0; - virtual T operator()(Compound_Selector_Ptr x)= 0; - virtual T operator()(Complex_Selector_Ptr x) = 0; - virtual T operator()(Selector_List_Ptr x) = 0; - - template - T fallback(U x) { return T(); } - }; - - template - class Operation_CRTP : public Operation { - public: - D& impl() { return static_cast(*this); } - public: - T operator()(AST_Node_Ptr x) { return static_cast(this)->fallback(x); } - // statements - T operator()(Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Ruleset_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Bubble_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Trace_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Media_Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(At_Root_Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Directive_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Keyframe_Rule_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Declaration_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Assignment_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Import_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Import_Stub_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Warning_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Error_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Debug_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Comment_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(If_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(For_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Each_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(While_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Return_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Content_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Extension_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Definition_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Mixin_Call_Ptr x) { return static_cast(this)->fallback(x); } - // expressions - T operator()(List_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Map_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Function_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Binary_Expression_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Unary_Expression_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Function_Call_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Function_Call_Schema_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Warning_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Error_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Variable_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Number_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Color_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Boolean_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(String_Schema_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(String_Constant_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(String_Quoted_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Condition_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Operator_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Negation_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Declaration_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Interpolation_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query_Expression_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(At_Root_Query_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Null_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Parent_Selector_Ptr x) { return static_cast(this)->fallback(x); } - // parameters and arguments - T operator()(Parameter_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Parameters_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Argument_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Arguments_Ptr x) { return static_cast(this)->fallback(x); } - // selectors - T operator()(Selector_Schema_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Placeholder_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Element_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Class_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Id_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Attribute_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Pseudo_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Wrapped_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Compound_Selector_Ptr x){ return static_cast(this)->fallback(x); } - T operator()(Complex_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Selector_List_Ptr x) { return static_cast(this)->fallback(x); } - - template - T fallback(U x) { return T(); } - }; - -} - -#endif diff --git a/src/libsass/src/operators.cpp b/src/libsass/src/operators.cpp deleted file mode 100644 index 02e303738..000000000 --- a/src/libsass/src/operators.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include "sass.hpp" -#include "operators.hpp" - -namespace Sass { - - namespace Operators { - - inline double add(double x, double y) { return x + y; } - inline double sub(double x, double y) { return x - y; } - inline double mul(double x, double y) { return x * y; } - inline double div(double x, double y) { return x / y; } // x/0 checked by caller - - inline double mod(double x, double y) { // x/0 checked by caller - if ((x > 0 && y < 0) || (x < 0 && y > 0)) { - double ret = std::fmod(x, y); - return ret ? ret + y : ret; - } else { - return std::fmod(x, y); - } - } - - typedef double (*bop)(double, double); - bop ops[Sass_OP::NUM_OPS] = { - 0, 0, // and, or - 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte - add, sub, mul, div, mod - }; - - /* static function, has no pstate or traces */ - bool eq(Expression_Obj lhs, Expression_Obj rhs) - { - // operation is undefined if one is not a number - if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); - // use compare operator from ast node - return *lhs == *rhs; - } - - /* static function, throws OperationError, has no pstate or traces */ - bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) - { - // can only compare numbers!? - Number_Obj l = Cast(lhs); - Number_Obj r = Cast(rhs); - // operation is undefined if one is not a number - if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); - // use compare operator from ast node - return *l < *r; - } - - /* static functions, throws OperationError, has no pstate or traces */ - bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } - bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } - bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } - bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } - bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - enum Sass_OP op = operand.operand; - - String_Quoted_Ptr lqstr = Cast(&lhs); - String_Quoted_Ptr rqstr = Cast(&rhs); - - std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); - std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); - - if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - - std::string sep; - switch (op) { - case Sass_OP::ADD: sep = ""; break; - case Sass_OP::SUB: sep = "-"; break; - case Sass_OP::DIV: sep = "/"; break; - case Sass_OP::EQ: sep = "=="; break; - case Sass_OP::NEQ: sep = "!="; break; - case Sass_OP::LT: sep = "<"; break; - case Sass_OP::GT: sep = ">"; break; - case Sass_OP::LTE: sep = "<="; break; - case Sass_OP::GTE: sep = ">="; break; - default: - throw Exception::UndefinedOperation(&lhs, &rhs, op); - break; - } - - if (op == Sass_OP::ADD) { - // create string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); - } - - // add whitespace around operator - // but only if result is not delayed - if (sep != "" && delayed == false) { - if (operand.ws_before) sep = " " + sep; - if (operand.ws_after) sep = sep + " "; - } - - if (op == Sass_OP::SUB || op == Sass_OP::DIV) { - if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); - if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); - } - - return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - if (lhs.a() != rhs.a()) { - throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); - } - if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) { - throw Exception::ZeroDivisionError(lhs, rhs); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lhs.r(), rhs.r()), - ops[op](lhs.g(), rhs.g()), - ops[op](lhs.b(), rhs.b()), - lhs.a()); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - double lval = lhs.value(); - double rval = rhs.value(); - - if (op == Sass_OP::MOD && rval == 0) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); - } - - if (op == Sass_OP::DIV && rval == 0) { - std::string result(lval ? "Infinity" : "NaN"); - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - size_t l_n_units = lhs.numerators.size(); - size_t l_d_units = lhs.numerators.size(); - size_t r_n_units = rhs.denominators.size(); - size_t r_d_units = rhs.denominators.size(); - // optimize out the most common and simplest case - if (l_n_units == r_n_units && l_d_units == r_d_units) { - if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { - if (lhs.numerators == rhs.numerators) { - if (lhs.denominators == rhs.denominators) { - Number_Ptr v = SASS_MEMORY_COPY(&lhs); - v->value(ops[op](lval, rval)); - return v; - } - } - } - } - - Number_Obj v = SASS_MEMORY_COPY(&lhs); - - if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerators = rhs.numerators; - v->denominators = rhs.denominators; - } - - if (op == Sass_OP::MUL) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->reduce(); - } - else if (op == Sass_OP::DIV) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->reduce(); - } - else { - Number ln(lhs), rn(rhs); - ln.reduce(); rn.reduce(); - double f(rn.convert_factor(ln)); - v->value(ops[op](lval, rn.value() * f)); - } - - v->pstate(pstate); - return v.detach(); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - double lval = lhs.value(); - switch (op) { - case Sass_OP::ADD: - case Sass_OP::MUL: { - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lval, rhs.r()), - ops[op](lval, rhs.g()), - ops[op](lval, rhs.b()), - rhs.a()); - } - case Sass_OP::SUB: - case Sass_OP::DIV: { - std::string color(rhs.to_string(opt)); - return SASS_MEMORY_NEW(String_Quoted, - pstate, - lhs.to_string(opt) - + sass_op_separator(op) - + color); - } - default: break; - } - throw Exception::UndefinedOperation(&lhs, &rhs, op); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - double rval = rhs.value(); - if (op == Sass_OP::DIV && rval == 0) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(lhs, rhs); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lhs.r(), rval), - ops[op](lhs.g(), rval), - ops[op](lhs.b(), rval), - lhs.a()); - } - - } - -} diff --git a/src/libsass/src/operators.hpp b/src/libsass/src/operators.hpp deleted file mode 100644 index f89eb4ee2..000000000 --- a/src/libsass/src/operators.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SASS_OPERATORS_H -#define SASS_OPERATORS_H - -#include "values.hpp" -#include "sass/values.h" - -namespace Sass { - - namespace Operators { - - // equality operator using AST Node operator== - bool eq(Expression_Obj, Expression_Obj); - bool neq(Expression_Obj, Expression_Obj); - // specific operators based on cmp and eq - bool lt(Expression_Obj, Expression_Obj); - bool gt(Expression_Obj, Expression_Obj); - bool lte(Expression_Obj, Expression_Obj); - bool gte(Expression_Obj, Expression_Obj); - // arithmetic for all the combinations that matter - Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - - }; - -} - -#endif diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp deleted file mode 100644 index b2ca65e7e..000000000 --- a/src/libsass/src/output.cpp +++ /dev/null @@ -1,336 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "output.hpp" - -namespace Sass { - - Output::Output(Sass_Output_Options& opt) - : Inspect(Emitter(opt)), - charset(""), - top_nodes(0) - {} - - Output::~Output() { } - - void Output::fallback_impl(AST_Node_Ptr n) - { - return n->perform(this); - } - - void Output::operator()(Number_Ptr n) - { - // check for a valid unit here - // includes result for reporting - if (!n->is_valid_css_unit()) { - // should be handle in check_expression - throw Exception::InvalidValue({}, *n); - } - // use values to_string facility - std::string res = n->to_string(opt); - // output the final token - append_token(res, n); - } - - void Output::operator()(Import_Ptr imp) - { - top_nodes.push_back(imp); - } - - void Output::operator()(Map_Ptr m) - { - // should be handle in check_expression - throw Exception::InvalidValue({}, *m); - } - - OutputBuffer Output::get_buffer(void) - { - - Emitter emitter(opt); - Inspect inspect(emitter); - - size_t size_nodes = top_nodes.size(); - for (size_t i = 0; i < size_nodes; i++) { - top_nodes[i]->perform(&inspect); - inspect.append_mandatory_linefeed(); - } - - // flush scheduled outputs - // maybe omit semicolon if possible - inspect.finalize(wbuf.buffer.size() == 0); - // prepend buffer on top - prepend_output(inspect.output()); - // make sure we end with a linefeed - if (!ends_with(wbuf.buffer, opt.linefeed)) { - // if the output is not completely empty - if (!wbuf.buffer.empty()) append_string(opt.linefeed); - } - - // search for unicode char - for(const char& chr : wbuf.buffer) { - // skip all ascii chars - // static cast to unsigned to handle `char` being signed / unsigned - if (static_cast(chr) < 128) continue; - // declare the charset - if (output_style() != COMPRESSED) - charset = "@charset \"UTF-8\";" - + std::string(opt.linefeed); - else charset = "\xEF\xBB\xBF"; - // abort search - break; - } - - // add charset as first line, before comments and imports - if (!charset.empty()) prepend_string(charset); - - return wbuf; - - } - - void Output::operator()(Comment_Ptr c) - { - std::string txt = c->text()->to_string(opt); - // if (indentation && txt == "/**/") return; - bool important = c->is_important(); - if (output_style() != COMPRESSED || important) { - if (buffer().size() == 0) { - top_nodes.push_back(c); - } else { - in_comment = true; - append_indentation(); - c->text()->perform(this); - in_comment = false; - if (indentation == 0) { - append_mandatory_linefeed(); - } else { - append_optional_linefeed(); - } - } - } - } - - void Output::operator()(Ruleset_Ptr r) - { - Selector_Obj s = r->selector(); - Block_Obj b = r->block(); - - // Filter out rulesets that aren't printable (process its children though) - if (!Util::isPrintable(r, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - const Statement_Obj& stm = b->at(i); - if (Cast(stm)) { - if (!Cast(stm)) { - stm->perform(this); - } - } - } - return; - } - - if (output_style() == NESTED) indentation += r->tabs(); - if (opt.source_comments) { - std::stringstream ss; - append_indentation(); - std::string path(File::abs2rel(r->pstate().path)); - ss << "/* line " << r->pstate().line + 1 << ", " << path << " */"; - append_string(ss.str()); - append_optional_linefeed(); - } - scheduled_crutch = s; - if (s) s->perform(this); - append_scope_opener(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - bool bPrintExpression = true; - // Check print conditions - if (Declaration_Ptr dec = Cast(stm)) { - if (String_Constant_Ptr valConst = Cast(dec->value())) { - std::string val(valConst->value()); - if (String_Quoted_Ptr qstr = Cast(valConst)) { - if (!qstr->quote_mark() && val.empty()) { - bPrintExpression = false; - } - } - } - else if (List_Ptr list = Cast(dec->value())) { - bool all_invisible = true; - for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { - Expression_Ptr item = list->at(list_i); - if (!item->is_invisible()) all_invisible = false; - } - if (all_invisible && !list->is_bracketed()) bPrintExpression = false; - } - } - // Print if OK - if (bPrintExpression) { - stm->perform(this); - } - } - if (output_style() == NESTED) indentation -= r->tabs(); - append_scope_closer(b); - - } - void Output::operator()(Keyframe_Rule_Ptr r) - { - Block_Obj b = r->block(); - Selector_Obj v = r->name(); - - if (!v.isNull()) { - v->perform(this); - } - - if (!b) { - append_colon_separator(); - return; - } - - append_scope_opener(); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - stm->perform(this); - if (i < L - 1) append_special_linefeed(); - } - append_scope_closer(); - } - - void Output::operator()(Supports_Block_Ptr f) - { - if (f->is_invisible()) return; - - Supports_Condition_Obj c = f->condition(); - Block_Obj b = f->block(); - - // Filter out feature blocks that aren't printable (process its children though) - if (!Util::isPrintable(f, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - stm->perform(this); - } - } - return; - } - - if (output_style() == NESTED) indentation += f->tabs(); - append_indentation(); - append_token("@supports", f); - append_mandatory_space(); - c->perform(this); - append_scope_opener(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - stm->perform(this); - if (i < L - 1) append_special_linefeed(); - } - - if (output_style() == NESTED) indentation -= f->tabs(); - - append_scope_closer(); - - } - - void Output::operator()(Media_Block_Ptr m) - { - if (m->is_invisible()) return; - - Block_Obj b = m->block(); - - // Filter out media blocks that aren't printable (process its children though) - if (!Util::isPrintable(m, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - stm->perform(this); - } - } - return; - } - if (output_style() == NESTED) indentation += m->tabs(); - append_indentation(); - append_token("@media", m); - append_mandatory_space(); - in_media_block = true; - m->media_queries()->perform(this); - in_media_block = false; - append_scope_opener(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->at(i)) { - Statement_Obj stm = b->at(i); - stm->perform(this); - } - if (i < L - 1) append_special_linefeed(); - } - - if (output_style() == NESTED) indentation -= m->tabs(); - append_scope_closer(); - } - - void Output::operator()(Directive_Ptr a) - { - std::string kwd = a->keyword(); - Selector_Obj s = a->selector(); - Expression_Obj v = a->value(); - Block_Obj b = a->block(); - - append_indentation(); - append_token(kwd, a); - if (s) { - append_mandatory_space(); - in_wrapped = true; - s->perform(this); - in_wrapped = false; - } - if (v) { - append_mandatory_space(); - // ruby sass bug? should use options? - append_token(v->to_string(/* opt */), v); - } - if (!b) { - append_delimiter(); - return; - } - - if (b->is_invisible() || b->length() == 0) { - append_optional_space(); - return append_string("{}"); - } - - append_scope_opener(); - - bool format = kwd != "@font-face";; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - stm->perform(this); - if (i < L - 1 && format) append_special_linefeed(); - } - - append_scope_closer(); - } - - void Output::operator()(String_Quoted_Ptr s) - { - if (s->quote_mark()) { - append_token(quote(s->value(), s->quote_mark()), s); - } else if (!in_comment) { - append_token(string_to_output(s->value()), s); - } else { - append_token(s->value(), s); - } - } - - void Output::operator()(String_Constant_Ptr s) - { - std::string value(s->value()); - if (s->can_compress_whitespace() && output_style() == COMPRESSED) { - value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); - } - if (!in_comment && !in_custom_property) { - append_token(string_to_output(value), s); - } else { - append_token(value, s); - } - } - -} diff --git a/src/libsass/src/output.hpp b/src/libsass/src/output.hpp deleted file mode 100644 index c460b13fe..000000000 --- a/src/libsass/src/output.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef SASS_OUTPUT_H -#define SASS_OUTPUT_H - -#include -#include - -#include "util.hpp" -#include "inspect.hpp" -#include "operation.hpp" - -namespace Sass { - class Context; - - // Refactor to make it generic to find linefeed (look behind) - inline bool ends_with(std::string const & value, std::string const & ending) - { - if (ending.size() > value.size()) return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); - } - - class Output : public Inspect { - protected: - using Inspect::operator(); - - public: - Output(Sass_Output_Options& opt); - virtual ~Output(); - - protected: - std::string charset; - std::vector top_nodes; - - public: - OutputBuffer get_buffer(void); - - virtual void operator()(Map_Ptr); - virtual void operator()(Ruleset_Ptr); - virtual void operator()(Supports_Block_Ptr); - virtual void operator()(Media_Block_Ptr); - virtual void operator()(Directive_Ptr); - virtual void operator()(Keyframe_Rule_Ptr); - virtual void operator()(Import_Ptr); - virtual void operator()(Comment_Ptr); - virtual void operator()(Number_Ptr); - virtual void operator()(String_Quoted_Ptr); - virtual void operator()(String_Constant_Ptr); - - void fallback_impl(AST_Node_Ptr n); - - }; - -} - -#endif diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp deleted file mode 100644 index ee51d56b6..000000000 --- a/src/libsass/src/parser.cpp +++ /dev/null @@ -1,3137 +0,0 @@ -#include "sass.hpp" -#include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "constants.hpp" -#include "util.hpp" -#include "prelexer.hpp" -#include "color_maps.hpp" -#include "sass/functions.h" -#include "error_handling.hpp" - -// Notes about delayed: some ast nodes can have delayed evaluation so -// they can preserve their original semantics if needed. This is most -// prominently exhibited by the division operation, since it is not -// only a valid operation, but also a valid css statement (i.e. for -// fonts, as in `16px/24px`). When parsing lists and expression we -// unwrap single items from lists and other operations. A nested list -// must not be delayed, only the items of the first level sometimes -// are delayed (as with argument lists). To achieve this we need to -// pass status to the list parser, so this can be set correctly. -// Another case with delayed values are colors. In compressed mode -// only processed values get compressed (other are left as written). - -#include -#include -#include -#include - -namespace Sass { - using namespace Constants; - using namespace Prelexer; - - Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - pstate.offset.column = 0; - pstate.offset.line = 0; - Parser p(ctx, pstate, traces); - p.source = source ? source : beg; - p.position = beg ? beg : p.source; - p.end = p.position + strlen(p.position); - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - p.block_stack.push_back(root); - root->is_root(true); - return p; - } - - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - pstate.offset.column = 0; - pstate.offset.line = 0; - Parser p(ctx, pstate, traces); - p.source = source ? source : beg; - p.position = beg ? beg : p.source; - p.end = end ? end : p.position + strlen(p.position); - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - p.block_stack.push_back(root); - root->is_root(true); - return p; - } - - void Parser::advanceToNextToken() { - lex < css_comments >(false); - // advance to position - pstate += pstate.offset; - pstate.offset.column = 0; - pstate.offset.line = 0; - } - - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); - // ToDo: ruby sass errors on parent references - // ToDo: remap the source-map entries somehow - return p.parse_selector_list(false); - } - - bool Parser::peek_newline(const char* start) - { - return peek_linefeed(start ? start : position) - && ! peek_css>(start); - } - - Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) - { - Parser p(ctx, pstate, traces); - p.source = source ? source : t.begin; - p.position = t.begin ? t.begin : p.source; - p.end = t.end ? t.end : p.position + strlen(p.position); - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - p.block_stack.push_back(root); - root->is_root(true); - return p; - } - - /* main entry point to parse root block */ - Block_Obj Parser::parse() - { - - // consume unicode BOM - read_bom(); - - // scan the input to find invalid utf8 sequences - const char* it = utf8::find_invalid(position, end); - - // report invalid utf8 - if (it != end) { - pstate += Offset::init(position, it); - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); - } - - // create a block AST node to hold children - Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); - - // check seems a bit esoteric but works - if (ctx.resources.size() == 1) { - // apply headers only on very first include - ctx.apply_custom_headers(root, path, pstate); - } - - // parse children nodes - block_stack.push_back(root); - parse_block_nodes(true); - block_stack.pop_back(); - - // update final position - root->update_pstate(pstate); - - if (position != end) { - css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); - } - - return root; - } - - - // convenience function for block parsing - // will create a new block ad-hoc for you - // this is the base block parsing function - Block_Obj Parser::parse_css_block(bool is_root) - { - - // parse comments before block - // lex < optional_css_comments >(); - - // lex mandatory opener or error out - if (!lex_css < exactly<'{'> >()) { - css_error("Invalid CSS", " after ", ": expected \"{\", was "); - } - // create new block and push to the selector stack - Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); - block_stack.push_back(block); - - if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - - if (!lex_css < exactly<'}'> >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - - // update for end position - // this seems to be done somewhere else - // but that fixed selector schema issue - // block->update_pstate(pstate); - - // parse comments after block - // lex < optional_css_comments >(); - - block_stack.pop_back(); - - return block; - } - - // convenience function for block parsing - // will create a new block ad-hoc for you - // also updates the `in_at_root` flag - Block_Obj Parser::parse_block(bool is_root) - { - return parse_css_block(is_root); - } - - // the main block parsing function - // parses stuff between `{` and `}` - bool Parser::parse_block_nodes(bool is_root) - { - - // loop until end of string - while (position < end) { - - // we should be able to refactor this - parse_block_comments(); - lex < css_whitespace >(); - - if (lex < exactly<';'> >()) continue; - if (peek < end_of_file >()) return true; - if (peek < exactly<'}'> >()) return true; - - if (parse_block_node(is_root)) continue; - - parse_block_comments(); - - if (lex_css < exactly<';'> >()) continue; - if (peek_css < end_of_file >()) return true; - if (peek_css < exactly<'}'> >()) return true; - - // illegal sass - return false; - } - // return success - return true; - } - - // parser for a single node in a block - // semicolons must be lexed beforehand - bool Parser::parse_block_node(bool is_root) { - - Block_Obj block = block_stack.back(); - - parse_block_comments(); - - // throw away white-space - // includes line comments - lex < css_whitespace >(); - - Lookahead lookahead_result; - - // also parse block comments - - // first parse everything that is allowed in functions - if (lex < variable >(true)) { block->append(parse_assignment()); } - else if (lex < kwd_err >(true)) { block->append(parse_error()); } - else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } - else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } - else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } - else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } - else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } - else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } - else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } - - // parse imports to process later - else if (lex < kwd_import >(true)) { - Scope parent = stack.empty() ? Scope::Rules : stack.back(); - if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { - if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 - error("Import directives may not be used within control directives or mixins."); - } - } - // this puts the parsed doc into sheets - // import stub will fetch this in expand - Import_Obj imp = parse_import(); - // if it is a url, we only add the statement - if (!imp->urls().empty()) block->append(imp); - // process all resources now (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); - } - } - - else if (lex < kwd_extend >(true)) { - Lookahead lookahead = lookahead_for_include(position); - if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - Selector_List_Obj target; - if (!lookahead.has_interpolants) { - target = parse_selector_list(true); - } - else { - target = SASS_MEMORY_NEW(Selector_List, pstate); - target->schema(parse_selector_schema(lookahead.found, true)); - } - - block->append(SASS_MEMORY_NEW(Extension, pstate, target)); - } - - // selector may contain interpolations which need delayed evaluation - else if ( - !(lookahead_result = lookahead_for_selector(position)).error && - !lookahead_result.is_custom_property - ) - { - block->append(parse_ruleset(lookahead_result)); - } - - // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } - else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } - else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } - else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } - else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } - else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } - else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } - - // ignore the @charset directive for now - else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } - - // generic at keyword (keep last) - else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } - else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } - else if (lex< at_keyword >(true)) { block->append(parse_directive()); } - - else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { - lex< css_whitespace >(); - if (position >= end) return true; - css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); - } - // parse a declaration - else - { - // ToDo: how does it handle parse errors? - // maybe we are expected to parse something? - Declaration_Obj decl = parse_declaration(); - decl->tabs(indentation); - block->append(decl); - // maybe we have a "sub-block" - if (peek< exactly<'{'> >()) { - if (decl->is_indented()) ++ indentation; - // parse a propset that rides on the declaration's property - stack.push_back(Scope::Properties); - decl->block(parse_block()); - stack.pop_back(); - if (decl->is_indented()) -- indentation; - } - } - // something matched - return true; - } - // EO parse_block_nodes - - // parse imports inside the - Import_Obj Parser::parse_import() - { - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - std::vector> to_import; - bool first = true; - do { - while (lex< block_comment >()); - if (lex< quoted_string >()) { - to_import.push_back(std::pair(std::string(lexed), 0)); - } - else if (lex< uri_prefix >()) { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); - - if (lex< quoted_string >()) { - Expression_Obj quoted_url = parse_string(); - args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); - } - else if (String_Obj string_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); - } - else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { - Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); - } - else { - error("malformed URL"); - } - if (!lex< exactly<')'> >()) error("URI is missing ')'"); - to_import.push_back(std::pair("", result)); - } - else { - if (first) error("@import directive requires a url or quoted path"); - else error("expecting another url or quoted path in @import list"); - } - first = false; - } while (lex_css< exactly<','> >()); - - if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { - List_Obj import_queries = parse_media_queries(); - imp->import_queries(import_queries); - } - - for(auto location : to_import) { - if (location.second) { - imp->urls().push_back(location.second); - } - // check if custom importers want to take over the handling - else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { - // nobody wants it, so we do our import - ctx.import_url(imp, location.first, path); - } - } - - return imp; - } - - Definition_Obj Parser::parse_definition(Definition::Type which_type) - { - std::string which_str(lexed); - if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); - std::string name(Util::normalize_underscores(lexed)); - if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) - { error("Invalid function name \"" + name + "\"."); } - ParserState source_position_of_def = pstate; - Parameters_Obj params = parse_parameters(); - if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); - else stack.push_back(Scope::Function); - Block_Obj body = parse_block(); - stack.pop_back(); - return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); - } - - Parameters_Obj Parser::parse_parameters() - { - Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - params->append(parse_parameter()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - } - return params; - } - - Parameter_Obj Parser::parse_parameter() - { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); - } - while (lex< alternatives < spaces, block_comment > >()); - lex < variable >(); - std::string name(Util::normalize_underscores(lexed)); - ParserState pos = pstate; - Expression_Obj val; - bool is_rest = false; - while (lex< alternatives < spaces, block_comment > >()); - if (lex< exactly<':'> >()) { // there's a default value - while (lex< block_comment >()); - val = parse_space_list(); - } - else if (lex< exactly< ellipsis > >()) { - is_rest = true; - } - return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); - } - - Arguments_Obj Parser::parse_arguments() - { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - args->append(parse_argument()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } - return args; - } - - Argument_Obj Parser::parse_argument() - { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { - position += 2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - - Argument_Obj arg; - if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { - lex_css< variable >(); - std::string name(Util::normalize_underscores(lexed)); - ParserState p = pstate; - lex_css< exactly<':'> >(); - Expression_Obj val = parse_space_list(); - arg = SASS_MEMORY_NEW(Argument, p, val, name); - } - else { - bool is_arglist = false; - bool is_keyword = false; - Expression_Obj val = parse_space_list(); - List_Ptr l = Cast(val); - if (lex_css< exactly< ellipsis > >()) { - if (val->concrete_type() == Expression::MAP || ( - (l != NULL && l->separator() == SASS_HASH) - )) is_keyword = true; - else is_arglist = true; - } - arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); - } - return arg; - } - - Assignment_Obj Parser::parse_assignment() - { - std::string name(Util::normalize_underscores(lexed)); - ParserState var_source_position = pstate; - if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); - if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - Expression_Obj val; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.has_interpolants && lookahead.found) { - val = parse_value_schema(lookahead.found); - } else { - val = parse_list(); - } - bool is_default = false; - bool is_global = false; - while (peek< alternatives < default_flag, global_flag > >()) { - if (lex< default_flag >()) is_default = true; - else if (lex< global_flag >()) is_global = true; - } - return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); - } - - // a ruleset connects a selector and a block - Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) - { - NESTING_GUARD(nestings); - // inherit is_root from parent block - Block_Obj parent = block_stack.back(); - bool is_root = parent && parent->is_root(); - // make sure to move up the the last position - lex < optional_css_whitespace >(false, true); - // create the connector object (add parts later) - Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); - // parse selector static or as schema to be evaluated later - if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); - else { - Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); - list->schema(parse_selector_schema(lookahead.position, false)); - ruleset->selector(list); - } - // then parse the inner block - stack.push_back(Scope::Rules); - ruleset->block(parse_block()); - stack.pop_back(); - // update for end position - ruleset->update_pstate(pstate); - ruleset->block()->update_pstate(pstate); - // need this info for sanity checks - ruleset->is_root(is_root); - // return AST Node - return ruleset; - } - - // parse a selector schema that will be evaluated in the eval stage - // uses a string schema internally to do the actual schema handling - // in the eval stage we will be re-parse it into an actual selector - Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) - { - NESTING_GUARD(nestings); - // move up to the start - lex< optional_spaces >(); - const char* i = position; - // selector schema re-uses string schema implementation - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - // the selector schema is pretty much just a wrapper for the string schema - Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); - selector_schema->connect_parent(chroot == false); - selector_schema->media_block(last_media_block); - - // process until end - while (i < end_of_selector) { - // try to parse mutliple interpolants - if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { - // accumulate the preceding segment if the position has advanced - if (i < p) { - std::string parsed(i, p); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate += Offset(parsed); - str->update_pstate(pstate); - schema->append(str); - } - - // skip over all nested inner interpolations up to our own delimiter - const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); - // check if the interpolation never ends of only contains white-space (error out) - if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { - position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - // pass inner expression to the parser to resolve nested interpolations - pstate.add(p, p+2); - Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); - // set status on the list expression - interpolant->is_interpolant(true); - // schema->has_interpolants(true); - // add to the string schema - schema->append(interpolant); - // advance parser state - pstate.add(p+2, j); - // advance position - i = j; - } - // no more interpolants have been found - // add the last segment if there is one - else { - // make sure to add the last bits of the string up to the end (if any) - if (i < end_of_selector) { - std::string parsed(i, end_of_selector); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate += Offset(parsed); - str->update_pstate(pstate); - i = end_of_selector; - schema->append(str); - } - // exit loop - } - } - // EO until eos - - // update position - position = i; - - // update for end position - selector_schema->update_pstate(pstate); - schema->update_pstate(pstate); - - after_token = before_token = pstate; - - // return parsed result - return selector_schema.detach(); - } - // EO parse_selector_schema - - void Parser::parse_charset_directive() - { - lex < - sequence < - quoted_string, - optional_spaces, - exactly <';'> - > - >(); - } - - // called after parsing `kwd_include_directive` - Mixin_Call_Obj Parser::parse_include_directive() - { - // lex identifier into `lexed` var - lex_identifier(); // may error out - // normalize underscores to hyphens - std::string name(Util::normalize_underscores(lexed)); - // create the initial mixin call object - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); - // parse mandatory arguments - call->arguments(parse_arguments()); - // parse optional block - if (peek < exactly <'{'> >()) { - call->block(parse_block()); - } - // return ast node - return call.detach(); - } - // EO parse_include_directive - - // parse a list of complex selectors - // this is the main entry point for most - Selector_List_Obj Parser::parse_selector_list(bool chroot) - { - bool reloop; - bool had_linefeed = false; - NESTING_GUARD(nestings); - Complex_Selector_Obj sel; - Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); - group->media_block(last_media_block); - - if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - - do { - reloop = false; - - had_linefeed = had_linefeed || peek_newline(); - - if (peek_css< alternatives < class_char < selector_list_delims > > >()) - break; // in case there are superfluous commas at the end - - // now parse the complex selector - sel = parse_complex_selector(chroot); - - if (!sel) return group.detach(); - - sel->has_line_feed(had_linefeed); - - had_linefeed = false; - - while (peek_css< exactly<','> >()) - { - lex< css_comments >(false); - // consume everything up and including the comma separator - reloop = lex< exactly<','> >() != 0; - // remember line break (also between some commas) - had_linefeed = had_linefeed || peek_newline(); - // remember line break (also between some commas) - } - group->append(sel); - } - while (reloop); - while (lex_css< kwd_optional >()) { - group->is_optional(true); - } - // update for end position - group->update_pstate(pstate); - if (sel) sel->last()->has_line_break(false); - return group.detach(); - } - // EO parse_selector_list - - // a complex selector combines a compound selector with another - // complex selector, with one of four combinator operations. - // the compound selector (head) is optional, since the combinator - // can come first in the whole selector sequence (like `> DIV'). - Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) - { - - NESTING_GUARD(nestings); - String_Obj reference = 0; - lex < block_comment >(); - advanceToNextToken(); - Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); - - if (peek < end_of_file >()) return 0; - - // parse the left hand side - Compound_Selector_Obj lhs; - // special case if it starts with combinator ([+~>]) - if (!peek_css< class_char < selector_combinator_ops > >()) { - // parse the left hand side - lhs = parse_compound_selector(); - } - - - // parse combinator between lhs and rhs - Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; - if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; - else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; - else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; - else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { - // comments are allowed, but not spaces? - combinator = Complex_Selector::REFERENCE; - if (!lex < re_reference_combinator >()) return 0; - reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? - } - - if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; - - // lex < block_comment >(); - sel->head(lhs); - sel->combinator(combinator); - sel->media_block(last_media_block); - - if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); - // has linfeed after combinator? - sel->has_line_break(peek_newline()); - // sel->has_line_feed(has_line_feed); - - // check if we got the abort condition (ToDo: optimize) - if (!peek_css< class_char < complex_selector_delims > >()) { - // parse next selector in sequence - sel->tail(parse_complex_selector(true)); - } - - // add a parent selector if we are not in a root - // also skip adding parent ref if we only have refs - if (!sel->has_parent_ref() && !chroot) { - // create the objects to wrap parent selector reference - Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); - Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); - parent->media_block(last_media_block); - head->media_block(last_media_block); - // add simple selector - head->append(parent); - // selector may not have any head yet - if (!sel->head()) { sel->head(head); } - // otherwise we need to create a new complex selector and set the old one as its tail - else { - sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); - sel->media_block(last_media_block); - } - // peek for linefeed and remember result on head - // if (peek_newline()) head->has_line_break(true); - } - - sel->update_pstate(pstate); - // complex selector - return sel; - } - // EO parse_complex_selector - - // parse one compound selector, which is basically - // a list of simple selectors (directly adjacent) - // lex them exactly (without skipping white-space) - Compound_Selector_Obj Parser::parse_compound_selector() - { - // init an empty compound selector wrapper - Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); - seq->media_block(last_media_block); - - // skip initial white-space - lex< css_whitespace >(); - - // parse list - while (true) - { - // remove all block comments (don't skip white-space) - lex< delimited_by< slash_star, star_slash, false > >(false); - // parse functional - if (match < re_pseudo_selector >()) - { - seq->append(parse_simple_selector()); - } - // parse parent selector - else if (lex< exactly<'&'> >(false)) - { - // this produces a linefeed!? - seq->has_parent_reference(true); - seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); - // parent selector only allowed at start - // upcoming Sass may allow also trailing - if (seq->length() > 1) { - ParserState state(pstate); - Simple_Selector_Obj cur = (*seq)[seq->length()-1]; - Simple_Selector_Obj prev = (*seq)[seq->length()-2]; - std::string sel(prev->to_string({ NESTED, 5 })); - std::string found(cur->to_string({ NESTED, 5 })); - if (lex < identifier >()) { found += std::string(lexed); } - error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" - "\"" + found + "\" may only be used at the beginning of a compound selector.", state); - } - } - // parse type selector - else if (lex< re_type_selector >(false)) - { - seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); - } - // peek for abort conditions - else if (peek< spaces >()) break; - else if (peek< end_of_file >()) { break; } - else if (peek_css < class_char < selector_combinator_ops > >()) break; - else if (peek_css < class_char < complex_selector_delims > >()) break; - // otherwise parse another simple selector - else { - Simple_Selector_Obj sel = parse_simple_selector(); - if (!sel) return 0; - seq->append(sel); - } - } - - if (seq && !peek_css>>()) { - seq->has_line_break(peek_newline()); - } - - // EO while true - return seq; - - } - // EO parse_compound_selector - - Simple_Selector_Obj Parser::parse_simple_selector() - { - lex < css_comments >(false); - if (lex< class_name >()) { - return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); - } - else if (lex< id_name >()) { - return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); - } - else if (lex< alternatives < variable, number, static_reference_combinator > >()) { - return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); - } - else if (peek< pseudo_not >()) { - return parse_negated_selector(); - } - else if (peek< re_pseudo_selector >()) { - return parse_pseudo_selector(); - } - else if (peek< exactly<':'> >()) { - return parse_pseudo_selector(); - } - else if (lex < exactly<'['> >()) { - return parse_attribute_selector(); - } - else if (lex< placeholder >()) { - Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); - sel->media_block(last_media_block); - return sel; - } - else { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - // failed - return 0; - } - - Wrapped_Selector_Obj Parser::parse_negated_selector() - { - lex< pseudo_not >(); - std::string name(lexed); - ParserState nsource_position = pstate; - Selector_List_Obj negated = parse_selector_list(true); - if (!lex< exactly<')'> >()) { - error("negated selector is missing ')'"); - } - name.erase(name.size() - 1); - return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); - } - - // a pseudo selector often starts with one or two colons - // it can contain more selectors inside parentheses - Simple_Selector_Obj Parser::parse_pseudo_selector() { - if (lex< sequence< - optional < pseudo_prefix >, - // we keep the space within the name, strange enough - // ToDo: refactor output to schedule the space for it - // or do we really want to keep the real white-space? - sequence< identifier, optional < block_comment >, exactly<'('> > - > >()) - { - - std::string name(lexed); - name.erase(name.size() - 1); - ParserState p = pstate; - - // specially parse static stuff - // ToDo: really everything static? - if (peek_css < - sequence < - alternatives < - static_value, - binomial - >, - optional_css_whitespace, - exactly<')'> - > - >() - ) { - lex_css< alternatives < static_value, binomial > >(); - String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (lex_css< exactly<')'> >()) { - expr->can_compress_whitespace(true); - return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); - } - } - else if (Selector_List_Obj wrapped = parse_selector_list(true)) { - if (wrapped && lex_css< exactly<')'> >()) { - return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); - } - } - - } - // EO if pseudo selector - - else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { - return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); - } - else if(lex < pseudo_prefix >()) { - css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); - } - - css_error("Invalid CSS", " after ", ": expected \")\", was "); - - // unreachable statement - return 0; - } - - const char* Parser::re_attr_sensitive_close(const char* src) - { - return alternatives < exactly<']'>, exactly<'/'> >(src); - } - - const char* Parser::re_attr_insensitive_close(const char* src) - { - return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); - } - - Attribute_Selector_Obj Parser::parse_attribute_selector() - { - ParserState p = pstate; - if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); - std::string name(lexed); - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); - } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); - } - if (!lex_css< alternatives< exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match > >()) { - error("invalid operator in attribute selector for " + name); - } - std::string matcher(lexed); - - String_Obj value = 0; - if (lex_css< identifier >()) { - value = SASS_MEMORY_NEW(String_Constant, p, lexed); - } - else if (lex_css< quoted_string >()) { - value = parse_interpolated_chunk(lexed, true); // needed! - } - else { - error("expected a string constant or identifier in attribute selector for " + name); - } - - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); - } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); - } - error("unterminated attribute selector for " + name); - return NULL; // to satisfy compilers (error must not return) - } - - /* parse block comment and add to block */ - void Parser::parse_block_comments() - { - Block_Obj block = block_stack.back(); - - while (lex< block_comment >()) { - bool is_important = lexed.begin[2] == '!'; - // flag on second param is to skip loosely over comments - String_Obj contents = parse_interpolated_chunk(lexed, true, false); - block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); - } - } - - Declaration_Obj Parser::parse_declaration() { - String_Obj prop; - bool is_custom_property = false; - if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { - const std::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = parse_identifier_schema(); - } - else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { - const std::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - else { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - bool is_indented = true; - const std::string property(lexed); - if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); - if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); - if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty - if (is_custom_property) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); - } - lex < css_comments >(false); - if (peek_css< static_value >()) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); - } - else { - Expression_Obj value; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.found) { - if (lookahead.has_interpolants) { - value = parse_value_schema(lookahead.found); - } else { - value = parse_list(DELAYED); - } - } - else { - value = parse_list(DELAYED); - if (List_Ptr list = Cast(value)) { - if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } - } - lex < css_comments >(false); - Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); - decl->is_indented(is_indented); - decl->update_pstate(pstate); - return decl; - } - } - - // parse +/- and return false if negative - // this is never hit via spec tests - bool Parser::parse_number_prefix() - { - bool positive = true; - while(true) { - if (lex < block_comment >()) continue; - if (lex < number_prefix >()) continue; - if (lex < exactly < '-' > >()) { - positive = !positive; - continue; - } - break; - } - return positive; - } - - Expression_Obj Parser::parse_map() - { - NESTING_GUARD(nestings); - Expression_Obj key = parse_list(); - List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); - - // it's not a map so return the lexed value as a list value - if (!lex_css< exactly<':'> >()) - { return key; } - - List_Obj l = Cast(key); - if (l && l->separator() == SASS_COMMA) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - - Expression_Obj value = parse_space_list(); - - map->append(key); - map->append(value); - - while (lex_css< exactly<','> >()) - { - // allow trailing commas - #495 - if (peek_css< exactly<')'> >(position)) - { break; } - - key = parse_space_list(); - - if (!(lex< exactly<':'> >())) - { css_error("Invalid CSS", " after ", ": expected \":\", was "); } - - value = parse_space_list(); - - map->append(key); - map->append(value); - } - - ParserState ps = map->pstate(); - ps.offset = pstate - ps + pstate.offset; - map->pstate(ps); - - return map; - } - - Expression_Obj Parser::parse_bracket_list() - { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); - } - - bool has_paren = peek_css< exactly<'('> >() != NULL; - - // now try to parse a space list - Expression_Obj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - List_Obj l = Cast(list); - if (!l || l->is_bracketed() || has_paren) { - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); - bracketed_list->append(list); - return bracketed_list; - } - l->is_bracketed(true); - return l; - } - - // if we got so far, we actually do have a comma list - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); - // wrap the first expression - bracketed_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - bracketed_list->append(parse_space_list()); - } - // return the list - return bracketed_list; - } - - // parse list returns either a space separated list, - // a comma separated list or any bare expression found. - // so to speak: we unwrap items from lists if possible here! - Expression_Obj Parser::parse_list(bool delayed) - { - NESTING_GUARD(nestings); - return parse_comma_list(delayed); - } - - // will return singletons unwrapped - Expression_Obj Parser::parse_comma_list(bool delayed) - { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0); - } - - // now try to parse a space list - Expression_Obj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - // set_delay doesn't apply to list children - // so this will only undelay single values - if (!delayed) list->set_delayed(false); - return list; - } - - // if we got so far, we actually do have a comma list - List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); - // wrap the first expression - comma_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - comma_list->append(parse_space_list()); - } - // return the list - return comma_list; - } - // EO parse_comma_list - - // will return singletons unwrapped - Expression_Obj Parser::parse_space_list() - { - NESTING_GUARD(nestings); - Expression_Obj disj1 = parse_disjunction(); - // if it's a singleton, return it (don't wrap it) - if (peek_css< space_list_terminator >(position) - ) { - return disj1; } - - List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); - space_list->append(disj1); - - while ( - !(peek_css< space_list_terminator >(position)) && - peek_css< optional_css_whitespace >() != end - ) { - // the space is parsed implicitly? - space_list->append(parse_disjunction()); - } - // return the list - return space_list; - } - // EO parse_space_list - - // parse logical OR operation - Expression_Obj Parser::parse_disjunction() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parse the left hand side conjunction - Expression_Obj conj = parse_conjunction(); - // parse multiple right hand sides - std::vector operands; - while (lex_css< kwd_or >()) - operands.push_back(parse_conjunction()); - // if it's a singleton, return it directly - if (operands.size() == 0) return conj; - // fold all operands into one binary expression - Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_disjunction - - // parse logical AND operation - Expression_Obj Parser::parse_conjunction() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parse the left hand side relation - Expression_Obj rel = parse_relation(); - // parse multiple right hand sides - std::vector operands; - while (lex_css< kwd_and >()) { - operands.push_back(parse_relation()); - } - // if it's a singleton, return it directly - if (operands.size() == 0) return rel; - // fold all operands into one binary expression - Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_conjunction - - // parse comparison operations - Expression_Obj Parser::parse_relation() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parse the left hand side expression - Expression_Obj lhs = parse_expression(); - std::vector operands; - std::vector operators; - // if it's a singleton, return it (don't wrap it) - while (peek< alternatives < - kwd_eq, - kwd_neq, - kwd_gte, - kwd_gt, - kwd_lte, - kwd_lt - > >(position)) - { - // is directly adjancent to expression? - bool left_ws = peek < css_comments >() != NULL; - // parse the operator - enum Sass_OP op - = lex() ? Sass_OP::EQ - : lex() ? Sass_OP::NEQ - : lex() ? Sass_OP::GTE - : lex() ? Sass_OP::LTE - : lex() ? Sass_OP::GT - : lex() ? Sass_OP::LT - // we checked the possibilities on top of fn - : Sass_OP::EQ; - // is directly adjacent to expression? - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ op, left_ws, right_ws }); - operands.push_back(parse_expression()); - } - // we are called recursively for list, so we first - // fold inner binary expression which has delayed - // correctly set to zero. After folding we also unwrap - // single nested items. So we cannot set delay on the - // returned result here, as we have lost nestings ... - Expression_Obj ex = fold_operands(lhs, operands, operators); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // parse_relation - - // parse expression valid for operations - // called from parse_relation - // called from parse_for_directive - // called from parse_media_expression - // parse addition and subtraction operations - Expression_Obj Parser::parse_expression() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - // parses multiple add and subtract operations - // NOTE: make sure that identifiers starting with - // NOTE: dashes do NOT count as subtract operation - Expression_Obj lhs = parse_operators(); - // if it's a singleton, return it (don't wrap it) - if (!(peek_css< exactly<'+'> >(position) || - // condition is a bit misterious, but some combinations should not be counted as operations - (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || - (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || - peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) - { return lhs; } - - std::vector operands; - std::vector operators; - bool left_ws = peek < css_comments >() != NULL; - while ( - lex_css< exactly<'+'> >() || - - ( - ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) - && lex_css< sequence< negate< digit >, exactly<'-'> > >() - ) - - ) { - - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); - operands.push_back(parse_operators()); - left_ws = peek < css_comments >() != NULL; - } - - if (operands.size() == 0) return lhs; - Expression_Obj ex = fold_operands(lhs, operands, operators); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - - // parse addition and subtraction operations - Expression_Obj Parser::parse_operators() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - ParserState state(pstate); - Expression_Obj factor = parse_factor(); - // if it's a singleton, return it (don't wrap it) - std::vector operands; // factors - std::vector operators; // ops - // lex operations to apply to lhs - const char* left_ws = peek < css_comments >(); - while (lex_css< class_char< static_ops > >()) { - const char* right_ws = peek < css_comments >(); - switch(*lexed.begin) { - case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; - case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; - case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; - default: throw std::runtime_error("unknown static op parsed"); - } - operands.push_back(parse_factor()); - left_ws = peek < css_comments >(); - } - // operands and operators to binary expression - Expression_Obj ex = fold_operands(factor, operands, operators); - state.offset = pstate - state + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_operators - - - // called from parse_operators - // called from parse_value_schema - Expression_Obj Parser::parse_factor() - { - NESTING_GUARD(nestings); - lex < css_comments >(false); - if (lex_css< exactly<'('> >()) { - // parse_map may return a list - Expression_Obj value = parse_map(); - // lex the expected closing parenthesis - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); - // expression can be evaluated - return value; - } - else if (lex_css< exactly<'['> >()) { - // explicit bracketed - Expression_Obj value = parse_bracket_list(); - // lex the expected closing square bracket - if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); - return value; - } - // string may be interpolated - // if (lex< quoted_string >()) { - // return &parse_string(); - // } - else if (peek< ie_property >()) { - return parse_ie_property(); - } - else if (peek< ie_keyword_arg >()) { - return parse_ie_keyword_arg(); - } - else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { - return parse_calc_function(); - } - else if (lex < functional_schema >()) { - return parse_function_call_schema(); - } - else if (lex< identifier_schema >()) { - String_Obj string = parse_identifier_schema(); - if (String_Schema_Ptr schema = Cast(string)) { - if (lex < exactly < '(' > >()) { - schema->append(parse_list()); - lex < exactly < ')' > >(); - } - } - return string; - } - else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { - return parse_url_function_string(); - } - else if (peek< re_functional >()) { - return parse_function_call(); - } - else if (lex< exactly<'+'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'-'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'/'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< sequence< kwd_not > >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - // this whole branch is never hit via spec tests - else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { - if (parse_number_prefix()) return parse_value(); // prefix is positive - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); - if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else { - return parse_value(); - } - } - - bool number_has_zero(const std::string& parsed) - { - size_t L = parsed.length(); - return !( (L > 0 && parsed.substr(0, 1) == ".") || - (L > 1 && parsed.substr(0, 2) == "0.") || - (L > 1 && parsed.substr(0, 2) == "-.") || - (L > 2 && parsed.substr(0, 3) == "-0.") ); - } - - Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) - { - Number_Ptr nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "", - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) - { - Number_Ptr nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "%", - true); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) - { - size_t L = parsed.length(); - size_t num_pos = parsed.find_first_not_of(" \n\r\t"); - if (num_pos == std::string::npos) num_pos = L; - size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); - if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { - unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); - } - if (unit_pos == std::string::npos) unit_pos = L; - const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); - Number_Ptr nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(num.c_str()), - Token(number(parsed.c_str())), - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) - { - Color_Ptr color = NULL; - if (parsed[0] != '#') { - return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); - } - // chop off the '#' - std::string hext(parsed.substr(1)); - if (parsed.length() == 4) { - std::string r(2, parsed[1]); - std::string g(2, parsed[2]); - std::string b(2, parsed[3]); - color = SASS_MEMORY_NEW(Color, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 7) { - std::string r(parsed.substr(1,2)); - std::string g(parsed.substr(3,2)); - std::string b(parsed.substr(5,2)); - color = SASS_MEMORY_NEW(Color, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 9) { - std::string r(parsed.substr(1,2)); - std::string g(parsed.substr(3,2)); - std::string b(parsed.substr(5,2)); - std::string a(parsed.substr(7,2)); - color = SASS_MEMORY_NEW(Color, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - static_cast(strtol(a.c_str(), NULL, 16)) / 255, - parsed); - } - color->is_interpolant(false); - color->is_delayed(false); - return color; - } - - Value_Ptr Parser::color_or_string(const std::string& lexed) const - { - if (auto color = name_to_color(lexed)) { - auto c = SASS_MEMORY_NEW(Color, color); - c->is_delayed(true); - c->pstate(pstate); - c->disp(lexed); - return c; - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - - // parse one value for a list - Expression_Obj Parser::parse_value() - { - lex< css_comments >(false); - if (lex< ampersand >()) - { - if (match< ampersand >()) { - warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); - } - return SASS_MEMORY_NEW(Parent_Selector, pstate); } - - if (lex< kwd_important >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } - - // parse `10%4px` into separated items and not a schema - if (lex< sequence < percentage, lookahead < number > > >()) - { return lexed_percentage(lexed); } - - if (lex< sequence < number, lookahead< sequence < op, number > > > >()) - { return lexed_number(lexed); } - - // string may be interpolated - if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) - { return parse_string(); } - - if (const char* stop = peek< value_schema >()) - { return parse_value_schema(stop); } - - // string may be interpolated - if (lex< quoted_string >()) - { return parse_string(); } - - if (lex< kwd_true >()) - { return SASS_MEMORY_NEW(Boolean, pstate, true); } - - if (lex< kwd_false >()) - { return SASS_MEMORY_NEW(Boolean, pstate, false); } - - if (lex< kwd_null >()) - { return SASS_MEMORY_NEW(Null, pstate); } - - if (lex< identifier >()) { - return color_or_string(lexed); - } - - if (lex< percentage >()) - { return lexed_percentage(lexed); } - - // match hex number first because 0x000 looks like a number followed by an identifier - if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) - { return lexed_hex_color(lexed); } - - if (lex< hexa >()) - { - std::string s = lexed.to_string(); - - deprecated( - "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in", - "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.", - true, pstate - ); - - return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); - } - - if (lex< sequence < exactly <'#'>, identifier > >()) - { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } - - // also handle the 10em- foo special case - // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression - if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) - { return lexed_dimension(lexed); } - - if (lex< sequence< static_component, one_plus< strict_identifier > > >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - - if (lex< number >()) - { return lexed_number(lexed); } - - if (lex< variable >()) - { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } - - // Special case handling for `%` proceeding an interpolant. - if (lex< sequence< exactly<'%'>, optional< percentage > > >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - - // unreachable statement - return 0; - } - - // this parses interpolation inside other strings - // means the result should later be quoted again - String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) - { - const char* i = chunk.begin; - // see if there any interpolants - const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - - if (!p) { - String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); - if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); - return str_quoted; - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); - schema->is_interpolant(true); - while (i < chunk.end) { - p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace - if (j) { --j; - // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside string constant " + chunk.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - // check if we need quotes here (was not sure after merge) - if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); - break; - } - ++ i; - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value(bool top_level) - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - String_Schema_Obj tok; - if (!(tok = parse_css_variable_value_token(top_level))) { - return NULL; - } - - schema->concat(tok); - while ((tok = parse_css_variable_value_token(top_level))) { - schema->concat(tok); - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if ( - (top_level && lex< css_variable_top_level_value >(false)) || - (!top_level && lex< css_variable_value >(false)) - ) { - Token str(lexed); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); - } - else if (Expression_Obj tok = lex_interpolation()) { - if (String_Schema_Ptr s = Cast(tok)) { - schema->concat(s); - } else { - schema->append(tok); - } - } - else if (lex< quoted_string >()) { - Expression_Obj tok = parse_string(); - if (String_Schema_Ptr s = Cast(tok)) { - schema->concat(s); - } else { - schema->append(tok); - } - } - else { - if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { - if (lex< exactly<'('> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); - } - else if (lex< exactly<'['> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); - } - else if (lex< exactly<'{'> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); - } - } - } - - return schema->length() > 0 ? schema.detach() : NULL; - } - - Value_Obj Parser::parse_static_value() - { - lex< static_value >(); - Token str(lexed); - // static values always have trailing white- - // space and end delimiter (\s*[;]$) included - --pstate.offset.column; - --after_token.column; - --str.end; - --position; - - return color_or_string(str.time_wspace());; - } - - String_Obj Parser::parse_string() - { - return parse_interpolated_chunk(Token(lexed)); - } - - String_Obj Parser::parse_ie_property() - { - lex< ie_property >(); - Token str(lexed); - const char* i = str.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); - if (!p) { - return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); - } - - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < str.end) { - p = find_first_in_interval< exactly, block_comment >(i, str.end); - if (p) { - if (i < p) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty - } - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside IE function " + str.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < str.end) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); - } - break; - } - } - return schema; - } - - String_Obj Parser::parse_ie_keyword_arg() - { - String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); - if (lex< variable >()) { - kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); - } else { - lex< alternatives< identifier_schema, identifier > >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - lex< exactly<'='> >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (peek< variable >()) kwd_arg->append(parse_list()); - else if (lex< number >()) { - std::string parsed(lexed); - Util::normalize_decimals(parsed); - kwd_arg->append(lexed_number(parsed)); - } - else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } - return kwd_arg; - } - - String_Schema_Obj Parser::parse_value_schema(const char* stop) - { - // initialize the string schema object to add tokens - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - - if (peek>()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - - const char* e; - const char* ee = end; - end = stop; - size_t num_items = 0; - bool need_space = false; - while (position < stop) { - // parse space between tokens - if (lex< spaces >() && num_items) { - need_space = true; - } - if (need_space) { - need_space = false; - // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - } - if ((e = peek< re_functional >()) && e < stop) { - schema->append(parse_function_call()); - } - // lex an interpolant /#{...}/ - else if (lex< exactly < hash_lbrace > >()) { - // Try to lex static expression first - if (peek< exactly< rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - Expression_Obj ex; - if (lex< re_static_expression >()) { - ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } else { - ex = parse_list(true); - } - ex->is_interpolant(true); - schema->append(ex); - if (!lex < exactly < rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - } - // lex some string constants or other valid token - // Note: [-+] chars are left over from i.e. `#{3}+3` - else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - // lex a quoted string - else if (lex< quoted_string >()) { - // need_space = true; - // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - // else need_space = true; - schema->append(parse_string()); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - if (peek < exactly < '-' > >()) break; - } - else if (lex< identifier >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - } - // lex (normalized) variable - else if (lex< variable >()) { - std::string name(Util::normalize_underscores(lexed)); - schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); - } - // lex percentage value - else if (lex< percentage >()) { - schema->append(lexed_percentage(lexed)); - } - // lex dimension value - else if (lex< dimension >()) { - schema->append(lexed_dimension(lexed)); - } - // lex number value - else if (lex< number >()) { - schema->append(lexed_number(lexed)); - } - // lex hex color value - else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { - schema->append(lexed_hex_color(lexed)); - } - else if (lex< sequence < exactly <'#'>, identifier > >()) { - schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); - } - // lex a value in parentheses - else if (peek< parenthese_scope >()) { - schema->append(parse_factor()); - } - else { - break; - } - ++num_items; - } - if (position != stop) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); - position = stop; - } - end = ee; - return schema; - } - - // this parses interpolation outside other strings - // means the result must not be quoted again later - String_Obj Parser::parse_identifier_schema() - { - Token id(lexed); - const char* i = id.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); - if (!p) { - return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < id.end) { - p = find_first_in_interval< exactly, block_comment >(i, id.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - const char* o = position; position = i; - schema->append(parse_value_schema(p)); - position = o; - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); - interp_node->is_interpolant(true); - schema->append(interp_node); - // schema->has_interpolants(true); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside interpolated identifier " + id.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < end) { - const char* o = position; position = i; - schema->append(parse_value_schema(id.end)); - position = o; - } - break; - } - } - return schema ? schema.detach() : 0; - } - - // calc functions should preserve arguments - Function_Call_Obj Parser::parse_calc_function() - { - lex< identifier >(); - std::string name(lexed); - ParserState call_pos = pstate; - lex< exactly<'('> >(); - ParserState arg_pos = pstate; - const char* arg_beg = position; - parse_list(); - const char* arg_end = position; - lex< skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > >(); - - Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); - args->append(arg); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - String_Obj Parser::parse_url_function_string() - { - std::string prefix(""); - if (lex< uri_prefix >()) { - prefix = std::string(lexed); - } - - lex < optional_spaces >(); - String_Obj url_string = parse_url_function_argument(); - - std::string suffix(""); - if (lex< real_uri_suffix >()) { - suffix = std::string(lexed); - } - - std::string uri(""); - if (url_string) { - uri = url_string->to_string({ NESTED, 5 }); - } - - if (String_Schema_Ptr schema = Cast(url_string)) { - String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); - res->append(schema); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); - return res; - } else { - std::string res = prefix + uri + suffix; - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - } - - String_Obj Parser::parse_url_function_argument() - { - const char* p = position; - - std::string uri(""); - if (lex< real_uri_value >(false)) { - uri = lexed.to_string(); - } - - if (peek< exactly< hash_lbrace > >()) { - const char* pp = position; - // TODO: error checking for unclosed interpolants - while (pp && peek< exactly< hash_lbrace > >(pp)) { - pp = sequence< interpolant, real_uri_value >(pp); - } - position = pp; - return parse_interpolated_chunk(Token(p, position)); - } - else if (uri != "") { - std::string res = Util::rtrim(uri); - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - - return 0; - } - - Function_Call_Obj Parser::parse_function_call() - { - lex< identifier >(); - std::string name(lexed); - - if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) - { error("Cannot call content-exists() except within a mixin."); } - - ParserState call_pos = pstate; - Arguments_Obj args = parse_arguments(); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - Function_Call_Schema_Obj Parser::parse_function_call_schema() - { - String_Obj name = parse_identifier_schema(); - ParserState source_position_of_call = pstate; - Arguments_Obj args = parse_arguments(); - - return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); - } - - Content_Obj Parser::parse_content_directive() - { - return SASS_MEMORY_NEW(Content, pstate); - } - - If_Obj Parser::parse_if_directive(bool else_if) - { - stack.push_back(Scope::Control); - ParserState if_source_position = pstate; - bool root = block_stack.back()->is_root(); - Expression_Obj predicate = parse_list(); - Block_Obj block = parse_block(root); - Block_Obj alternative = NULL; - - // only throw away comment if we parse a case - // we want all other comments to be parsed - if (lex_css< elseif_directive >()) { - alternative = SASS_MEMORY_NEW(Block, pstate); - alternative->append(parse_if_directive(true)); - } - else if (lex_css< kwd_else_directive >()) { - alternative = parse_block(root); - } - stack.pop_back(); - return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); - } - - For_Obj Parser::parse_for_directive() - { - stack.push_back(Scope::Control); - ParserState for_source_position = pstate; - bool root = block_stack.back()->is_root(); - lex_variable(); - std::string var(Util::normalize_underscores(lexed)); - if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); - Expression_Obj lower_bound = parse_expression(); - bool inclusive = false; - if (lex< kwd_through >()) inclusive = true; - else if (lex< kwd_to >()) inclusive = false; - else error("expected 'through' or 'to' keyword in @for directive"); - Expression_Obj upper_bound = parse_expression(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); - } - - // helper to parse a var token - Token Parser::lex_variable() - { - // peek for dollar sign first - if (!peek< exactly <'$'> >()) { - css_error("Invalid CSS", " after ", ": expected \"$\", was "); - } - // we expect a simple identifier as the call name - if (!lex< sequence < exactly <'$'>, identifier > >()) { - lex< exactly <'$'> >(); // move pstate and position up - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return token; - } - // helper to parse identifier - Token Parser::lex_identifier() - { - // we expect a simple identifier as the call name - if (!lex< identifier >()) { // ToDo: pstate wrong? - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return token; - } - - Each_Obj Parser::parse_each_directive() - { - stack.push_back(Scope::Control); - ParserState each_source_position = pstate; - bool root = block_stack.back()->is_root(); - std::vector vars; - lex_variable(); - vars.push_back(Util::normalize_underscores(lexed)); - while (lex< exactly<','> >()) { - if (!lex< variable >()) error("@each directive requires an iteration variable"); - vars.push_back(Util::normalize_underscores(lexed)); - } - if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); - Expression_Obj list = parse_list(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); - } - - // called after parsing `kwd_while_directive` - While_Obj Parser::parse_while_directive() - { - stack.push_back(Scope::Control); - bool root = block_stack.back()->is_root(); - // create the initial while call object - While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); - // parse mandatory predicate - Expression_Obj predicate = parse_list(); - List_Obj l = Cast(predicate); - if (!predicate || (l && !l->length())) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); - } - call->predicate(predicate); - // parse mandatory block - call->block(parse_block(root)); - // return ast node - stack.pop_back(); - // return ast node - return call.detach(); - } - - // EO parse_while_directive - Media_Block_Obj Parser::parse_media_block() - { - stack.push_back(Scope::Media); - Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); - - media_block->media_queries(parse_media_queries()); - - Media_Block_Obj prev_media_block = last_media_block; - last_media_block = media_block; - media_block->block(parse_css_block()); - last_media_block = prev_media_block; - stack.pop_back(); - return media_block.detach(); - } - - List_Obj Parser::parse_media_queries() - { - advanceToNextToken(); - List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); - if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); - while (lex_css < exactly <','> >()) queries->append(parse_media_query()); - queries->update_pstate(pstate); - return queries.detach(); - } - - // Expression_Ptr Parser::parse_media_query() - Media_Query_Obj Parser::parse_media_query() - { - advanceToNextToken(); - Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); - if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } - else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } - - if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); - else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); - else media_query->append(parse_media_expression()); - - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - if (lex < identifier_schema >()) { - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - schema->append(media_query->media_type()); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - schema->append(parse_identifier_schema()); - media_query->media_type(schema); - } - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - - media_query->update_pstate(pstate); - - return media_query; - } - - Media_Query_Expression_Obj Parser::parse_media_expression() - { - if (lex < identifier_schema >()) { - String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); - } - if (!lex_css< exactly<'('> >()) { - error("media query expression must begin with '('"); - } - Expression_Obj feature; - if (peek_css< exactly<')'> >()) { - error("media feature required in media query expression"); - } - feature = parse_expression(); - Expression_Obj expression = 0; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!lex_css< exactly<')'> >()) { - error("unclosed parenthesis in media query expression"); - } - return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); - } - - // lexed after `kwd_supports_directive` - // these are very similar to media blocks - Supports_Block_Obj Parser::parse_supports_directive() - { - Supports_Condition_Obj cond = parse_supports_condition(); - if (!cond) { - css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); - } - // create the ast node object for the support queries - Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); - // additional block is mandatory - // parse inner block - query->block(parse_block()); - // return ast node - return query; - } - - // parse one query operation - // may encounter nested queries - Supports_Condition_Obj Parser::parse_supports_condition() - { - lex < css_whitespace >(); - Supports_Condition_Obj cond; - if ((cond = parse_supports_negation())) return cond; - if ((cond = parse_supports_operator())) return cond; - if ((cond = parse_supports_interpolation())) return cond; - return cond; - } - - Supports_Condition_Obj Parser::parse_supports_negation() - { - if (!lex < kwd_not >()) return 0; - Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); - } - - Supports_Condition_Obj Parser::parse_supports_operator() - { - Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - if (cond.isNull()) return 0; - - while (true) { - Supports_Operator::Operand op = Supports_Operator::OR; - if (lex < kwd_and >()) { op = Supports_Operator::AND; } - else if(!lex < kwd_or >()) { break; } - - lex < css_whitespace >(); - Supports_Condition_Obj right = parse_supports_condition_in_parens(); - - // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); - cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); - } - return cond; - } - - Supports_Condition_Obj Parser::parse_supports_interpolation() - { - if (!lex < interpolant >()) return 0; - - String_Obj interp = parse_interpolated_chunk(lexed); - if (!interp) return 0; - - return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); - } - - // TODO: This needs some major work. Although feature conditions - // look like declarations their semantics differ significantly - Supports_Condition_Obj Parser::parse_supports_declaration() - { - Supports_Condition_Ptr cond; - // parse something declaration like - Expression_Obj feature = parse_expression(); - Expression_Obj expression = 0; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!feature || !expression) error("@supports condition expected declaration"); - cond = SASS_MEMORY_NEW(Supports_Declaration, - feature->pstate(), - feature, - expression); - // ToDo: maybe we need an additional error condition? - return cond; - } - - Supports_Condition_Obj Parser::parse_supports_condition_in_parens() - { - Supports_Condition_Obj interp = parse_supports_interpolation(); - if (interp != 0) return interp; - - if (!lex < exactly <'('> >()) return 0; - lex < css_whitespace >(); - - Supports_Condition_Obj cond = parse_supports_condition(); - if (cond != 0) { - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - } else { - cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - } - lex < css_whitespace >(); - return cond; - } - - At_Root_Block_Obj Parser::parse_at_root_block() - { - stack.push_back(Scope::AtRoot); - ParserState at_source_position = pstate; - Block_Obj body = 0; - At_Root_Query_Obj expr; - Lookahead lookahead_result; - if (lex_css< exactly<'('> >()) { - expr = parse_at_root_query(); - } - if (peek_css < exactly<'{'> >()) { - lex (); - body = parse_block(true); - } - else if ((lookahead_result = lookahead_for_selector(position)).found) { - Ruleset_Obj r = parse_ruleset(lookahead_result); - body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); - body->append(r); - } - At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); - if (!expr.isNull()) at_root->expression(expr); - stack.pop_back(); - return at_root; - } - - At_Root_Query_Obj Parser::parse_at_root_query() - { - if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); - - if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { - css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); - } - - Expression_Obj feature = parse_list(); - if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); - Expression_Obj expression = parse_list(); - List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); - - if (expression->concrete_type() == Expression::LIST) { - value = Cast(expression); - } - else value->append(expression); - - At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, - value->pstate(), - feature, - value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); - return cond; - } - - Directive_Obj Parser::parse_special_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - // this whole branch is never hit via spec tests - - Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - // this whole branch is never hit via spec tests - Directive_Obj Parser::parse_prefixed_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - - Directive_Obj Parser::parse_directive() - { - Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); - String_Schema_Obj val = parse_almost_any_value(); - // strip left and right if they are of type string - directive->value(val); - if (peek< exactly<'{'> >()) { - directive->block(parse_block()); - } - return directive; - } - - Expression_Obj Parser::lex_interpolation() - { - if (lex < interpolant >(true) != NULL) { - return parse_interpolated_chunk(lexed, true); - } - return 0; - } - - Expression_Obj Parser::lex_interp_uri() - { - // create a string schema by lexing optional interpolations - return lex_interp< re_string_uri_open, re_string_uri_close >(); - } - - Expression_Obj Parser::lex_interp_string() - { - Expression_Obj rv; - if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; - if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; - return rv; - } - - Expression_Obj Parser::lex_almost_any_value_chars() - { - const char* match = - lex < - one_plus < - alternatives < - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - sequence < - exactly < url_kwd >, - exactly <'('> - > - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - > - >(false); - if (match) { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - return NULL; - } - - Expression_Obj Parser::lex_almost_any_value_token() - { - Expression_Obj rv; - if (*position == 0) return 0; - if ((rv = lex_almost_any_value_chars())) return rv; - // if ((rv = lex_block_comment())) return rv; - // if ((rv = lex_single_line_comment())) return rv; - if ((rv = lex_interp_string())) return rv; - if ((rv = lex_interp_uri())) return rv; - if ((rv = lex_interpolation())) return rv; - if (lex< alternatives< hex, hex0 > >()) - { return lexed_hex_color(lexed); } - return rv; - } - - String_Schema_Obj Parser::parse_almost_any_value() - { - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (*position == 0) return 0; - lex < spaces >(false); - Expression_Obj token = lex_almost_any_value_token(); - if (!token) return 0; - schema->append(token); - if (*position == 0) { - schema->rtrim(); - return schema.detach(); - } - - while ((token = lex_almost_any_value_token())) { - schema->append(token); - } - - lex < css_whitespace >(); - - schema->rtrim(); - - return schema.detach(); - } - - Warning_Obj Parser::parse_warning() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); - } - - Error_Obj Parser::parse_error() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); - } - - Debug_Obj Parser::parse_debug() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); - } - - Return_Obj Parser::parse_return_directive() - { - // check that we do not have an empty list (ToDo: check if we got all cases) - if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) - { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - return SASS_MEMORY_NEW(Return, pstate, parse_list()); - } - - Lookahead Parser::lookahead_for_selector(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - rv.error = p; - if (const char* q = - peek < - re_selector_list - >(p) - ) { - bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; - bool could_be_escaped = false; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - // A property that's ambiguous with a nested selector is interpreted as a - // custom property. - if (*p == ':' && !could_be_escaped) { - rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); - } - could_be_escaped = *p == '\\'; - ++ p; - } - // store anyway } - - - // ToDo: remove - rv.error = q; - rv.position = q; - // check expected opening bracket - // only after successfull matching - if (peek < exactly<'{'> >(q)) rv.found = q; - // else if (peek < end_of_file >(q)) rv.found = q; - else if (peek < exactly<'('> >(q)) rv.found = q; - // else if (peek < exactly<';'> >(q)) rv.found = q; - // else if (peek < exactly<'}'> >(q)) rv.found = q; - if (rv.found || *p == 0) rv.error = 0; - } - - rv.parsable = ! rv.has_interpolants; - - // return result - return rv; - - } - // EO lookahead_for_selector - - // used in parse_block_nodes and parse_special_directive - // ToDo: actual usage is still not really clear to me? - Lookahead Parser::lookahead_for_include(const char* start) - { - // we actually just lookahead for a selector - Lookahead rv = lookahead_for_selector(start); - // but the "found" rules are different - if (const char* p = rv.position) { - // check for additional abort condition - if (peek < exactly<';'> >(p)) rv.found = p; - else if (peek < exactly<'}'> >(p)) rv.found = p; - } - // return result - return rv; - } - // EO lookahead_for_include - - // look ahead for a token with interpolation in it - // we mostly use the result if there is an interpolation - // everything that passes here gets parsed as one schema - // meaning it will not be parsed as a space separated list - Lookahead Parser::lookahead_for_value(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - if (const char* q = - peek < - non_greedy < - alternatives < - // consume whitespace - block_comment, // spaces, - // main tokens - sequence < - interpolant, - optional < - quoted_string - > - >, - identifier, - variable, - // issue #442 - sequence < - parenthese_scope, - interpolant, - optional < - quoted_string - > - > - >, - sequence < - // optional_spaces, - alternatives < - // end_of_file, - exactly<'{'>, - exactly<'}'>, - exactly<';'> - > - > - > - >(p) - ) { - if (p == q) return rv; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - ++ p; - } - // store anyway - // ToDo: remove - rv.position = q; - // check expected opening bracket - // only after successful matching - if (peek < exactly<'{'> >(q)) rv.found = q; - else if (peek < exactly<';'> >(q)) rv.found = q; - else if (peek < exactly<'}'> >(q)) rv.found = q; - } - - // return result - return rv; - } - // EO lookahead_for_value - - void Parser::read_bom() - { - size_t skip = 0; - std::string encoding; - bool utf_8 = false; - switch ((unsigned char) source[0]) { - case 0xEF: - skip = check_bom_chars(source, end, utf_8_bom, 3); - encoding = "UTF-8"; - utf_8 = true; - break; - case 0xFE: - skip = check_bom_chars(source, end, utf_16_bom_be, 2); - encoding = "UTF-16 (big endian)"; - break; - case 0xFF: - skip = check_bom_chars(source, end, utf_16_bom_le, 2); - skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); - encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); - break; - case 0x00: - skip = check_bom_chars(source, end, utf_32_bom_be, 4); - encoding = "UTF-32 (big endian)"; - break; - case 0x2B: - skip = check_bom_chars(source, end, utf_7_bom_1, 4) - | check_bom_chars(source, end, utf_7_bom_2, 4) - | check_bom_chars(source, end, utf_7_bom_3, 4) - | check_bom_chars(source, end, utf_7_bom_4, 4) - | check_bom_chars(source, end, utf_7_bom_5, 5); - encoding = "UTF-7"; - break; - case 0xF7: - skip = check_bom_chars(source, end, utf_1_bom, 3); - encoding = "UTF-1"; - break; - case 0xDD: - skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); - encoding = "UTF-EBCDIC"; - break; - case 0x0E: - skip = check_bom_chars(source, end, scsu_bom, 3); - encoding = "SCSU"; - break; - case 0xFB: - skip = check_bom_chars(source, end, bocu_1_bom, 3); - encoding = "BOCU-1"; - break; - case 0x84: - skip = check_bom_chars(source, end, gb_18030_bom, 4); - encoding = "GB-18030"; - break; - default: break; - } - if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); - position += skip; - } - - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) - { - size_t skip = 0; - if (src + len > end) return 0; - for (size_t i = 0; i < len; ++i, ++skip) { - if ((unsigned char) src[i] != bom[i]) return 0; - } - return skip; - } - - - Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) - { - for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); - } - return base; - } - - Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) - { - if (String_Schema_Ptr schema = Cast(base)) { - // return schema; - if (schema->has_interpolants()) { - if (i + 1 < operands.size() && ( - (ops[0].operand == Sass_OP::EQ) - || (ops[0].operand == Sass_OP::ADD) - || (ops[0].operand == Sass_OP::DIV) - || (ops[0].operand == Sass_OP::MUL) - || (ops[0].operand == Sass_OP::NEQ) - || (ops[0].operand == Sass_OP::LT) - || (ops[0].operand == Sass_OP::GT) - || (ops[0].operand == Sass_OP::LTE) - || (ops[0].operand == Sass_OP::GTE) - )) { - Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); - return rhs; - } - // return schema; - } - } - - for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema_Ptr schema = Cast(operands[i])) { - if (schema->has_interpolants()) { - if (i + 1 < S) { - // this whole branch is never hit via spec tests - Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); - return base; - } - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - return base; - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - Binary_Expression_Ptr b = Cast(base.ptr()); - if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { - base->is_delayed(true); - } - } - // nested binary expression are never to be delayed - if (Binary_Expression_Ptr b = Cast(base)) { - if (Cast(b->left())) base->set_delayed(false); - if (Cast(b->right())) base->set_delayed(false); - } - return base; - } - - void Parser::error(std::string msg, Position pos) - { - Position p(pos.line ? pos : before_token); - ParserState pstate(path, source, p, Offset(0, 0)); - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, msg); - } - - void Parser::error(std::string msg) - { - error(msg, pstate); - } - - // print a css parsing error with actual context information from parsed source - void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) - { - int max_len = 18; - const char* end = this->end; - while (*end != 0) ++ end; - const char* pos = peek < optional_spaces >(); - if (!pos) pos = position; - - const char* last_pos(pos); - if (last_pos > source) { - utf8::prior(last_pos, source); - } - // backup position to last significant char - while (trim && last_pos > source && last_pos < end) { - if (!Prelexer::is_space(*last_pos)) break; - utf8::prior(last_pos, source); - } - - bool ellipsis_left = false; - const char* pos_left(last_pos); - const char* end_left(last_pos); - - if (*pos_left) utf8::next(pos_left, end); - if (*end_left) utf8::next(end_left, end); - while (pos_left > source) { - if (utf8::distance(pos_left, end_left) >= max_len) { - utf8::prior(pos_left, source); - ellipsis_left = *(pos_left) != '\n' && - *(pos_left) != '\r'; - utf8::next(pos_left, end); - break; - } - - const char* prev = pos_left; - utf8::prior(prev, source); - if (*prev == '\r') break; - if (*prev == '\n') break; - pos_left = prev; - } - if (pos_left < source) { - pos_left = source; - } - - bool ellipsis_right = false; - const char* end_right(pos); - const char* pos_right(pos); - while (end_right < end) { - if (utf8::distance(pos_right, end_right) > max_len) { - ellipsis_left = *(pos_right) != '\n' && - *(pos_right) != '\r'; - break; - } - if (*end_right == '\r') break; - if (*end_right == '\n') break; - utf8::next(end_right, end); - } - // if (*end_right == 0) end_right ++; - - std::string left(pos_left, end_left); - std::string right(pos_right, end_right); - size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; - size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; - if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); - if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; - // Hotfix when source is null, probably due to interpolation parsing!? - if (source == NULL || *source == 0) source = pstate.src; - // now pass new message to the more generic error function - error(msg + prefix + quote(left) + middle + quote(right)); - } - -} diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp deleted file mode 100644 index d2a6ddc1a..000000000 --- a/src/libsass/src/parser.hpp +++ /dev/null @@ -1,400 +0,0 @@ -#ifndef SASS_PARSER_H -#define SASS_PARSER_H - -#include -#include - -#include "ast.hpp" -#include "position.hpp" -#include "context.hpp" -#include "position.hpp" -#include "prelexer.hpp" - -#ifndef MAX_NESTING -// Note that this limit is not an exact science -// it depends on various factors, which some are -// not under our control (compile time or even OS -// dependent settings on the available stack size) -// It should fix most common segfault cases though. -#define MAX_NESTING 512 -#endif - -struct Lookahead { - const char* found; - const char* error; - const char* position; - bool parsable; - bool has_interpolants; - bool is_custom_property; -}; - -namespace Sass { - - class Parser : public ParserState { - public: - - enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; - - Context& ctx; - std::vector block_stack; - std::vector stack; - Media_Block_Ptr last_media_block; - const char* source; - const char* position; - const char* end; - Position before_token; - Position after_token; - ParserState pstate; - Backtraces traces; - size_t indentation; - size_t nestings; - - Token lexed; - - Parser(Context& ctx, const ParserState& pstate, Backtraces traces) - : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), - pstate(pstate), traces(traces), indentation(0), nestings(0) - { - stack.push_back(Scope::Root); - } - - // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); - static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); - // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); - -#ifdef __clang__ - - // lex and peak uses the template parameter to branch on the action, which - // triggers clangs tautological comparison on the single-comparison - // branches. This is not a bug, just a merging of behaviour into - // one function - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - -#endif - - - // skip current token and next whitespace - // moves ParserState right before next token - void advanceToNextToken(); - - bool peek_newline(const char* start = 0); - - // skip over spaces, tabs and line comments - template - const char* sneak(const char* start = 0) - { - using namespace Prelexer; - - // maybe use optional start position from arguments? - const char* it_position = start ? start : position; - - // skip white-space? - if (mx == spaces || - mx == no_spaces || - mx == css_comments || - mx == css_whitespace || - mx == optional_spaces || - mx == optional_css_comments || - mx == optional_css_whitespace - ) { - return it_position; - } - - // skip over spaces, tabs and sass line comments - const char* pos = optional_css_whitespace(it_position); - // always return a valid position - return pos ? pos : it_position; - - } - - // match will not skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* match(const char* start = 0) - { - // match the given prelexer - return mx(position); - } - - // peek will only skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* peek(const char* start = 0) - { - - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - const char* it_before_token = sneak < mx >(start); - - // match the given prelexer - const char* match = mx(it_before_token); - - // check if match is in valid range - return match <= end ? match : 0; - - } - - // white-space handling is built into the lexer - // this way you do not need to parse it yourself - // some matchers don't accept certain white-space - // we do not support start arg, since we manipulate - // sourcemap offset and we modify the position pointer! - // lex will only skip over space, tabs and line comment - template - const char* lex(bool lazy = true, bool force = false) - { - - if (*position == 0) return 0; - - // position considered before lexed token - // we can skip whitespace or comments for - // lazy developers (but we need control) - const char* it_before_token = position; - - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - if (lazy) it_before_token = sneak < mx >(position); - - // now call matcher to get position after token - const char* it_after_token = mx(it_before_token); - - // check if match is in valid range - if (it_after_token > end) return 0; - - // maybe we want to update the parser state anyway? - if (force == false) { - // assertion that we got a valid match - if (it_after_token == 0) return 0; - // assertion that we actually lexed something - if (it_after_token == it_before_token) return 0; - } - - // create new lexed token object (holds the parse results) - lexed = Token(position, it_before_token, it_after_token); - - // advance position (add whitespace before current token) - before_token = after_token.add(position, it_before_token); - - // update after_token position for current token - after_token.add(it_before_token, it_after_token); - - // ToDo: could probably do this incremetal on original object (API wants offset?) - pstate = ParserState(path, source, lexed, before_token, after_token - before_token); - - // advance internal char iterator - return position = it_after_token; - - } - - // lex_css skips over space, tabs, line and block comment - // all block comments will be consumed and thrown away - // source-map position will point to token after the comment - template - const char* lex_css() - { - // copy old token - Token prev = lexed; - // store previous pointer - const char* oldpos = position; - Position bt = before_token; - Position at = after_token; - ParserState op = pstate; - // throw away comments - // update srcmap position - lex < Prelexer::css_comments >(); - // now lex a new token - const char* pos = lex< mx >(); - // maybe restore prev state - if (pos == 0) { - pstate = op; - lexed = prev; - position = oldpos; - after_token = at; - before_token = bt; - } - // return match - return pos; - } - - // all block comments will be skipped and thrown away - template - const char* peek_css(const char* start = 0) - { - // now peek a token (skip comments first) - return peek< mx >(peek < Prelexer::css_comments >(start)); - } - -#ifdef __clang__ - -#pragma clang diagnostic pop - -#endif - - void error(std::string msg); - void error(std::string msg, Position pos); - // generate message with given and expected sample - // text before and in the middle are configurable - void css_error(const std::string& msg, - const std::string& prefix = " after ", - const std::string& middle = ", was: ", - const bool trim = true); - void read_bom(); - - Block_Obj parse(); - Import_Obj parse_import(); - Definition_Obj parse_definition(Definition::Type which_type); - Parameters_Obj parse_parameters(); - Parameter_Obj parse_parameter(); - Mixin_Call_Obj parse_include_directive(); - Arguments_Obj parse_arguments(); - Argument_Obj parse_argument(); - Assignment_Obj parse_assignment(); - Ruleset_Obj parse_ruleset(Lookahead lookahead); - Selector_List_Obj parse_selector_list(bool chroot); - Complex_Selector_Obj parse_complex_selector(bool chroot); - Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); - Compound_Selector_Obj parse_compound_selector(); - Simple_Selector_Obj parse_simple_selector(); - Wrapped_Selector_Obj parse_negated_selector(); - Simple_Selector_Obj parse_pseudo_selector(); - Attribute_Selector_Obj parse_attribute_selector(); - Block_Obj parse_block(bool is_root = false); - Block_Obj parse_css_block(bool is_root = false); - bool parse_block_nodes(bool is_root = false); - bool parse_block_node(bool is_root = false); - - bool parse_number_prefix(); - Declaration_Obj parse_declaration(); - Expression_Obj parse_map(); - Expression_Obj parse_bracket_list(); - Expression_Obj parse_list(bool delayed = false); - Expression_Obj parse_comma_list(bool delayed = false); - Expression_Obj parse_space_list(); - Expression_Obj parse_disjunction(); - Expression_Obj parse_conjunction(); - Expression_Obj parse_relation(); - Expression_Obj parse_expression(); - Expression_Obj parse_operators(); - Expression_Obj parse_factor(); - Expression_Obj parse_value(); - Function_Call_Obj parse_calc_function(); - Function_Call_Obj parse_function_call(); - Function_Call_Schema_Obj parse_function_call_schema(); - String_Obj parse_url_function_string(); - String_Obj parse_url_function_argument(); - String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); - String_Obj parse_string(); - Value_Obj parse_static_value(); - String_Schema_Obj parse_css_variable_value(bool top_level = true); - String_Schema_Obj parse_css_variable_value_token(bool top_level = true); - String_Obj parse_ie_property(); - String_Obj parse_ie_keyword_arg(); - String_Schema_Obj parse_value_schema(const char* stop); - String_Obj parse_identifier_schema(); - If_Obj parse_if_directive(bool else_if = false); - For_Obj parse_for_directive(); - Each_Obj parse_each_directive(); - While_Obj parse_while_directive(); - Return_Obj parse_return_directive(); - Content_Obj parse_content_directive(); - void parse_charset_directive(); - Media_Block_Obj parse_media_block(); - List_Obj parse_media_queries(); - Media_Query_Obj parse_media_query(); - Media_Query_Expression_Obj parse_media_expression(); - Supports_Block_Obj parse_supports_directive(); - Supports_Condition_Obj parse_supports_condition(); - Supports_Condition_Obj parse_supports_negation(); - Supports_Condition_Obj parse_supports_operator(); - Supports_Condition_Obj parse_supports_interpolation(); - Supports_Condition_Obj parse_supports_declaration(); - Supports_Condition_Obj parse_supports_condition_in_parens(); - At_Root_Block_Obj parse_at_root_block(); - At_Root_Query_Obj parse_at_root_query(); - String_Schema_Obj parse_almost_any_value(); - Directive_Obj parse_special_directive(); - Directive_Obj parse_prefixed_directive(); - Directive_Obj parse_directive(); - Warning_Obj parse_warning(); - Error_Obj parse_error(); - Debug_Obj parse_debug(); - - Value_Ptr color_or_string(const std::string& lexed) const; - - // be more like ruby sass - Expression_Obj lex_almost_any_value_token(); - Expression_Obj lex_almost_any_value_chars(); - Expression_Obj lex_interp_string(); - Expression_Obj lex_interp_uri(); - Expression_Obj lex_interpolation(); - - // these will throw errors - Token lex_variable(); - Token lex_identifier(); - - void parse_block_comments(); - - Lookahead lookahead_for_value(const char* start = 0); - Lookahead lookahead_for_selector(const char* start = 0); - Lookahead lookahead_for_include(const char* start = 0); - - Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); - Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); - - void throw_syntax_error(std::string message, size_t ln = 0); - void throw_read_error(std::string message, size_t ln = 0); - - - template - Expression_Obj lex_interp() - { - if (lex < open >(false)) { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - Expression_Obj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - while (lex < close >(false)) { - // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - Expression_Obj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - } else { - return schema; - } - } - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - return 0; - } - - public: - static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); - static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); - static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); - static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); - private: - Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; - Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; - Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; - Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; - - static const char* re_attr_sensitive_close(const char* src); - static const char* re_attr_insensitive_close(const char* src); - - }; - - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); -} - -#endif diff --git a/src/libsass/src/paths.hpp b/src/libsass/src/paths.hpp deleted file mode 100644 index aabab94ae..000000000 --- a/src/libsass/src/paths.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef SASS_PATHS_H -#define SASS_PATHS_H - -#include -#include -#include - - -template -std::string vector_to_string(std::vector v) -{ - std::stringstream buffer; - buffer << "["; - - if (!v.empty()) - { buffer << v[0]; } - else - { buffer << "]"; } - - if (v.size() == 1) - { buffer << "]"; } - else - { - for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; - buffer << "]"; - } - - return buffer.str(); -} - -namespace Sass { - - - template - std::vector > paths(std::vector > strata, size_t from_end = 0) - { - if (strata.empty()) { - return std::vector >(); - } - - size_t end = strata.size() - from_end; - if (end <= 1) { - std::vector > starting_points; - starting_points.reserve(strata[0].size()); - for (size_t i = 0, S = strata[0].size(); i < S; ++i) { - std::vector starting_point; - starting_point.push_back(strata[0][i]); - starting_points.push_back(starting_point); - } - return starting_points; - } - - std::vector > up_to_here = paths(strata, from_end + 1); - std::vector here = strata[end-1]; - - std::vector > branches; - branches.reserve(up_to_here.size() * here.size()); - for (size_t i = 0, S1 = up_to_here.size(); i < S1; ++i) { - for (size_t j = 0, S2 = here.size(); j < S2; ++j) { - std::vector branch = up_to_here[i]; - branch.push_back(here[j]); - branches.push_back(branch); - } - } - - return branches; - } - -} - -#endif diff --git a/src/libsass/src/plugins.cpp b/src/libsass/src/plugins.cpp deleted file mode 100644 index eecba7880..000000000 --- a/src/libsass/src/plugins.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "sass.hpp" -#include -#include "output.hpp" -#include "plugins.hpp" - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#include -#endif - -namespace Sass { - - Plugins::Plugins(void) { } - Plugins::~Plugins(void) - { - for (auto function : functions) { - sass_delete_function(function); - } - for (auto importer : importers) { - sass_delete_importer(importer); - } - for (auto header : headers) { - sass_delete_importer(header); - } - } - - // check if plugin is compatible with this version - // plugins may be linked static against libsass - // we try to be compatible between major versions - inline bool compatibility(const char* their_version) - { -// const char* their_version = "3.1.2"; - // first check if anyone has an unknown version - const char* our_version = libsass_version(); - if (!strcmp(their_version, "[na]")) return false; - if (!strcmp(our_version, "[na]")) return false; - - // find the position of the second dot - size_t pos = std::string(our_version).find('.', 0); - if (pos != std::string::npos) pos = std::string(our_version).find('.', pos + 1); - - // if we do not have two dots we fallback to compare complete string - if (pos == std::string::npos) { return strcmp(their_version, our_version) ? 0 : 1; } - // otherwise only compare up to the second dot (major versions) - else { return strncmp(their_version, our_version, pos) ? 0 : 1; } - - } - - // load one specific plugin - bool Plugins::load_plugin (const std::string& path) - { - - typedef const char* (*__plugin_version__)(void); - typedef Sass_Function_List (*__plugin_load_fns__)(void); - typedef Sass_Importer_List (*__plugin_load_imps__)(void); - - if (LOAD_LIB(plugin, path)) - { - // try to load initial function to query libsass version suppor - if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version")) - { - // get the libsass version of the plugin - if (!compatibility(plugin_version())) return false; - // try to get import address for "libsass_load_functions" - if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) - { - Sass_Function_List fns = plugin_load_functions(), _p = fns; - while (fns && *fns) { functions.push_back(*fns); ++ fns; } - sass_free_memory(_p); // only delete the container, items not yet - } - // try to get import address for "libsass_load_importers" - if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers")) - { - Sass_Importer_List imps = plugin_load_importers(), _p = imps; - while (imps && *imps) { importers.push_back(*imps); ++ imps; } - sass_free_memory(_p); // only delete the container, items not yet - } - // try to get import address for "libsass_load_headers" - if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers")) - { - Sass_Importer_List imps = plugin_load_headers(), _p = imps; - while (imps && *imps) { headers.push_back(*imps); ++ imps; } - sass_free_memory(_p); // only delete the container, items not yet - } - // success - return true; - } - else - { - // print debug message to stderr (should not happen) - std::cerr << "failed loading 'libsass_support' in <" << path << ">" << std::endl; - if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << std::endl; - CLOSE_LIB(plugin); - } - } - else - { - // print debug message to stderr (should not happen) - std::cerr << "failed loading plugin <" << path << ">" << std::endl; - if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << std::endl; - } - - return false; - - } - - size_t Plugins::load_plugins(const std::string& path) - { - - // count plugins - size_t loaded = 0; - - #ifdef _WIN32 - - try - { - - // use wchar (utf16) - WIN32_FIND_DATAW data; - // trailing slash is guaranteed - std::string globsrch(path + "*.dll"); - // convert to wide chars (utf16) for system call - std::wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); - HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); - // check if system called returned a result - // ToDo: maybe we should print a debug message - if (hFile == INVALID_HANDLE_VALUE) return -1; - - // read directory - while (true) - { - try - { - // the system will report the filenames with wide chars (utf16) - std::string entry = UTF_8::convert_from_utf16(data.cFileName); - // check if file ending matches exactly - if (!ends_with(entry, ".dll")) continue; - // load the plugin and increase counter - if (load_plugin(path + entry)) ++ loaded; - // check if there should be more entries - if (GetLastError() == ERROR_NO_MORE_FILES) break; - // load next entry (check for return type) - if (!FindNextFileW(hFile, &data)) break; - } - catch (...) - { - // report the error to the console (should not happen) - // seems like we got strange data from the system call? - std::cerr << "filename in plugin path has invalid utf8?" << std::endl; - } - } - } - catch (utf8::invalid_utf8) - { - // report the error to the console (should not happen) - // implementors should make sure to provide valid utf8 - std::cerr << "plugin path contains invalid utf8" << std::endl; - } - - #else - - DIR *dp; - struct dirent *dirp; - if((dp = opendir(path.c_str())) == NULL) return -1; - while ((dirp = readdir(dp)) != NULL) { - #if __APPLE__ - if (!ends_with(dirp->d_name, ".dylib")) continue; - #else - if (!ends_with(dirp->d_name, ".so")) continue; - #endif - if (load_plugin(path + dirp->d_name)) ++ loaded; - } - closedir(dp); - - #endif - return loaded; - - } - -} diff --git a/src/libsass/src/plugins.hpp b/src/libsass/src/plugins.hpp deleted file mode 100644 index fe4eed010..000000000 --- a/src/libsass/src/plugins.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef SASS_PLUGINS_H -#define SASS_PLUGINS_H - -#include -#include -#include "utf8_string.hpp" -#include "sass/functions.h" - -#ifdef _WIN32 - - #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str()) - #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str()) - #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name) - #define CLOSE_LIB(var) FreeLibrary(var) - - #ifndef dlerror - #define dlerror() 0 - #endif - -#else - - #define LOAD_LIB(var, path) void* var = dlopen(path.c_str(), RTLD_LAZY) - #define LOAD_LIB_FN(type, var, name) type var = (type) dlsym(plugin, name) - #define CLOSE_LIB(var) dlclose(var) - -#endif - -namespace Sass { - - - class Plugins { - - public: // c-tor - Plugins(void); - ~Plugins(void); - - public: // methods - // load one specific plugin - bool load_plugin(const std::string& path); - // load all plugins from a directory - size_t load_plugins(const std::string& path); - - public: // public accessors - const std::vector get_headers(void) { return headers; } - const std::vector get_importers(void) { return importers; } - const std::vector get_functions(void) { return functions; } - - private: // private vars - std::vector headers; - std::vector importers; - std::vector functions; - - }; - -} - -#endif diff --git a/src/libsass/src/position.cpp b/src/libsass/src/position.cpp deleted file mode 100644 index 312e04ca6..000000000 --- a/src/libsass/src/position.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "sass.hpp" -#include "position.hpp" - -namespace Sass { - - - Offset::Offset(const char chr) - : line(chr == '\n' ? 1 : 0), - column(chr == '\n' ? 0 : 1) - {} - - Offset::Offset(const char* string) - : line(0), column(0) - { - *this = inc(string, string + strlen(string)); - } - - Offset::Offset(const std::string& text) - : line(0), column(0) - { - *this = inc(text.c_str(), text.c_str() + text.size()); - } - - Offset::Offset(const size_t line, const size_t column) - : line(line), column(column) { } - - // init/create instance from const char substring - Offset Offset::init(const char* beg, const char* end) - { - Offset offset(0, 0); - if (end == 0) { - end += strlen(beg); - } - offset.add(beg, end); - return offset; - } - - // increase offset by given string (mostly called by lexer) - // increase line counter and count columns on the last line - Offset Offset::add(const char* begin, const char* end) - { - if (end == 0) return *this; - while (begin < end && *begin) { - if (*begin == '\n') { - ++ line; - // start new line - column = 0; - } else { - // do not count any utf8 continuation bytes - // https://stackoverflow.com/a/9356203/1550314 - // https://en.wikipedia.org/wiki/UTF-8#Description - unsigned char chr = *begin; - // skip over 10xxxxxx - // is 1st bit not set - if ((chr & 128) == 0) { - // regular ascii char - column += 1; - } - // is 2nd bit not set - else if ((chr & 64) == 0) { - // first utf8 byte - column += 1; - } - } - ++ begin; - } - return *this; - } - - // increase offset by given string (mostly called by lexer) - // increase line counter and count columns on the last line - Offset Offset::inc(const char* begin, const char* end) const - { - Offset offset(line, column); - offset.add(begin, end); - return offset; - } - - bool Offset::operator== (const Offset &pos) const - { - return line == pos.line && column == pos.column; - } - - bool Offset::operator!= (const Offset &pos) const - { - return line != pos.line || column != pos.column; - } - - void Offset::operator+= (const Offset &off) - { - *this = Offset(line + off.line, off.line > 0 ? off.column : column + off.column); - } - - Offset Offset::operator+ (const Offset &off) const - { - return Offset(line + off.line, off.line > 0 ? off.column : column + off.column); - } - - Offset Offset::operator- (const Offset &off) const - { - return Offset(line - off.line, off.line == line ? column - off.column : column); - } - - Position::Position(const size_t file) - : Offset(0, 0), file(file) { } - - Position::Position(const size_t file, const Offset& offset) - : Offset(offset), file(file) { } - - Position::Position(const size_t line, const size_t column) - : Offset(line, column), file(-1) { } - - Position::Position(const size_t file, const size_t line, const size_t column) - : Offset(line, column), file(file) { } - - - ParserState::ParserState(const char* path, const char* src, const size_t file) - : Position(file, 0, 0), path(path), src(src), offset(0, 0), token() { } - - ParserState::ParserState(const char* path, const char* src, const Position& position, Offset offset) - : Position(position), path(path), src(src), offset(offset), token() { } - - ParserState::ParserState(const char* path, const char* src, const Token& token, const Position& position, Offset offset) - : Position(position), path(path), src(src), offset(offset), token(token) { } - - Position Position::add(const char* begin, const char* end) - { - Offset::add(begin, end); - return *this; - } - - Position Position::inc(const char* begin, const char* end) const - { - Offset offset(line, column); - offset = offset.inc(begin, end); - return Position(file, offset); - } - - bool Position::operator== (const Position &pos) const - { - return file == pos.file && line == pos.line && column == pos.column; - } - - bool Position::operator!= (const Position &pos) const - { - return file == pos.file || line != pos.line || column != pos.column; - } - - void Position::operator+= (const Offset &off) - { - *this = Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); - } - - const Position Position::operator+ (const Offset &off) const - { - return Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); - } - - const Offset Position::operator- (const Offset &off) const - { - return Offset(line - off.line, off.line == line ? column - off.column : column); - } - - /* not used anymore - remove? - std::ostream& operator<<(std::ostream& strm, const Offset& off) - { - if (off.line == string::npos) strm << "-1:"; else strm << off.line << ":"; - if (off.column == string::npos) strm << "-1"; else strm << off.column; - return strm; - } */ - - /* not used anymore - remove? - std::ostream& operator<<(std::ostream& strm, const Position& pos) - { - if (pos.file != string::npos) strm << pos.file << ":"; - if (pos.line == string::npos) strm << "-1:"; else strm << pos.line << ":"; - if (pos.column == string::npos) strm << "-1"; else strm << pos.column; - return strm; - } */ - -} diff --git a/src/libsass/src/position.hpp b/src/libsass/src/position.hpp deleted file mode 100644 index 923be3c59..000000000 --- a/src/libsass/src/position.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef SASS_POSITION_H -#define SASS_POSITION_H - -#include -#include -// #include - -namespace Sass { - - - class Offset { - - public: // c-tor - Offset(const char chr); - Offset(const char* string); - Offset(const std::string& text); - Offset(const size_t line, const size_t column); - - // return new position, incremented by the given string - Offset add(const char* begin, const char* end); - Offset inc(const char* begin, const char* end) const; - - // init/create instance from const char substring - static Offset init(const char* beg, const char* end); - - public: // overload operators for position - void operator+= (const Offset &pos); - bool operator== (const Offset &pos) const; - bool operator!= (const Offset &pos) const; - Offset operator+ (const Offset &off) const; - Offset operator- (const Offset &off) const; - - public: // overload output stream operator - // friend std::ostream& operator<<(std::ostream& strm, const Offset& off); - - public: - Offset off() { return *this; } - - public: - size_t line; - size_t column; - - }; - - class Position : public Offset { - - public: // c-tor - Position(const size_t file); // line(0), column(0) - Position(const size_t file, const Offset& offset); - Position(const size_t line, const size_t column); // file(-1) - Position(const size_t file, const size_t line, const size_t column); - - public: // overload operators for position - void operator+= (const Offset &off); - bool operator== (const Position &pos) const; - bool operator!= (const Position &pos) const; - const Position operator+ (const Offset &off) const; - const Offset operator- (const Offset &off) const; - // return new position, incremented by the given string - Position add(const char* begin, const char* end); - Position inc(const char* begin, const char* end) const; - - public: // overload output stream operator - // friend std::ostream& operator<<(std::ostream& strm, const Position& pos); - - public: - size_t file; - - }; - - // Token type for representing lexed chunks of text - class Token { - public: - const char* prefix; - const char* begin; - const char* end; - - Token() - : prefix(0), begin(0), end(0) { } - Token(const char* b, const char* e) - : prefix(b), begin(b), end(e) { } - Token(const char* str) - : prefix(str), begin(str), end(str + strlen(str)) { } - Token(const char* p, const char* b, const char* e) - : prefix(p), begin(b), end(e) { } - - size_t length() const { return end - begin; } - std::string ws_before() const { return std::string(prefix, begin); } - const std::string to_string() const { return std::string(begin, end); } - std::string time_wspace() const { - std::string str(to_string()); - std::string whitespaces(" \t\f\v\n\r"); - return str.erase(str.find_last_not_of(whitespaces)+1); - } - - operator bool() { return begin && end && begin >= end; } - operator std::string() { return to_string(); } - - bool operator==(Token t) { return to_string() == t.to_string(); } - }; - - class ParserState : public Position { - - public: // c-tor - ParserState(const char* path, const char* src = 0, const size_t file = std::string::npos); - ParserState(const char* path, const char* src, const Position& position, Offset offset = Offset(0, 0)); - ParserState(const char* path, const char* src, const Token& token, const Position& position, Offset offset = Offset(0, 0)); - - public: // down casts - Offset off() { return *this; } - Position pos() { return *this; } - ParserState pstate() { return *this; } - - public: - const char* path; - const char* src; - Offset offset; - Token token; - - }; - -} - -#endif diff --git a/src/libsass/src/prelexer.cpp b/src/libsass/src/prelexer.cpp deleted file mode 100644 index a43b1ee3c..000000000 --- a/src/libsass/src/prelexer.cpp +++ /dev/null @@ -1,1774 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include "util.hpp" -#include "position.hpp" -#include "prelexer.hpp" -#include "constants.hpp" - - -namespace Sass { - // using namespace Lexer; - using namespace Constants; - - namespace Prelexer { - - - /* - - def string_re(open, close) - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - end - end - - # A hash of regular expressions that are used for tokenizing strings. - # - # The key is a `[Symbol, Boolean]` pair. - # The symbol represents which style of quotation to use, - # while the boolean represents whether or not the string - # is following an interpolated segment. - STRING_REGULAR_EXPRESSIONS = { - :double => { - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - false => string_re('"', '"'), - true => string_re('', '"') - }, - :single => { - false => string_re("'", "'"), - true => string_re('', "'") - }, - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a - # non-standard version of http://www.w3.org/TR/css3-conditional/ - :url_prefix => { - false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - :domain => { - false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - } - } - */ - - /* - /#{open} - ( - \\. - | - \# (?!\{) - | - [^#{close}\\#] - )* - (#{close}|#\{) - /m - false => string_re('"', '"'), - true => string_re('', '"') - */ - extern const char string_double_negates[] = "\"\\#"; - const char* re_string_double_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_double_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'"'>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - extern const char string_single_negates[] = "'\\#"; - const char* re_string_single_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_single_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'\''>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - /* - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - */ - const char* re_string_uri_close(const char* src) - { - return sequence < - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < optional < W >, exactly <')'> >, - lookahead < exactly< hash_lbrace > > - > - >, - optional < - sequence < optional < W >, exactly <')'> > - > - >(src); - } - - const char* re_string_uri_open(const char* src) - { - return sequence < - exactly <'u'>, - exactly <'r'>, - exactly <'l'>, - exactly <'('>, - W, - alternatives< - quoted_string, - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < W, exactly <')'> >, - exactly< hash_lbrace > - > - > - > - >(src); - } - - // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. - const char* line_comment(const char* src) - { - return sequence< - exactly < - slash_slash - >, - non_greedy< - any_char, - end_of_line - > - >(src); - } - - // Match a block comment. - const char* block_comment(const char* src) - { - return sequence< - delimited_by< - slash_star, - star_slash, - false - > - >(src); - } - /* not use anymore - remove? - const char* block_comment_prefix(const char* src) { - return exactly(src); - } - // Match either comment. - const char* comment(const char* src) { - return line_comment(src); - } - */ - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_whitespace(const char* src) { - return one_plus< alternatives >(src); - } - // Match optional_css_whitepace plus block_comments - const char* optional_css_comments(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_comments(const char* src) { - return one_plus< alternatives >(src); - } - - // Match one backslash escaped char /\\./ - const char* escape_seq(const char* src) - { - return sequence< - exactly<'\\'>, - alternatives < - minmax_range< - 1, 3, xdigit - >, - any_char - >, - optional < - exactly <' '> - > - >(src); - } - - // Match identifier start - const char* identifier_alpha(const char* src) - { - return alternatives< - unicode_seq, - alpha, - unicode, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match identifier after start - const char* identifier_alnum(const char* src) - { - return alternatives< - unicode_seq, - alnum, - unicode, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match CSS identifiers. - const char* strict_identifier(const char* src) - { - return sequence< - one_plus < strict_identifier_alpha >, - zero_plus < strict_identifier_alnum > - // word_boundary not needed - >(src); - } - - // Match CSS identifiers. - const char* identifier(const char* src) - { - return sequence< - zero_plus< exactly<'-'> >, - one_plus < identifier_alpha >, - zero_plus < identifier_alnum > - // word_boundary not needed - >(src); - } - - const char* strict_identifier_alpha(const char* src) - { - return alternatives < - alpha, - unicode, - escape_seq, - exactly<'_'> - >(src); - } - - const char* strict_identifier_alnum(const char* src) - { - return alternatives < - alnum, - unicode, - escape_seq, - exactly<'_'> - >(src); - } - - // Match a single CSS unit - const char* one_unit(const char* src) - { - return sequence < - optional < exactly <'-'> >, - strict_identifier_alpha, - zero_plus < alternatives< - strict_identifier_alnum, - sequence < - one_plus < exactly<'-'> >, - strict_identifier_alpha - > - > > - >(src); - } - - // Match numerator/denominator CSS units - const char* multiple_units(const char* src) - { - return - sequence < - one_unit, - zero_plus < - sequence < - exactly <'*'>, - one_unit - > - > - >(src); - } - - // Match complex CSS unit identifiers - const char* unit_identifier(const char* src) - { - return sequence < - multiple_units, - optional < - sequence < - exactly <'/'>, - negate < sequence < - exactly < calc_fn_kwd >, - exactly < '(' > - > >, - multiple_units - > > - >(src); - } - - const char* identifier_alnums(const char* src) - { - return one_plus< identifier_alnum >(src); - } - - // Match number prefix ([\+\-]+) - const char* number_prefix(const char* src) { - return alternatives < - exactly < '+' >, - sequence < - exactly < '-' >, - optional_css_whitespace, - exactly< '-' > - > - >(src); - } - - // Match interpolant schemas - const char* identifier_schema(const char* src) { - - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - sequence < - optional < - exactly <'$'> - >, - identifier - >, - exactly <'-'> - > - >, - interpolant, - zero_plus < - alternatives < - digits, - sequence < - optional < - exactly <'$'> - >, - identifier - >, - quoted_string, - exactly<'-'> - > - > - > - >, - negate < - exactly<'%'> - > - > (src); - } - - // interpolants can be recursive/nested - const char* interpolant(const char* src) { - return recursive_scopes< exactly, exactly >(src); - } - - // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ - const char* single_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'\''>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '\'' > - > - >, - exactly <'\''> - >(src); - } - - // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ - const char* double_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'"'>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '"' > - > - >, - exactly <'"'> - >(src); - } - - // $re_quoted = /(?:$re_squote|$re_dquote)/ - const char* quoted_string(const char* src) { - // match a quoted string, while skipping interpolants - return alternatives< - single_quoted_string, - double_quoted_string - >(src); - } - - const char* sass_value(const char* src) { - return alternatives < - quoted_string, - identifier, - percentage, - hex, - dimension, - number - >(src); - } - - // this is basically `one_plus < sass_value >` - // takes care to not parse invalid combinations - const char* value_combinations(const char* src) { - // `2px-2px` is invalid combo - bool was_number = false; - const char* pos; - while (src) { - if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { - was_number = false; - src = pos; - } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { - was_number = true; - src = pos; - } else { - break; - } - } - return src; - } - - // must be at least one interpolant - // can be surrounded by sass values - // make sure to never parse (dim)(dim) - // since this wrongly consumes `2px-1px` - // `2px1px` is valid number (unit `px1px`) - const char* value_schema(const char* src) - { - return sequence < - one_plus < - sequence < - optional < value_combinations >, - interpolant, - optional < value_combinations > - > - > - >(src); - } - - // Match CSS '@' keywords. - const char* at_keyword(const char* src) { - return sequence, identifier>(src); - } - - /* - tok(%r{ - ( - \\. - | - (?!url\() - [^"'/\#!;\{\}] # " - | - /(?![\*\/]) - | - \#(?!\{) - | - !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. - )+ - }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || - interpolation(:warn_for_color) - */ - const char* re_almost_any_value_token(const char* src) { - - return alternatives < - one_plus < - alternatives < - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - uri_prefix - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - >, - block_comment, - line_comment, - interpolant, - space, - sequence < - exactly<'u'>, - exactly<'r'>, - exactly<'l'>, - exactly<'('>, - zero_plus < - alternatives < - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - > - >, - // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - exactly<')'> - > - >(src); - } - - /* - DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, - :each, :while, :if, :else, :extend, :import, :media, :charset, :content, - :_moz_document, :at_root, :error] - */ - const char* re_special_directive(const char* src) { - return alternatives < - word < mixin_kwd >, - word < include_kwd >, - word < function_kwd >, - word < return_kwd >, - word < debug_kwd >, - word < warn_kwd >, - word < for_kwd >, - word < each_kwd >, - word < while_kwd >, - word < if_kwd >, - word < else_kwd >, - word < extend_kwd >, - word < import_kwd >, - word < media_kwd >, - word < charset_kwd >, - word < content_kwd >, - // exactly < moz_document_kwd >, - word < at_root_kwd >, - word < error_kwd > - >(src); - } - - const char* re_prefixed_directive(const char* src) { - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < alnum >, - exactly <'-'> - > - >, - exactly < supports_kwd > - >(src); - } - - const char* re_reference_combinator(const char* src) { - return sequence < - optional < - sequence < - zero_plus < - exactly <'-'> - >, - identifier, - exactly <'|'> - > - >, - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* static_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - re_reference_combinator, - exactly <'/'> - >(src); - } - - const char* schema_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - optional < - sequence < - css_ip_identifier, - exactly <'|'> - > - >, - css_ip_identifier, - exactly <'/'> - > (src); - } - - const char* kwd_import(const char* src) { - return word(src); - } - - const char* kwd_at_root(const char* src) { - return word(src); - } - - const char* kwd_with_directive(const char* src) { - return word(src); - } - - const char* kwd_without_directive(const char* src) { - return word(src); - } - - const char* kwd_media(const char* src) { - return word(src); - } - - const char* kwd_supports_directive(const char* src) { - return word(src); - } - - const char* kwd_mixin(const char* src) { - return word(src); - } - - const char* kwd_function(const char* src) { - return word(src); - } - - const char* kwd_return_directive(const char* src) { - return word(src); - } - - const char* kwd_include_directive(const char* src) { - return word(src); - } - - const char* kwd_content_directive(const char* src) { - return word(src); - } - - const char* kwd_charset_directive(const char* src) { - return word(src); - } - - const char* kwd_extend(const char* src) { - return word(src); - } - - - const char* kwd_if_directive(const char* src) { - return word(src); - } - - const char* kwd_else_directive(const char* src) { - return word(src); - } - const char* elseif_directive(const char* src) { - return sequence< exactly< else_kwd >, - optional_css_comments, - word< if_after_else_kwd > >(src); - } - - const char* kwd_for_directive(const char* src) { - return word(src); - } - - const char* kwd_from(const char* src) { - return word(src); - } - - const char* kwd_to(const char* src) { - return word(src); - } - - const char* kwd_through(const char* src) { - return word(src); - } - - const char* kwd_each_directive(const char* src) { - return word(src); - } - - const char* kwd_in(const char* src) { - return word(src); - } - - const char* kwd_while_directive(const char* src) { - return word(src); - } - - const char* name(const char* src) { - return one_plus< alternatives< alnum, - exactly<'-'>, - exactly<'_'>, - escape_seq > >(src); - } - - const char* kwd_warn(const char* src) { - return word(src); - } - - const char* kwd_err(const char* src) { - return word(src); - } - - const char* kwd_dbg(const char* src) { - return word(src); - } - - /* not used anymore - remove? - const char* directive(const char* src) { - return sequence< exactly<'@'>, identifier >(src); - } */ - - const char* kwd_null(const char* src) { - return word(src); - } - - const char* css_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* css_ip_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - alternatives < - identifier, - interpolant - > - >(src); - } - - // Match CSS type selectors - const char* namespace_prefix(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_identifier - > - >, - exactly <'|'>, - negate < - exactly <'='> - > - >(src); - } - - // Match CSS type selectors - const char* namespace_schema(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_ip_identifier - > - >, - exactly<'|'>, - negate < - exactly <'='> - > - >(src); - } - - const char* hyphens_and_identifier(const char* src) { - return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); - } - const char* hyphens_and_name(const char* src) { - return sequence< zero_plus< exactly< '-' > >, name >(src); - } - const char* universal(const char* src) { - return sequence< optional, exactly<'*'> >(src); - } - // Match CSS id names. - const char* id_name(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS class names. - const char* class_name(const char* src) { - return sequence, identifier >(src); - } - // Attribute name in an attribute selector. - const char* attribute_name(const char* src) { - return alternatives< sequence< optional, identifier>, - identifier >(src); - } - // match placeholder selectors - const char* placeholder(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS numeric constants. - - const char* op(const char* src) { - return class_char(src); - } - const char* sign(const char* src) { - return class_char(src); - } - const char* unsigned_number(const char* src) { - return alternatives, - exactly<'.'>, - one_plus >, - digits>(src); - } - const char* number(const char* src) { - return sequence< - optional, - unsigned_number, - optional< - sequence< - exactly<'e'>, - optional, - unsigned_number - > - > - >(src); - } - const char* coefficient(const char* src) { - return alternatives< sequence< optional, digits >, - sign >(src); - } - const char* binomial(const char* src) { - return sequence < - optional < sign >, - optional < digits >, - exactly <'n'>, - zero_plus < sequence < - optional_css_whitespace, sign, - optional_css_whitespace, digits - > > - >(src); - } - const char* percentage(const char* src) { - return sequence< number, exactly<'%'> >(src); - } - const char* ampersand(const char* src) { - return exactly<'&'>(src); - } - - /* not used anymore - remove? - const char* em(const char* src) { - return sequence< number, exactly >(src); - } */ - const char* dimension(const char* src) { - return sequence(src); - } - const char* hex(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 4 && len != 7) ? 0 : p; - } - const char* hexa(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 9) ? 0 : p; - } - const char* hex0(const char* src) { - const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 8) ? 0 : p; - } - - /* no longer used - remove? - const char* rgb_prefix(const char* src) { - return word(src); - }*/ - // Match CSS uri specifiers. - - const char* uri_prefix(const char* src) { - return sequence < - exactly < - url_kwd - >, - zero_plus < - sequence < - exactly <'-'>, - one_plus < - alpha - > - > - >, - exactly <'('> - >(src); - } - - // TODO: rename the following two functions - /* no longer used - remove? - const char* uri(const char* src) { - return sequence< exactly, - optional, - quoted_string, - optional, - exactly<')'> >(src); - }*/ - /* no longer used - remove? - const char* url_value(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename - optional< exactly<'/'> > >(src); - }*/ - /* no longer used - remove? - const char* url_schema(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - filename_schema >(src); // optional trailing slash - }*/ - // Match CSS "!important" keyword. - const char* kwd_important(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!default" keyword. - const char* default_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!global" keyword. - const char* global_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS pseudo-class/element prefixes. - const char* pseudo_prefix(const char* src) { - return sequence< exactly<':'>, optional< exactly<':'> > >(src); - } - // Match CSS function call openers. - const char* functional_schema(const char* src) { - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - identifier, - exactly <'-'> - > - >, - one_plus < - sequence < - interpolant, - alternatives < - digits, - identifier, - exactly<'+'>, - exactly<'-'> - > - > - > - > - >, - negate < - exactly <'%'> - >, - lookahead < - exactly <'('> - > - > (src); - } - - const char* re_nothing(const char* src) { - return src; - } - - const char* re_functional(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - const char* re_pseudo_selector(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - // Match the CSS negation pseudo-class. - const char* pseudo_not(const char* src) { - return word< pseudo_not_fn_kwd >(src); - } - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src) { - return word(src); - } - const char* odd(const char* src) { - return word(src); - } - // Match CSS attribute-matching operators. - const char* exact_match(const char* src) { return exactly<'='>(src); } - const char* class_match(const char* src) { return exactly(src); } - const char* dash_match(const char* src) { return exactly(src); } - const char* prefix_match(const char* src) { return exactly(src); } - const char* suffix_match(const char* src) { return exactly(src); } - const char* substring_match(const char* src) { return exactly(src); } - // Match CSS combinators. - /* not used anymore - remove? - const char* adjacent_to(const char* src) { - return sequence< optional_spaces, exactly<'+'> >(src); - } - const char* precedes(const char* src) { - return sequence< optional_spaces, exactly<'~'> >(src); - } - const char* parent_of(const char* src) { - return sequence< optional_spaces, exactly<'>'> >(src); - } - const char* ancestor_of(const char* src) { - return sequence< spaces, negate< exactly<'{'> > >(src); - }*/ - - // Match SCSS variable names. - const char* variable(const char* src) { - return sequence, identifier>(src); - } - - // parse `calc`, `-a-calc` and `--b-c-calc` - // but do not parse `foocalc` or `foo-calc` - const char* calc_fn_call(const char* src) { - return sequence < - optional < sequence < - hyphens, - one_plus < sequence < - strict_identifier, - hyphens - > > - > >, - exactly < calc_fn_kwd >, - word_boundary - >(src); - } - - // Match Sass boolean keywords. - const char* kwd_true(const char* src) { - return word(src); - } - const char* kwd_false(const char* src) { - return word(src); - } - const char* kwd_only(const char* src) { - return keyword < only_kwd >(src); - } - const char* kwd_and(const char* src) { - return keyword < and_kwd >(src); - } - const char* kwd_or(const char* src) { - return keyword < or_kwd >(src); - } - const char* kwd_not(const char* src) { - return keyword < not_kwd >(src); - } - const char* kwd_eq(const char* src) { - return exactly(src); - } - const char* kwd_neq(const char* src) { - return exactly(src); - } - const char* kwd_gt(const char* src) { - return exactly(src); - } - const char* kwd_gte(const char* src) { - return exactly(src); - } - const char* kwd_lt(const char* src) { - return exactly(src); - } - const char* kwd_lte(const char* src) { - return exactly(src); - } - - // match specific IE syntax - const char* ie_progid(const char* src) { - return sequence < - word, - exactly<':'>, - alternatives< identifier_schema, identifier >, - zero_plus< sequence< - exactly<'.'>, - alternatives< identifier_schema, identifier > - > >, - zero_plus < sequence< - exactly<'('>, - optional_css_whitespace, - optional < sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, - zero_plus< sequence< - optional_css_whitespace, - exactly<','>, - optional_css_whitespace, - sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > - > - > > - > >, - optional_css_whitespace, - exactly<')'> - > > - >(src); - } - const char* ie_expression(const char* src) { - return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); - } - const char* ie_property(const char* src) { - return alternatives < ie_expression, ie_progid >(src); - } - - // const char* ie_args(const char* src) { - // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, - // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); - // } - - const char* ie_keyword_arg_property(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier - >(src); - } - const char* ie_keyword_arg_value(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier, - quoted_string, - number, - hex, - hexa, - sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - > - >(src); - } - - const char* ie_keyword_arg(const char* src) { - return sequence < - ie_keyword_arg_property, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - ie_keyword_arg_value - >(src); - } - - // Path matching functions. - /* not used anymore - remove? - const char* folder(const char* src) { - return sequence< zero_plus< any_char_except<'/'> >, - exactly<'/'> >(src); - } - const char* folders(const char* src) { - return zero_plus< folder >(src); - }*/ - /* not used anymore - remove? - const char* chunk(const char* src) { - char inside_str = 0; - const char* p = src; - size_t depth = 0; - while (true) { - if (!*p) { - return 0; - } - else if (!inside_str && (*p == '"' || *p == '\'')) { - inside_str = *p; - } - else if (*p == inside_str && *(p-1) != '\\') { - inside_str = 0; - } - else if (*p == '(' && !inside_str) { - ++depth; - } - else if (*p == ')' && !inside_str) { - if (depth == 0) return p; - else --depth; - } - ++p; - } - // unreachable - return 0; - } - */ - - // follow the CSS spec more closely and see if this helps us scan URLs correctly - /* not used anymore - remove? - const char* NL(const char* src) { - return alternatives< exactly<'\n'>, - sequence< exactly<'\r'>, exactly<'\n'> >, - exactly<'\r'>, - exactly<'\f'> >(src); - }*/ - - const char* H(const char* src) { - return std::isxdigit(*src) ? src+1 : 0; - } - - const char* W(const char* src) { - return zero_plus< alternatives< - space, - exactly< '\t' >, - exactly< '\r' >, - exactly< '\n' >, - exactly< '\f' > - > >(src); - } - - const char* UUNICODE(const char* src) { - return sequence< exactly<'\\'>, - between, - optional< W > - >(src); - } - - const char* NONASCII(const char* src) { - return nonascii(src); - } - - const char* ESCAPE(const char* src) { - return alternatives< - UUNICODE, - sequence< - exactly<'\\'>, - alternatives< - NONASCII, - escapable_character - > - > - >(src); - } - - const char* list_terminator(const char* src) { - return alternatives < - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<']'>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - >(src); - }; - - const char* space_list_terminator(const char* src) { - return alternatives < - exactly<','>, - list_terminator - >(src); - }; - - - // const char* real_uri_prefix(const char* src) { - // return alternatives< - // exactly< url_kwd >, - // exactly< url_prefix_kwd > - // >(src); - // } - - const char* real_uri(const char* src) { - return sequence< - exactly< url_kwd >, - exactly< '(' >, - W, - real_uri_value, - exactly< ')' > - >(src); - } - - const char* real_uri_suffix(const char* src) { - return sequence< W, exactly< ')' > >(src); - } - - const char* real_uri_value(const char* src) { - return - sequence< - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - real_uri_suffix, - exactly< hash_lbrace > - > - > - > - (src); - } - - const char* static_string(const char* src) { - const char* pos = src; - const char * s = quoted_string(pos); - Token t(pos, s); - const unsigned int p = count_interval< interpolant >(t.begin, t.end); - return (p == 0) ? t.end : 0; - } - - const char* unicode_seq(const char* src) { - return sequence < - alternatives < - exactly< 'U' >, - exactly< 'u' > - >, - exactly< '+' >, - padded_token < - 6, xdigit, - exactly < '?' > - > - >(src); - } - - const char* static_component(const char* src) { - return alternatives< identifier, - static_string, - percentage, - hex, - hexa, - exactly<'|'>, - // exactly<'+'>, - sequence < number, unit_identifier >, - number, - sequence< exactly<'!'>, word > - >(src); - } - - const char* static_property(const char* src) { - return - sequence < - zero_plus< - sequence < - optional_css_comments, - alternatives < - exactly<','>, - exactly<'('>, - exactly<')'>, - kwd_optional, - quoted_string, - interpolant, - identifier, - percentage, - dimension, - variable, - alnum, - sequence < - exactly <'\\'>, - any_char - > - > - > - >, - lookahead < - sequence < - optional_css_comments, - alternatives < - exactly <';'>, - exactly <'}'>, - end_of_file - > - > - > - >(src); - } - - const char* static_value(const char* src) { - return sequence< sequence< - static_component, - zero_plus< identifier > - >, - zero_plus < sequence< - alternatives< - sequence< optional_spaces, alternatives< - exactly < '/' >, - exactly < ',' >, - exactly < ' ' > - >, optional_spaces >, - spaces - >, - static_component - > >, - zero_plus < spaces >, - alternatives< exactly<';'>, exactly<'}'> > - >(src); - } - - extern const char css_variable_url_negates[] = "()[]{}\"'#/"; - const char* css_variable_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; - const char* css_variable_top_level_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_top_level_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - const char* parenthese_scope(const char* src) { - return sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - >(src); - } - - const char* re_selector_list(const char* src) { - return alternatives < - // partial bem selector - sequence < - ampersand, - one_plus < - exactly < '-' > - >, - word_boundary, - optional_spaces - >, - // main selector matching - one_plus < - alternatives < - // consume whitespace and comments - spaces, block_comment, line_comment, - // match `/deep/` selector (pass-trough) - // there is no functionality for it yet - schema_reference_combinator, - // match selector ops /[*&%,\[\]]/ - class_char < selector_lookahead_ops >, - // match selector combinators /[>+~]/ - class_char < selector_combinator_ops >, - // match pseudo selectors - sequence < - exactly <'('>, - optional_spaces, - optional , - optional_spaces, - exactly <')'> - >, - // match attribute compare operators - alternatives < - exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match - >, - // main selector match - sequence < - // allow namespace prefix - optional < namespace_schema >, - // modifiers prefixes - alternatives < - sequence < - exactly <'#'>, - // not for interpolation - negate < exactly <'{'> > - >, - // class match - exactly <'.'>, - // single or double colon - sequence < - optional < pseudo_prefix >, - // fix libsass issue 2376 - negate < uri_prefix > - > - >, - // accept hypens in token - one_plus < sequence < - // can start with hyphens - zero_plus < - sequence < - exactly <'-'>, - optional_spaces - > - >, - // now the main token - alternatives < - kwd_optional, - exactly <'*'>, - quoted_string, - interpolant, - identifier, - variable, - percentage, - binomial, - dimension, - alnum - > - > >, - // can also end with hyphens - zero_plus < exactly<'-'> > - > - > - > - >(src); - } - - const char* type_selector(const char* src) { - return sequence< optional, identifier>(src); - } - const char* re_type_selector(const char* src) { - return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); - } - const char* re_static_expression(const char* src) { - return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); - } - - // lexer special_fn: these functions cannot be overloaded - // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) - const char* re_special_fun(const char* src) { - - // match this first as we test prefix hyphens - if (const char* calc = calc_fn_call(src)) { - return calc; - } - - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < - alternatives < - alpha, - exactly <'+'>, - exactly <'-'> - > - > - > - >, - alternatives < - word < expression_kwd >, - sequence < - sequence < - exactly < progid_kwd >, - exactly <':'> - >, - zero_plus < - alternatives < - char_range <'a', 'z'>, - exactly <'.'> - > - > - > - > - >(src); - } - - } -} diff --git a/src/libsass/src/prelexer.hpp b/src/libsass/src/prelexer.hpp deleted file mode 100644 index 2d8f83164..000000000 --- a/src/libsass/src/prelexer.hpp +++ /dev/null @@ -1,484 +0,0 @@ -#ifndef SASS_PRELEXER_H -#define SASS_PRELEXER_H - -#include -#include "lexer.hpp" - -namespace Sass { - // using namespace Lexer; - namespace Prelexer { - - //#################################### - // KEYWORD "REGEX" MATCHERS - //#################################### - - // Match Sass boolean keywords. - const char* kwd_true(const char* src); - const char* kwd_false(const char* src); - const char* kwd_only(const char* src); - const char* kwd_and(const char* src); - const char* kwd_or(const char* src); - const char* kwd_not(const char* src); - const char* kwd_eq(const char* src); - const char* kwd_neq(const char* src); - const char* kwd_gt(const char* src); - const char* kwd_gte(const char* src); - const char* kwd_lt(const char* src); - const char* kwd_lte(const char* src); - - // Match standard control chars - const char* kwd_at(const char* src); - const char* kwd_dot(const char* src); - const char* kwd_comma(const char* src); - const char* kwd_colon(const char* src); - const char* kwd_slash(const char* src); - const char* kwd_star(const char* src); - const char* kwd_plus(const char* src); - const char* kwd_minus(const char* src); - - //#################################### - // SPECIAL "REGEX" CONSTRUCTS - //#################################### - - // Match a sequence of characters delimited by the supplied chars. - template - const char* delimited_by(const char* src) { - src = exactly(src); - if (!src) return 0; - const char* stop; - while (true) { - if (!*src) return 0; - stop = exactly(src); - if (stop && (!esc || *(src - 1) != '\\')) return stop; - src = stop ? stop : src + 1; - } - } - - // skip to delimiter (mx) inside given range - // this will savely skip over all quoted strings - // recursive skip stuff delimited by start/stop - // first start/opener must be consumed already! - template - const char* skip_over_scopes(const char* src, const char* end) { - - size_t level = 0; - bool in_squote = false; - bool in_dquote = false; - // bool in_braces = false; - - while (*src) { - - // check for abort condition - if (end && src >= end) break; - - // has escaped sequence? - if (*src == '\\') { - ++ src; // skip this (and next) - } - else if (*src == '"') { - in_dquote = ! in_dquote; - } - else if (*src == '\'') { - in_squote = ! in_squote; - } - else if (in_dquote || in_squote) { - // take everything literally - } - - // find another opener inside? - else if (const char* pos = start(src)) { - ++ level; // increase counter - src = pos - 1; // advance position - } - - // look for the closer (maybe final, maybe not) - else if (const char* final = stop(src)) { - // only close one level? - if (level > 0) -- level; - // return position at end of stop - // delimiter may be multiple chars - else return final; - // advance position - src = final - 1; - } - - // next - ++ src; - } - - return 0; - } - - // skip to a skip delimited by parentheses - // uses smart `skip_over_scopes` internally - const char* parenthese_scope(const char* src); - - // skip to delimiter (mx) inside given range - // this will savely skip over all quoted strings - // recursive skip stuff delimited by start/stop - // first start/opener must be consumed already! - template - const char* skip_over_scopes(const char* src) { - return skip_over_scopes(src, 0); - } - - // Match a sequence of characters delimited by the supplied chars. - template - const char* recursive_scopes(const char* src) { - // parse opener - src = start(src); - // abort if not found - if (!src) return 0; - // parse the rest until final closer - return skip_over_scopes(src); - } - - // Match a sequence of characters delimited by the supplied strings. - template - const char* delimited_by(const char* src) { - src = exactly(src); - if (!src) return 0; - const char* stop; - while (true) { - if (!*src) return 0; - stop = exactly(src); - if (stop && (!esc || *(src - 1) != '\\')) return stop; - src = stop ? stop : src + 1; - } - } - - // Tries to match a certain number of times (between the supplied interval). - template - const char* between(const char* src) { - for (size_t i = 0; i < lo; ++i) { - src = mx(src); - if (!src) return 0; - } - for (size_t i = lo; i <= hi; ++i) { - const char* new_src = mx(src); - if (!new_src) return src; - src = new_src; - } - return src; - } - - // equivalent of STRING_REGULAR_EXPRESSIONS - const char* re_string_double_open(const char* src); - const char* re_string_double_close(const char* src); - const char* re_string_single_open(const char* src); - const char* re_string_single_close(const char* src); - const char* re_string_uri_open(const char* src); - const char* re_string_uri_close(const char* src); - - // Match a line comment. - const char* line_comment(const char* src); - - // Match a block comment. - const char* block_comment(const char* src); - // Match either. - const char* comment(const char* src); - // Match double- and single-quoted strings. - const char* double_quoted_string(const char* src); - const char* single_quoted_string(const char* src); - const char* quoted_string(const char* src); - // Match interpolants. - const char* interpolant(const char* src); - // Match number prefix ([\+\-]+) - const char* number_prefix(const char* src); - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src); - const char* css_whitespace(const char* src); - // Match optional_css_whitepace plus block_comments - const char* optional_css_comments(const char* src); - const char* css_comments(const char* src); - - // Match one backslash escaped char - const char* escape_seq(const char* src); - - // Match CSS css variables. - const char* custom_property_name(const char* src); - // Match a CSS identifier. - const char* identifier(const char* src); - const char* identifier_alpha(const char* src); - const char* identifier_alnum(const char* src); - const char* strict_identifier(const char* src); - const char* strict_identifier_alpha(const char* src); - const char* strict_identifier_alnum(const char* src); - // Match a CSS unit identifier. - const char* one_unit(const char* src); - const char* multiple_units(const char* src); - const char* unit_identifier(const char* src); - // const char* strict_identifier_alnums(const char* src); - // Match reference selector. - const char* re_reference_combinator(const char* src); - const char* static_reference_combinator(const char* src); - const char* schema_reference_combinator(const char* src); - - // Match interpolant schemas - const char* identifier_schema(const char* src); - const char* value_schema(const char* src); - const char* sass_value(const char* src); - // const char* filename(const char* src); - // const char* filename_schema(const char* src); - // const char* url_schema(const char* src); - // const char* url_value(const char* src); - const char* vendor_prefix(const char* src); - - const char* re_special_directive(const char* src); - const char* re_prefixed_directive(const char* src); - const char* re_almost_any_value_token(const char* src); - - // Match CSS '@' keywords. - const char* at_keyword(const char* src); - const char* kwd_import(const char* src); - const char* kwd_at_root(const char* src); - const char* kwd_with_directive(const char* src); - const char* kwd_without_directive(const char* src); - const char* kwd_media(const char* src); - const char* kwd_supports_directive(const char* src); - // const char* keyframes(const char* src); - // const char* keyf(const char* src); - const char* kwd_mixin(const char* src); - const char* kwd_function(const char* src); - const char* kwd_return_directive(const char* src); - const char* kwd_include_directive(const char* src); - const char* kwd_content_directive(const char* src); - const char* kwd_charset_directive(const char* src); - const char* kwd_extend(const char* src); - - const char* unicode_seq(const char* src); - - const char* kwd_if_directive(const char* src); - const char* kwd_else_directive(const char* src); - const char* elseif_directive(const char* src); - - const char* kwd_for_directive(const char* src); - const char* kwd_from(const char* src); - const char* kwd_to(const char* src); - const char* kwd_through(const char* src); - - const char* kwd_each_directive(const char* src); - const char* kwd_in(const char* src); - - const char* kwd_while_directive(const char* src); - - const char* re_nothing(const char* src); - - const char* re_special_fun(const char* src); - - const char* kwd_warn(const char* src); - const char* kwd_err(const char* src); - const char* kwd_dbg(const char* src); - - const char* kwd_null(const char* src); - - const char* re_selector_list(const char* src); - const char* re_type_selector(const char* src); - const char* re_static_expression(const char* src); - - // identifier that can start with hyphens - const char* css_identifier(const char* src); - const char* css_ip_identifier(const char* src); - - // Match CSS type selectors - const char* namespace_schema(const char* src); - const char* namespace_prefix(const char* src); - const char* type_selector(const char* src); - const char* hyphens_and_identifier(const char* src); - const char* hyphens_and_name(const char* src); - const char* universal(const char* src); - // Match CSS id names. - const char* id_name(const char* src); - // Match CSS class names. - const char* class_name(const char* src); - // Attribute name in an attribute selector - const char* attribute_name(const char* src); - // Match placeholder selectors. - const char* placeholder(const char* src); - // Match CSS numeric constants. - const char* op(const char* src); - const char* sign(const char* src); - const char* unsigned_number(const char* src); - const char* number(const char* src); - const char* coefficient(const char* src); - const char* binomial(const char* src); - const char* percentage(const char* src); - const char* ampersand(const char* src); - const char* dimension(const char* src); - const char* hex(const char* src); - const char* hexa(const char* src); - const char* hex0(const char* src); - // const char* rgb_prefix(const char* src); - // Match CSS uri specifiers. - const char* uri_prefix(const char* src); - // Match CSS "!important" keyword. - const char* kwd_important(const char* src); - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src); - // Match Sass "!default" keyword. - const char* default_flag(const char* src); - const char* global_flag(const char* src); - // Match CSS pseudo-class/element prefixes - const char* pseudo_prefix(const char* src); - // Match CSS function call openers. - const char* re_functional(const char* src); - const char* re_pseudo_selector(const char* src); - const char* functional_schema(const char* src); - const char* pseudo_not(const char* src); - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src); - const char* odd(const char* src); - // Match CSS attribute-matching operators. - const char* exact_match(const char* src); - const char* class_match(const char* src); - const char* dash_match(const char* src); - const char* prefix_match(const char* src); - const char* suffix_match(const char* src); - const char* substring_match(const char* src); - // Match CSS combinators. - // const char* adjacent_to(const char* src); - // const char* precedes(const char* src); - // const char* parent_of(const char* src); - // const char* ancestor_of(const char* src); - - // Match SCSS variable names. - const char* variable(const char* src); - const char* calc_fn_call(const char* src); - - // IE stuff - const char* ie_progid(const char* src); - const char* ie_expression(const char* src); - const char* ie_property(const char* src); - const char* ie_keyword_arg(const char* src); - const char* ie_keyword_arg_value(const char* src); - const char* ie_keyword_arg_property(const char* src); - - // characters that terminate parsing of a list - const char* list_terminator(const char* src); - const char* space_list_terminator(const char* src); - - // match url() - const char* H(const char* src); - const char* W(const char* src); - // `UNICODE` makes VS sad - const char* UUNICODE(const char* src); - const char* NONASCII(const char* src); - const char* ESCAPE(const char* src); - const char* real_uri(const char* src); - const char* real_uri_suffix(const char* src); - // const char* real_uri_prefix(const char* src); - const char* real_uri_value(const char* src); - - // Path matching functions. - // const char* folder(const char* src); - // const char* folders(const char* src); - - - const char* static_string(const char* src); - const char* static_component(const char* src); - const char* static_property(const char* src); - const char* static_value(const char* src); - - const char* css_variable_value(const char* src); - const char* css_variable_top_level_value(const char* src); - - // Utility functions for finding and counting characters in a string. - template - const char* find_first(const char* src) { - while (*src && *src != c) ++src; - return *src ? src : 0; - } - template - const char* find_first(const char* src) { - while (*src && !mx(src)) ++src; - return *src ? src : 0; - } - template - const char* find_first_in_interval(const char* beg, const char* end) { - bool esc = false; - while ((beg < end) && *beg) { - if (esc) esc = false; - else if (*beg == '\\') esc = true; - else if (mx(beg)) return beg; - ++beg; - } - return 0; - } - template - const char* find_first_in_interval(const char* beg, const char* end) { - bool esc = false; - while ((beg < end) && *beg) { - if (esc) esc = false; - else if (*beg == '\\') esc = true; - else if (const char* pos = skip(beg)) beg = pos; - else if (mx(beg)) return beg; - ++beg; - } - return 0; - } - template - unsigned int count_interval(const char* beg, const char* end) { - unsigned int counter = 0; - bool esc = false; - while (beg < end && *beg) { - const char* p; - if (esc) { - esc = false; - ++beg; - } else if (*beg == '\\') { - esc = true; - ++beg; - } else if ((p = mx(beg))) { - ++counter; - beg = p; - } - else { - ++beg; - } - } - return counter; - } - - template - const char* padded_token(const char* src) - { - size_t got = 0; - const char* pos = src; - while (got < size) { - if (!mx(pos)) break; - ++ pos; ++ got; - } - while (got < size) { - if (!pad(pos)) break; - ++ pos; ++ got; - } - return got ? pos : 0; - } - - template - const char* minmax_range(const char* src) - { - size_t got = 0; - const char* pos = src; - while (got < max) { - if (!mx(pos)) break; - ++ pos; ++ got; - } - if (got < min) return 0; - if (got > max) return 0; - return pos; - } - - template - const char* char_range(const char* src) - { - if (*src < min) return 0; - if (*src > max) return 0; - return src + 1; - } - - } -} - -#endif diff --git a/src/libsass/src/remove_placeholders.cpp b/src/libsass/src/remove_placeholders.cpp deleted file mode 100644 index 15cddace2..000000000 --- a/src/libsass/src/remove_placeholders.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "sass.hpp" -#include "remove_placeholders.hpp" -#include "context.hpp" -#include "inspect.hpp" -#include - -namespace Sass { - - Remove_Placeholders::Remove_Placeholders() - { } - - void Remove_Placeholders::operator()(Block_Ptr b) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr st = b->at(i); - st->perform(this); - } - } - - Selector_List_Ptr Remove_Placeholders::remove_placeholders(Selector_List_Ptr sl) - { - Selector_List_Ptr new_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); - - for (size_t i = 0, L = sl->length(); i < L; ++i) { - if (!sl->at(i)->contains_placeholder()) { - new_sl->append(sl->at(i)); - } - } - - return new_sl; - - } - - - void Remove_Placeholders::operator()(Ruleset_Ptr r) { - // Create a new selector group without placeholders - Selector_List_Obj sl = Cast(r->selector()); - - if (sl) { - // Set the new placeholder selector list - r->selector(remove_placeholders(sl)); - // Remove placeholders in wrapped selectors - for (Complex_Selector_Obj cs : sl->elements()) { - while (cs) { - if (cs->head()) { - for (Simple_Selector_Obj& ss : cs->head()->elements()) { - if (Wrapped_Selector_Ptr ws = Cast(ss)) { - if (Selector_List_Ptr wsl = Cast(ws->selector())) { - Selector_List_Ptr clean = remove_placeholders(wsl); - // also clean superflous parent selectors - // probably not really the correct place - clean->remove_parent_selectors(); - ws->selector(clean); - } - } - } - } - cs = cs->tail(); - } - } - } - - // Iterate into child blocks - Block_Obj b = r->block(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->at(i)) { - Statement_Obj st = b->at(i); - st->perform(this); - } - } - } - - void Remove_Placeholders::operator()(Media_Block_Ptr m) { - operator()(m->block()); - } - void Remove_Placeholders::operator()(Supports_Block_Ptr m) { - operator()(m->block()); - } - - void Remove_Placeholders::operator()(Directive_Ptr a) { - if (a->block()) a->block()->perform(this); - } - -} diff --git a/src/libsass/src/remove_placeholders.hpp b/src/libsass/src/remove_placeholders.hpp deleted file mode 100644 index c13b63134..000000000 --- a/src/libsass/src/remove_placeholders.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef SASS_REMOVE_PLACEHOLDERS_H -#define SASS_REMOVE_PLACEHOLDERS_H - -#pragma once - -#include "ast.hpp" -#include "operation.hpp" - -namespace Sass { - - - class Remove_Placeholders : public Operation_CRTP { - - void fallback_impl(AST_Node_Ptr n) {} - - public: - Selector_List_Ptr remove_placeholders(Selector_List_Ptr); - - public: - Remove_Placeholders(); - ~Remove_Placeholders() { } - - void operator()(Block_Ptr); - void operator()(Ruleset_Ptr); - void operator()(Media_Block_Ptr); - void operator()(Supports_Block_Ptr); - void operator()(Directive_Ptr); - - template - void fallback(U x) { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/sass.cpp b/src/libsass/src/sass.cpp deleted file mode 100644 index 72edd7ced..000000000 --- a/src/libsass/src/sass.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include - -#include "sass.h" -#include "file.hpp" -#include "util.hpp" -#include "sass_context.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - // helper to convert string list to vector - std::vector list2vec(struct string_list* cur) - { - std::vector list; - while (cur) { - list.push_back(cur->string); - cur = cur->next; - } - return list; - } - -} - -extern "C" { - using namespace Sass; - - // Allocate libsass heap memory - // Don't forget string termination! - void* ADDCALL sass_alloc_memory(size_t size) - { - void* ptr = malloc(size); - if (ptr == NULL) { - std::cerr << "Out of memory.\n"; - exit(EXIT_FAILURE); - } - return ptr; - } - - char* ADDCALL sass_copy_c_string(const char* str) - { - size_t len = strlen(str) + 1; - char* cpy = (char*) sass_alloc_memory(len); - std::memcpy(cpy, str, len); - return cpy; - } - - // Deallocate libsass heap memory - void ADDCALL sass_free_memory(void* ptr) - { - if (ptr) free (ptr); - } - - // caller must free the returned memory - char* ADDCALL sass_string_quote (const char *str, const char quote_mark) - { - std::string quoted = quote(str, quote_mark); - return sass_copy_c_string(quoted.c_str()); - } - - // caller must free the returned memory - char* ADDCALL sass_string_unquote (const char *str) - { - std::string unquoted = unquote(str); - return sass_copy_c_string(unquoted.c_str()); - } - - char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const std::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - std::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - std::string resolved(File::find_include(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const std::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - std::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - std::string resolved(File::find_file(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - // this has the original resolve logic for sass include - char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) - { - std::vector vec(list2vec(opt->include_paths)); - std::string resolved(File::find_include(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) - { - std::vector vec(list2vec(opt->include_paths)); - std::string resolved(File::find_file(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Get compiled libsass version - const char* ADDCALL libsass_version(void) - { - return LIBSASS_VERSION; - } - - // Get compiled libsass version - const char* ADDCALL libsass_language_version(void) - { - return LIBSASS_LANGUAGE_VERSION; - } - -} - -namespace Sass { - - // helper to aid dreaded MSVC debug mode - char* sass_copy_string(std::string str) - { - // In MSVC the following can lead to segfault: - // sass_copy_c_string(stream.str().c_str()); - // Reason is that the string returned by str() is disposed before - // sass_copy_c_string is invoked. The string is actually a stack - // object, so indeed nobody is holding on to it. So it seems - // perfectly fair to release it right away. So the const char* - // by c_str will point to invalid memory. I'm not sure if this is - // the behavior for all compiler, but I'm pretty sure we would - // have gotten more issues reported if that would be the case. - // Wrapping it in a functions seems the cleanest approach as the - // function must hold on to the stack variable until it's done. - return sass_copy_c_string(str.c_str()); - } - -} \ No newline at end of file diff --git a/src/libsass/src/sass.hpp b/src/libsass/src/sass.hpp deleted file mode 100644 index f0550490c..000000000 --- a/src/libsass/src/sass.hpp +++ /dev/null @@ -1,139 +0,0 @@ -// must be the first include in all compile units -#ifndef SASS_SASS_H -#define SASS_SASS_H - -// undefine extensions macro to tell sys includes -// that we do not want any macros to be exported -// mainly fixes an issue on SmartOS (SEC macro) -#undef __EXTENSIONS__ - -#ifdef _MSC_VER -#pragma warning(disable : 4005) -#endif - -// aplies to MSVC and MinGW -#ifdef _WIN32 -// we do not want the ERROR macro -# define NOGDI -// we do not want the min/max macro -# define NOMINMAX -// we do not want the IN/OUT macro -# define _NO_W32_PSEUDO_MODIFIERS -#endif - - -// should we be case insensitive -// when dealing with files or paths -#ifndef FS_CASE_SENSITIVE -# ifdef _WIN32 -# define FS_CASE_SENSITIVE 0 -# else -# define FS_CASE_SENSITIVE 1 -# endif -#endif - -// path separation char -#ifndef PATH_SEP -# ifdef _WIN32 -# define PATH_SEP ';' -# else -# define PATH_SEP ':' -# endif -#endif - - -// include C-API header -#include "sass/base.h" - -// For C++ helper -#include - -// output behaviours -namespace Sass { - - // create some C++ aliases for the most used options - const static Sass_Output_Style NESTED = SASS_STYLE_NESTED; - const static Sass_Output_Style COMPACT = SASS_STYLE_COMPACT; - const static Sass_Output_Style EXPANDED = SASS_STYLE_EXPANDED; - const static Sass_Output_Style COMPRESSED = SASS_STYLE_COMPRESSED; - // only used internal to trigger ruby inspect behavior - const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; - const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; - - // helper to aid dreaded MSVC debug mode - // see implementation for more details - char* sass_copy_string(std::string str); - -} - -// input behaviours -enum Sass_Input_Style { - SASS_CONTEXT_NULL, - SASS_CONTEXT_FILE, - SASS_CONTEXT_DATA, - SASS_CONTEXT_FOLDER -}; - -// simple linked list -struct string_list { - string_list* next; - char* string; -}; - -// sass config options structure -struct Sass_Inspect_Options { - - // Output style for the generated css code - // A value from above SASS_STYLE_* constants - enum Sass_Output_Style output_style; - - // Precision for fractional numbers - int precision; - - // Do not compress colors in selectors - bool in_selector; - - // initialization list (constructor with defaults) - Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 5, bool in_selector = false) - : output_style(style), precision(precision), in_selector(in_selector) - { } - -}; - -// sass config options structure -struct Sass_Output_Options : Sass_Inspect_Options { - - // String to be used for indentation - const char* indent; - // String to be used to for line feeds - const char* linefeed; - - // Emit comments in the generated CSS indicating - // the corresponding source line. - bool source_comments; - - // initialization list (constructor with defaults) - Sass_Output_Options(struct Sass_Inspect_Options opt, - const char* indent = " ", - const char* linefeed = "\n", - bool source_comments = false) - : Sass_Inspect_Options(opt), - indent(indent), linefeed(linefeed), - source_comments(source_comments) - { } - - // initialization list (constructor with defaults) - Sass_Output_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 5, - const char* indent = " ", - const char* linefeed = "\n", - bool source_comments = false) - : Sass_Inspect_Options(style, precision), - indent(indent), linefeed(linefeed), - source_comments(source_comments) - { } - -}; - -#endif diff --git a/src/libsass/src/sass2scss.cpp b/src/libsass/src/sass2scss.cpp deleted file mode 100644 index 56333b38e..000000000 --- a/src/libsass/src/sass2scss.cpp +++ /dev/null @@ -1,864 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#define _CRT_NONSTDC_NO_DEPRECATE -#endif - -// include library -#include -#include -#include -#include -#include -#include -#include - -///* -// -// src comments: comments in sass syntax (staring with //) -// css comments: multiline comments in css syntax (starting with /*) -// -// KEEP_COMMENT: keep src comments in the resulting css code -// STRIP_COMMENT: strip out all comments (either src or css) -// CONVERT_COMMENT: convert all src comments to css comments -// -//*/ - -// our own header -#include "sass2scss.h" - -// add namespace for c++ -namespace Sass -{ - - // return the actual prettify value from options - #define PRETTIFY(converter) (converter.options - (converter.options & 248)) - // query the options integer to check if the option is enables - #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) - #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) - #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) - - // some makros to access the indentation stack - #define INDENT(converter) (converter.indents.top()) - - // some makros to query comment parser status - #define IS_PARSING(converter) (converter.comment == "") - #define IS_COMMENT(converter) (converter.comment != "") - #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) - #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) - - // pretty printer helper function - static std::string closer (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " }" : - PRETTIFY(converter) <= 1 ? " }" : - "\n" + INDENT(converter) + "}"; - } - - // pretty printer helper function - static std::string opener (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " { " : - PRETTIFY(converter) <= 2 ? " {" : - "\n" + INDENT(converter) + "{"; - } - - // check if the given string is a pseudo selector - // needed to differentiate from sass property syntax - static bool isPseudoSelector (std::string& sel) - { - - size_t len = sel.length(); - if (len < 1) return false; - size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); - if (pos != std::string::npos) sel.erase(pos, std::string::npos); - size_t i = sel.length(); - while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } - - // CSS Level 1 - Recommendation - if (sel == ":link") return true; - if (sel == ":visited") return true; - if (sel == ":active") return true; - - // CSS Level 2 (Revision 1) - Recommendation - if (sel == ":lang") return true; - if (sel == ":first-child") return true; - if (sel == ":hover") return true; - if (sel == ":focus") return true; - // disabled - also valid properties - // if (sel == ":left") return true; - // if (sel == ":right") return true; - if (sel == ":first") return true; - - // Selectors Level 3 - Recommendation - if (sel == ":target") return true; - if (sel == ":root") return true; - if (sel == ":nth-child") return true; - if (sel == ":nth-last-of-child") return true; - if (sel == ":nth-of-type") return true; - if (sel == ":nth-last-of-type") return true; - if (sel == ":last-child") return true; - if (sel == ":first-of-type") return true; - if (sel == ":last-of-type") return true; - if (sel == ":only-child") return true; - if (sel == ":only-of-type") return true; - if (sel == ":empty") return true; - if (sel == ":not") return true; - - // CSS Basic User Interface Module Level 3 - Working Draft - if (sel == ":default") return true; - if (sel == ":valid") return true; - if (sel == ":invalid") return true; - if (sel == ":in-range") return true; - if (sel == ":out-of-range") return true; - if (sel == ":required") return true; - if (sel == ":optional") return true; - if (sel == ":read-only") return true; - if (sel == ":read-write") return true; - if (sel == ":dir") return true; - if (sel == ":enabled") return true; - if (sel == ":disabled") return true; - if (sel == ":checked") return true; - if (sel == ":indeterminate") return true; - if (sel == ":nth-last-child") return true; - - // Selectors Level 4 - Working Draft - if (sel == ":any-link") return true; - if (sel == ":local-link") return true; - if (sel == ":scope") return true; - if (sel == ":active-drop-target") return true; - if (sel == ":valid-drop-target") return true; - if (sel == ":invalid-drop-target") return true; - if (sel == ":current") return true; - if (sel == ":past") return true; - if (sel == ":future") return true; - if (sel == ":placeholder-shown") return true; - if (sel == ":user-error") return true; - if (sel == ":blank") return true; - if (sel == ":nth-match") return true; - if (sel == ":nth-last-match") return true; - if (sel == ":nth-column") return true; - if (sel == ":nth-last-column") return true; - if (sel == ":matches") return true; - - // Fullscreen API - Living Standard - if (sel == ":fullscreen") return true; - - // not a pseudo selector - return false; - - } - - // check if there is some char data - // will ignore everything in comments - static bool hasCharData (std::string& sass) - { - - size_t col_pos = 0; - - while (true) - { - - // try to find some meaningfull char - col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); - - // there was no meaningfull char found - if (col_pos == std::string::npos) return false; - - // found a multiline comment opener - if (sass.substr(col_pos, 2) == "/*") - { - // find the multiline comment closer - col_pos = sass.find("*/", col_pos); - // maybe we did not find the closer here - if (col_pos == std::string::npos) return false; - // skip closer - col_pos += 2; - } - else - { - return true; - } - - } - - } - // EO hasCharData - - // find src comment opener - // correctly skips quoted strings - static size_t findCommentOpener (std::string& sass) - { - - size_t col_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - size_t brackets = 0; - - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*()", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - if (character == '(') - { - if (!quoted && !apoed) brackets ++; - } - else if (character == ')') - { - if (!quoted && !apoed) brackets --; - } - else if (character == '\"') - { - // invert quote bool - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - // invert quote bool - if (!quoted && !comment) apoed = !apoed; - } - else if (col_pos > 0 && character == '/') - { - if (sass.at(col_pos - 1) == '*') - { - comment = false; - } - // next needs to be a slash too - else if (sass.at(col_pos - 1) == '/') - { - // only found if not in single or double quote, bracket or comment - if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; - } - } - else if (character == '\\') - { - // skip next char if in quote - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (col_pos > 0 && character == '*') - { - // opening a multiline comment - if (sass.at(col_pos - 1) == '/') - { - // we are now in a comment - if (!quoted && !apoed) comment = true; - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - return col_pos; - - } - // EO findCommentOpener - - // remove multiline comments from sass string - // correctly skips quoted strings - static std::string removeMultilineComment (std::string &sass) - { - - std::string clean = ""; - size_t col_pos = 0; - size_t open_pos = 0; - size_t close_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - - // process sass til string end - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - // found quoted string delimiter - if (character == '\"') - { - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - if (!quoted && !comment) apoed = !apoed; - } - // found possible comment closer - else if (character == '/') - { - // look back to see if it is actually a closer - if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') - { - close_pos = col_pos + 1; comment = false; - } - } - else if (character == '\\') - { - // skip escaped char - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (character == '*') - { - // look back to see if it is actually an opener - if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') - { - comment = true; open_pos = col_pos - 1; - clean += sass.substr(close_pos, open_pos - close_pos); - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - // add final parts (add half open comment text) - if (comment) clean += sass.substr(open_pos); - else clean += sass.substr(close_pos); - - // return string - return clean; - - } - // EO removeMultilineComment - - // right trim a given string - std::string rtrim(const std::string &sass) - { - std::string trimmed = sass; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) - { trimmed.erase(pos_ws + 1); } - else { trimmed.clear(); } - return trimmed; - } - // EO rtrim - - // flush whitespace and print additional text, but - // only print additional chars and buffer whitespace - std::string flush (std::string& sass, converter& converter) - { - - // return flushed - std::string scss = ""; - - // print whitespace buffer - scss += PRETTIFY(converter) > 0 ? - converter.whitespace : ""; - // reset whitespace buffer - converter.whitespace = ""; - - // remove possible newlines from string - size_t pos_right = sass.find_last_not_of("\n\r"); - if (pos_right == std::string::npos) return scss; - - // get the linefeeds from the string - std::string lfs = sass.substr(pos_right + 1); - sass = sass.substr(0, pos_right + 1); - - // find some source comment opener - size_t comment_pos = findCommentOpener(sass); - // check if there was a source comment - if (comment_pos != std::string::npos) - { - // convert comment (but only outside other coments) - if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) - { - // convert to multiline comment - sass.at(comment_pos + 1) = '*'; - // add comment node to the whitespace - sass += " */"; - } - // not at line start - if (comment_pos > 0) - { - // also include whitespace before the actual comment opener - size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); - comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; - } - if (!STRIP_COMMENT(converter)) - { - // add comment node to the whitespace - converter.whitespace += sass.substr(comment_pos); - } - else - { - // sass = removeMultilineComments(sass); - } - // update the actual sass code - sass = sass.substr(0, comment_pos); - } - - // add newline as getline discharged it - converter.whitespace += lfs + "\n"; - - // maybe remove any leading whitespace - if (PRETTIFY(converter) == 0) - { - // remove leading whitespace and update string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - if (pos_left != std::string::npos) sass = sass.substr(pos_left); - } - - // add flushed data - scss += sass; - - // return string - return scss; - - } - // EO flush - - // process a line of the sass text - std::string process (std::string& sass, converter& converter) - { - - // resulting string - std::string scss = ""; - - // strip multi line comments - if (STRIP_COMMENT(converter)) - { - sass = removeMultilineComment(sass); - } - - // right trim input - sass = rtrim(sass); - - // get postion of first meaningfull character in string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - - // special case for final run - if (converter.end_of_file) pos_left = 0; - - // maybe has only whitespace - if (pos_left == std::string::npos) - { - // just add complete whitespace - converter.whitespace += sass + "\n"; - } - // have meaningfull first char - else - { - - // extract and store indentation string - std::string indent = sass.substr(0, pos_left); - - // check if current line starts a comment - std::string open = sass.substr(pos_left, 2); - - // line has less or same indentation - // finalize previous open parser context - if (indent.length() <= INDENT(converter).length()) - { - - // close multilinie comment - if (IS_CSS_COMMENT(converter)) - { - // check if comments will be stripped anyway - if (!STRIP_COMMENT(converter)) scss += " */"; - } - // close src comment comment - else if (IS_SRC_COMMENT(converter)) - { - // add a newline to avoid closer on same line - // this would put the bracket in the comment node - // no longer needed since we parse them correctly - // if (KEEP_COMMENT(converter)) scss += "\n"; - } - // close css properties - else if (converter.property) - { - // add closer unless in concat mode - if (!converter.comma) - { - // if there was no colon we have a selector - // looks like there were no inner properties - if (converter.selector) scss += " {}"; - // add final semicolon - else if (!converter.semicolon) scss += ";"; - } - } - - // reset comment state - converter.comment = ""; - - } - - // make sure we close every "higher" block - while (indent.length() < INDENT(converter).length()) - { - // pop stacked context - converter.indents.pop(); - // print close bracket - if (IS_PARSING(converter)) - { scss += closer(converter); } - else { scss += " */"; } - // reset comment state - converter.comment = ""; - } - - // reset converter state - converter.selector = false; - - // looks like some undocumented behavior ... - // https://github.com/mgreter/sass2scss/issues/29 - if (sass.substr(pos_left, 1) == "\\") { - converter.selector = true; - sass[pos_left] = ' '; - } - - // check if we have sass property syntax - if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") - { - - // default to a selector - // change back if property found - converter.selector = true; - // get postion of first whitespace char - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - // assertion check for valid result - if (pos_wspace != std::string::npos) - { - // get the possible pseudo selector - std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); - // get position of the first real property value char - // pseudo selectors get this far, but have no actual value - size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); - // assertion check for valid result - if (pos_value != std::string::npos) - { - // only process if not (fallowed by a semicolon or is a pseudo selector) - if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) - { - // create new string by interchanging the colon sign for property and value - sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); - // try to find a colon in the current line, but only ... - size_t pos_colon = sass.find_first_not_of(":", pos_left); - // assertion for valid result - if (pos_colon != std::string::npos) - { - // ... after the first word (skip begining colons) - pos_colon = sass.find_first_of(":", pos_colon); - // it is a selector if there was no colon found - converter.selector = pos_colon == std::string::npos; - } - } - } - } - - // check if we have a BEM property (one colon and no selector) - if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; - } - - } - - // terminate some statements immediately - else if ( - sass.substr(pos_left, 5) == "@warn" || - sass.substr(pos_left, 6) == "@debug" || - sass.substr(pos_left, 6) == "@error" || - sass.substr(pos_left, 8) == "@charset" || - sass.substr(pos_left, 10) == "@namespace" - ) { sass = indent + sass.substr(pos_left); } - // replace some specific sass shorthand directives (if not fallowed by a white space character) - else if (sass.substr(pos_left, 1) == "=") - { sass = indent + "@mixin " + sass.substr(pos_left + 1); } - else if (sass.substr(pos_left, 1) == "+") - { - // must be followed by a mixin call (no whitespace afterwards or at ending directly) - if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { - sass = indent + "@include " + sass.substr(pos_left + 1); - } - } - - // add quotes for import if needed - else if (sass.substr(pos_left, 7) == "@import") - { - // get positions for the actual import url - size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - // leave proper urls untouched - if (sass.substr(pos_quote, 4) != "url(") - { - // check if the url appears to be already quoted - if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'") - { - // get position of the last char on the line - size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - // assertion check for valid result - if (pos_end != std::string::npos) - { - // add quotes around the full line after the import statement - sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\""; - } - } - } - - } - else if ( - sass.substr(pos_left, 7) != "@return" && - sass.substr(pos_left, 7) != "@extend" && - sass.substr(pos_left, 8) != "@include" && - sass.substr(pos_left, 8) != "@content" - ) { - - // probably a selector anyway - converter.selector = true; - // try to find first colon in the current line - size_t pos_colon = sass.find_first_of(":", pos_left); - // assertion that we have a colon - if (pos_colon != std::string::npos) - { - // it is not a selector if we have a space after a colon - if (sass[pos_colon+1] == ' ') converter.selector = false; - if (sass[pos_colon+1] == ' ') converter.selector = false; - } - - } - - // current line has more indentation - if (indent.length() >= INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // has meaningfull chars - if (hasCharData(sass)) - { - // is probably a property - // also true for selectors - converter.property = true; - } - } - } - // current line has more indentation - if (indent.length() > INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // had meaningfull chars - if (converter.property) - { - // print block opener - scss += opener(converter); - // push new stack context - converter.indents.push(""); - // store block indentation - INDENT(converter) = indent; - } - } - // is and will be a src comment - else if (!IS_CSS_COMMENT(converter)) - { - // scss does not allow multiline src comments - // therefore add forward slashes to all lines - sass.at(INDENT(converter).length()+0) = '/'; - // there is an edge case here if indentation - // is minimal (will overwrite the fist char) - sass.at(INDENT(converter).length()+1) = '/'; - // could code around that, but I dont' think - // this will ever be the cause for any trouble - } - } - - // line is opening a new comment - if (open == "/*" || open == "//") - { - // reset the property state - converter.property = false; - // close previous comment - if (IS_CSS_COMMENT(converter) && open != "") - { - if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; - } - // force single line comments - // into a correct css comment - if (CONVERT_COMMENT(converter)) - { - if (IS_PARSING(converter)) - { sass.at(pos_left + 1) = '*'; } - } - // set comment flag - converter.comment = open; - - } - - // flush data only under certain conditions - if (!( - // strip css and src comments if option is set - (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || - // strip src comment even if strip option is not set - // but only if the keep src comment option is not set - (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) - )) - { - // flush data and buffer whitespace - scss += flush(sass, converter); - } - - // get postion of last meaningfull char - size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - - // check for invalid result - if (pos_right != std::string::npos) - { - - // get the last meaningfull char - std::string close = sass.substr(pos_right, 1); - - // check if next line should be concatenated (list mode) - converter.comma = IS_PARSING(converter) && close == ","; - converter.semicolon = IS_PARSING(converter) && close == ";"; - - // check if we have more than - // one meaningfull char - if (pos_right > 0) - { - - // get the last two chars from string - std::string close = sass.substr(pos_right - 1, 2); - // update parser status for expicitly closed comment - if (close == "*/") converter.comment = ""; - - } - - } - // EO have meaningfull chars from end - - } - // EO have meaningfull chars from start - - // return scss - return scss; - - } - // EO process - - // read line with either CR, LF or CR LF format - // http://stackoverflow.com/a/6089413/1550314 - static std::istream& safeGetline(std::istream& is, std::string& t) - { - t.clear(); - - // The characters in the stream are read one-by-one using a std::streambuf. - // That is faster than reading them one-by-one using the std::istream. - // Code that uses streambuf this way must be guarded by a sentry object. - // The sentry object performs various tasks, - // such as thread synchronization and updating the stream state. - - std::istream::sentry se(is, true); - std::streambuf* sb = is.rdbuf(); - - for(;;) { - int c = sb->sbumpc(); - switch (c) { - case '\n': - return is; - case '\r': - if(sb->sgetc() == '\n') - sb->sbumpc(); - return is; - case EOF: - // Also handle the case when the last line has no line ending - if(t.empty()) - is.setstate(std::ios::eofbit); - return is; - default: - t += (char)c; - } - } - } - - // the main converter function for c++ - char* sass2scss (const std::string& sass, const int options) - { - - // local variables - std::string line; - std::string scss = ""; - std::stringstream stream(sass); - - // create converter variable - converter converter; - // initialise all options - converter.comma = false; - converter.property = false; - converter.selector = false; - converter.semicolon = false; - converter.end_of_file = false; - converter.comment = ""; - converter.whitespace = ""; - converter.indents.push(""); - converter.options = options; - - // read line by line and process them - while(safeGetline(stream, line) && !stream.eof()) - { scss += process(line, converter); } - - // create mutable string - std::string closer = ""; - // set the end of file flag - converter.end_of_file = true; - // process to close all open blocks - scss += process(closer, converter); - - // allocate new memory on the heap - // caller has to free it after use - char * cstr = (char*) malloc (scss.length() + 1); - // create a copy of the string - strcpy (cstr, scss.c_str()); - // return pointer - return &cstr[0]; - - } - // EO sass2scss - -} -// EO namespace - -// implement for c -extern "C" -{ - - char* ADDCALL sass2scss (const char* sass, const int options) - { - return Sass::sass2scss(sass, options); - } - - // Get compiled sass2scss version - const char* ADDCALL sass2scss_version(void) { - return SASS2SCSS_VERSION; - } - -} diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp deleted file mode 100644 index 7a0a49ce1..000000000 --- a/src/libsass/src/sass_context.cpp +++ /dev/null @@ -1,799 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include -#include - -#include "sass.h" -#include "ast.hpp" -#include "file.hpp" -#include "json.hpp" -#include "util.hpp" -#include "context.hpp" -#include "sass_context.hpp" -#include "sass_functions.hpp" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" - -#define LFEED "\n" - -// C++ helper -namespace Sass { - // see sass_copy_c_string(std::string str) - static inline JsonNode* json_mkstream(const std::stringstream& stream) - { - // hold on to string on stack! - std::string str(stream.str()); - return json_mkstring(str.c_str()); - } - - static int handle_error(Sass_Context* c_ctx) { - try { - throw; - } - catch (Exception::Base& e) { - std::stringstream msg_stream; - std::string cwd(Sass::File::get_cwd()); - std::string msg_prefix(e.errtype()); - bool got_newline = false; - msg_stream << msg_prefix << ": "; - const char* msg = e.what(); - while (msg && *msg) { - if (*msg == '\r') { - got_newline = true; - } - else if (*msg == '\n') { - got_newline = true; - } - else if (got_newline) { - msg_stream << std::string(msg_prefix.size() + 2, ' '); - got_newline = false; - } - msg_stream << *msg; - ++msg; - } - if (!got_newline) msg_stream << "\n"; - - if (e.traces.empty()) { - // we normally should have some traces, still here as a fallback - std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); - msg_stream << std::string(msg_prefix.size() + 2, ' '); - msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; - } - else { - std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); - msg_stream << traces_to_string(e.traces, " "); - } - - // now create the code trace (ToDo: maybe have util functions?) - if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { - size_t lines = e.pstate.line; - const char* line_beg = e.pstate.src; - // scan through src until target line - // move line_beg pointer to line start - while (line_beg && *line_beg && lines != 0) { - if (*line_beg == '\n') --lines; - utf8::unchecked::next(line_beg); - } - const char* line_end = line_beg; - // move line_end before next newline character - while (line_end && *line_end && *line_end != '\n') { - if (*line_end == '\n') break; - if (*line_end == '\r') break; - utf8::unchecked::next(line_end); - } - if (line_end && *line_end != 0) ++ line_end; - size_t line_len = line_end - line_beg; - size_t move_in = 0; size_t shorten = 0; - size_t left_chars = 42; size_t max_chars = 76; - // reported excerpt should not exceed `max_chars` chars - if (e.pstate.column > line_len) left_chars = e.pstate.column; - if (e.pstate.column > left_chars) move_in = e.pstate.column - left_chars; - if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; - utf8::advance(line_beg, move_in, line_end); - utf8::retreat(line_end, shorten, line_beg); - std::string sanitized; std::string marker(e.pstate.column - move_in, '-'); - utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); - msg_stream << ">> " << sanitized << "\n"; - msg_stream << " " << marker << "^\n"; - } - - JsonNode* json_err = json_mkobject(); - json_append_member(json_err, "status", json_mknumber(1)); - json_append_member(json_err, "file", json_mkstring(e.pstate.path)); - json_append_member(json_err, "line", json_mknumber((double)(e.pstate.line + 1))); - json_append_member(json_err, "column", json_mknumber((double)(e.pstate.column + 1))); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 1; - c_ctx->error_file = sass_copy_c_string(e.pstate.path); - c_ctx->error_line = e.pstate.line + 1; - c_ctx->error_column = e.pstate.column + 1; - c_ctx->error_src = e.pstate.src; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::bad_alloc& ba) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(2)); - json_append_member(json_err, "message", json_mkstring(ba.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(ba.what()); - c_ctx->error_status = 2; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::exception& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(3)); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 3; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::string& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e.c_str())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.c_str()); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (const char* e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e)); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (...) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unknown error occurred" << std::endl; - json_append_member(json_err, "status", json_mknumber(5)); - json_append_member(json_err, "message", json_mkstring("unknown")); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string("unknown"); - c_ctx->error_status = 5; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - return c_ctx->error_status; - } - - // allow one error handler to throw another error - // this can happen with invalid utf8 and json lib - static int handle_errors(Sass_Context* c_ctx) { - try { return handle_error(c_ctx); } - catch (...) { return handle_error(c_ctx); } - } - - static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() - { - - // assert valid pointer - if (compiler == 0) return 0; - // The cpp context must be set by now - Context* cpp_ctx = compiler->cpp_ctx; - Sass_Context* c_ctx = compiler->c_ctx; - // We will take care to wire up the rest - compiler->cpp_ctx->c_compiler = compiler; - compiler->state = SASS_COMPILER_PARSED; - - try { - - // get input/output path from options - std::string input_path = safe_str(c_ctx->input_path); - std::string output_path = safe_str(c_ctx->output_path); - - // maybe skip some entries of included files - // we do not include stdin for data contexts - bool skip = c_ctx->type == SASS_CONTEXT_DATA; - - // dispatch parse call - Block_Obj root(cpp_ctx->parse()); - // abort on errors - if (!root) return 0; - - // skip all prefixed files? (ToDo: check srcmap) - // IMO source-maps should point to headers already - // therefore don't skip it for now. re-enable or - // remove completely once this is tested - size_t headers = cpp_ctx->head_imports; - - // copy the included files on to the context (dont forget to free later) - if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) - throw(std::bad_alloc()); - - // return parsed block - return root; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return 0; - - } - -} - -extern "C" { - using namespace Sass; - - static void sass_clear_options (struct Sass_Options* options); - static void sass_reset_options (struct Sass_Options* options); - static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { - // do not overwrite ourself - if (to == from) return; - // free assigned memory - sass_clear_options(to); - // move memory - *to = *from; - // Reset pointers on source - sass_reset_options(from); - } - - #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } - #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } - #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ - { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } - #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) - - #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ - type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } - #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ - type sass_context_take_##option (struct Sass_Context* ctx) \ - { type foo = ctx->option; ctx->option = 0; return foo; } - - - // generic compilation function (not exported, use file/data compile instead) - static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() - { - try { - // register our custom functions - if (c_ctx->c_functions) { - auto this_func_data = c_ctx->c_functions; - while (this_func_data && *this_func_data) { - cpp_ctx->add_c_function(*this_func_data); - ++this_func_data; - } - } - - // register our custom headers - if (c_ctx->c_headers) { - auto this_head_data = c_ctx->c_headers; - while (this_head_data && *this_head_data) { - cpp_ctx->add_c_header(*this_head_data); - ++this_head_data; - } - } - - // register our custom importers - if (c_ctx->c_importers) { - auto this_imp_data = c_ctx->c_importers; - while (this_imp_data && *this_imp_data) { - cpp_ctx->add_c_importer(*this_imp_data); - ++this_imp_data; - } - } - - // reset error status - c_ctx->error_json = 0; - c_ctx->error_text = 0; - c_ctx->error_message = 0; - c_ctx->error_status = 0; - // reset error position - c_ctx->error_src = 0; - c_ctx->error_file = 0; - c_ctx->error_line = std::string::npos; - c_ctx->error_column = std::string::npos; - - // allocate a new compiler instance - void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); - if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } - Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; - compiler->state = SASS_COMPILER_CREATED; - - // store in sass compiler - compiler->c_ctx = c_ctx; - compiler->cpp_ctx = cpp_ctx; - cpp_ctx->c_compiler = compiler; - - // use to parse block - return compiler; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return 0; - - } - - // generic compilation function (not exported, use file/data compile instead) - static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) - { - - // prepare sass compiler with context and options - Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); - - try { - // call each compiler step - sass_compiler_parse(compiler); - sass_compiler_execute(compiler); - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - sass_delete_compiler(compiler); - - return c_ctx->error_status; - } - - inline void init_options (struct Sass_Options* options) - { - options->precision = 5; - options->indent = " "; - options->linefeed = LFEED; - } - - Sass_Options* ADDCALL sass_make_options (void) - { - struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); - if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } - init_options(options); - return options; - } - - Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) - { - SharedObj::setTaint(true); // needed for static colors - struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_FILE; - init_options(ctx); - try { - if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } - if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } - sass_option_set_input_path(ctx, input_path); - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) - { - struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_DATA; - init_options(ctx); - try { - if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } - if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } - ctx->source_string = source_string; - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 0; - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_prepare_context(data_ctx, cpp_ctx); - } - - struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 0; - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_prepare_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 1; - if (data_ctx->error_status) - return data_ctx->error_status; - try { - if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } - // empty source string is a valid case, even if not really usefull (different than with file context) - // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } - } - catch (...) { return handle_errors(data_ctx) | 1; } - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_compile_context(data_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 1; - if (file_ctx->error_status) - return file_ctx->error_status; - try { - if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } - if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } - } - catch (...) { return handle_errors(file_ctx) | 1; } - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_compile_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_PARSED) return 0; - if (compiler->state != SASS_COMPILER_CREATED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - // parse the context we have set up (file or data) - compiler->root = sass_parse_block(compiler); - // success - return 0; - } - - int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_EXECUTED) return 0; - if (compiler->state != SASS_COMPILER_PARSED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->root.isNull()) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - compiler->state = SASS_COMPILER_EXECUTED; - Context* cpp_ctx = compiler->cpp_ctx; - Block_Obj root = compiler->root; - // compile the parsed root block - try { compiler->c_ctx->output_string = cpp_ctx->render(root); } - // pass catched errors to generic error handler - catch (...) { return handle_errors(compiler->c_ctx) | 1; } - // generate source map json and store on context - compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); - // success - return 0; - } - - // helper function, not exported, only accessible locally - static void sass_reset_options (struct Sass_Options* options) - { - // free pointer before - // or copy/move them - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - options->extensions = 0; - } - - // helper function, not exported, only accessible locally - static void sass_clear_options (struct Sass_Options* options) - { - if (options == 0) return; - // Deallocate custom functions, headers and importes - sass_delete_function_list(options->c_functions); - sass_delete_importer_list(options->c_importers); - sass_delete_importer_list(options->c_headers); - // Deallocate inc paths - if (options->plugin_paths) { - struct string_list* cur; - struct string_list* next; - cur = options->plugin_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Deallocate inc paths - if (options->include_paths) { - struct string_list* cur; - struct string_list* next; - cur = options->include_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Deallocate extension - if (options->extensions) { - struct string_list* cur; - struct string_list* next; - cur = options->extensions; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Free options strings - free(options->input_path); - free(options->output_path); - free(options->plugin_path); - free(options->include_path); - free(options->source_map_file); - free(options->source_map_root); - // Reset our pointers - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - options->extensions = 0; - } - - // helper function, not exported, only accessible locally - // sass_free_context is also defined in old sass_interface - static void sass_clear_context (struct Sass_Context* ctx) - { - if (ctx == 0) return; - // release the allocated memory (mostly via sass_copy_c_string) - if (ctx->output_string) free(ctx->output_string); - if (ctx->source_map_string) free(ctx->source_map_string); - if (ctx->error_message) free(ctx->error_message); - if (ctx->error_text) free(ctx->error_text); - if (ctx->error_json) free(ctx->error_json); - if (ctx->error_file) free(ctx->error_file); - free_string_array(ctx->included_files); - // play safe and reset properties - ctx->output_string = 0; - ctx->source_map_string = 0; - ctx->error_message = 0; - ctx->error_text = 0; - ctx->error_json = 0; - ctx->error_file = 0; - ctx->included_files = 0; - // debug leaked memory - #ifdef DEBUG_SHARED_PTR - SharedObj::dumpMemLeaks(); - #endif - // now clear the options - sass_clear_options(ctx); - } - - void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) - { - if (compiler == 0) { - return; - } - Context* cpp_ctx = compiler->cpp_ctx; - if (cpp_ctx) delete(cpp_ctx); - compiler->cpp_ctx = NULL; - compiler->c_ctx = NULL; - compiler->root = NULL; - free(compiler); - } - - void ADDCALL sass_delete_options (struct Sass_Options* options) - { - sass_clear_options(options); free(options); - } - - // Deallocate all associated memory with file context - void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) - { - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - // Deallocate all associated memory with data context - void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) - { - // clean the source string if it was not passed - // we reset this member once we start parsing - if (ctx->source_string) free(ctx->source_string); - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - - // Getters for sass context from specific implementations - struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } - - // Getters for context options from Sass_Context - struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } - void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - - // Getters for Sass_Compiler options (get conected sass context) - enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } - struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - // Getters for Sass_Compiler options (query import stack) - size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } - Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } - Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } - // Getters for Sass_Compiler options (query function stack) - size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } - - // Calculate the size of the stored null terminated array - size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) - { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } - - // Create getter and setters for options - IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); - IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); - - // Create getter and setters for context - IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); - - // Take ownership of memory (value on context is set to 0) - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); - - // Push function for import extenions - void ADDCALL sass_option_push_import_extension(struct Sass_Options* options, const char* ext) - { - struct string_list* extension = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (extension == 0) return; - extension->string = ext ? sass_copy_c_string(ext) : 0; - struct string_list* last = options->extensions; - if (!options->extensions) { - options->extensions = extension; - } else { - while (last->next) - last = last->next; - last->next = extension; - } - } - - // Push function for include paths (no manipulation support for now) - void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) - { - - struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (include_path == 0) return; - include_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->include_paths; - if (!options->include_paths) { - options->include_paths = include_path; - } else { - while (last->next) - last = last->next; - last->next = include_path; - } - - } - - // Push function for include paths (no manipulation support for now) - size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) - { - size_t len = 0; - struct string_list* cur = options->include_paths; - while (cur) { len ++; cur = cur->next; } - return len; - } - - // Push function for include paths (no manipulation support for now) - const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) - { - struct string_list* cur = options->include_paths; - while (i) { i--; cur = cur->next; } - return cur->string; - } - - // Push function for plugin paths (no manipulation support for now) - void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) - { - - struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (plugin_path == 0) return; - plugin_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->plugin_paths; - if (!options->plugin_paths) { - options->plugin_paths = plugin_path; - } else { - while (last->next) - last = last->next; - last->next = plugin_path; - } - - } - -} diff --git a/src/libsass/src/sass_context.hpp b/src/libsass/src/sass_context.hpp deleted file mode 100644 index 9d192a301..000000000 --- a/src/libsass/src/sass_context.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef SASS_SASS_CONTEXT_H -#define SASS_SASS_CONTEXT_H - -#include "sass/base.h" -#include "sass/context.h" -#include "ast_fwd_decl.hpp" - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* extension; - char* include_path; - char* plugin_path; - - // Extensions (linked string list) - struct string_list* extensions; - // Include paths (linked string list) - struct string_list* include_paths; - // Plugin paths (linked string list) - struct string_list* plugin_paths; - - // Path to source map file - // Enables source map generation - // Used to create sourceMappingUrl - char* source_map_file; - - // Directly inserted in source maps - char* source_map_root; - - // Custom functions that can be called from sccs code - Sass_Function_List c_functions; - - // List of custom importers - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - const char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; - -#endif diff --git a/src/libsass/src/sass_functions.cpp b/src/libsass/src/sass_functions.cpp deleted file mode 100644 index bfbf25838..000000000 --- a/src/libsass/src/sass_functions.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include "sass.hpp" -#include -#include "util.hpp" -#include "context.hpp" -#include "values.hpp" -#include "sass/functions.h" -#include "sass_functions.hpp" - -extern "C" { - using namespace Sass; - - Sass_Function_List ADDCALL sass_make_function_list(size_t length) - { - return (Sass_Function_List) calloc(length + 1, sizeof(Sass_Function_Entry)); - } - - Sass_Function_Entry ADDCALL sass_make_function(const char* signature, Sass_Function_Fn function, void* cookie) - { - Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); - if (cb == 0) return 0; - cb->signature = sass_copy_c_string(signature); - cb->function = function; - cb->cookie = cookie; - return cb; - } - - void ADDCALL sass_delete_function(Sass_Function_Entry entry) - { - free(entry->signature); - free(entry); - } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_function_list(Sass_Function_List list) - { - Sass_Function_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_function(*list); - ++list; - } - free(it); - } - - // Setters and getters for callbacks on function lists - Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; } - void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; } - - const char* ADDCALL sass_function_get_signature(Sass_Function_Entry cb) { return cb->signature; } - Sass_Function_Fn ADDCALL sass_function_get_function(Sass_Function_Entry cb) { return cb->function; } - void* ADDCALL sass_function_get_cookie(Sass_Function_Entry cb) { return cb->cookie; } - - Sass_Importer_Entry ADDCALL sass_make_importer(Sass_Importer_Fn importer, double priority, void* cookie) - { - Sass_Importer_Entry cb = (Sass_Importer_Entry) calloc(1, sizeof(Sass_Importer)); - if (cb == 0) return 0; - cb->importer = importer; - cb->priority = priority; - cb->cookie = cookie; - return cb; - } - - Sass_Importer_Fn ADDCALL sass_importer_get_function(Sass_Importer_Entry cb) { return cb->importer; } - double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb) { return cb->priority; } - void* ADDCALL sass_importer_get_cookie(Sass_Importer_Entry cb) { return cb->cookie; } - - // Just in case we have some stray import structs - void ADDCALL sass_delete_importer (Sass_Importer_Entry cb) - { - free(cb); - } - - // Creator for sass custom importer function list - Sass_Importer_List ADDCALL sass_make_importer_list(size_t length) - { - return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry)); - } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_importer_list(Sass_Importer_List list) - { - Sass_Importer_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_importer(*list); - ++list; - } - free(it); - } - - Sass_Importer_Entry ADDCALL sass_importer_get_list_entry(Sass_Importer_List list, size_t idx) { return list[idx]; } - void ADDCALL sass_importer_set_list_entry(Sass_Importer_List list, size_t idx, Sass_Importer_Entry cb) { list[idx] = cb; } - - // Creator for sass custom importer return argument list - Sass_Import_List ADDCALL sass_make_import_list(size_t length) - { - return (Sass_Import**) calloc(length + 1, sizeof(Sass_Import*)); - } - - // Creator for a single import entry returned by the custom importer inside the list - // We take ownership of the memory for source and srcmap (freed when context is destroyd) - Sass_Import_Entry ADDCALL sass_make_import(const char* imp_path, const char* abs_path, char* source, char* srcmap) - { - Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import)); - if (v == 0) return 0; - v->imp_path = imp_path ? sass_copy_c_string(imp_path) : 0; - v->abs_path = abs_path ? sass_copy_c_string(abs_path) : 0; - v->source = source; - v->srcmap = srcmap; - v->error = 0; - v->line = -1; - v->column = -1; - return v; - } - - // Older style, but somehow still valid - keep around or deprecate? - Sass_Import_Entry ADDCALL sass_make_import_entry(const char* path, char* source, char* srcmap) - { - return sass_make_import(path, path, source, srcmap); - } - - // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting) - Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* error, size_t line, size_t col) - { - if (import == 0) return 0; - if (import->error) free(import->error); - import->error = error ? sass_copy_c_string(error) : 0; - import->line = line ? line : -1; - import->column = col ? col : -1; - return import; - } - - // Setters and getters for entries on the import list - void ADDCALL sass_import_set_list_entry(Sass_Import_List list, size_t idx, Sass_Import_Entry entry) { list[idx] = entry; } - Sass_Import_Entry ADDCALL sass_import_get_list_entry(Sass_Import_List list, size_t idx) { return list[idx]; } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_import_list(Sass_Import_List list) - { - Sass_Import_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_import(*list); - ++list; - } - free(it); - } - - // Just in case we have some stray import structs - void ADDCALL sass_delete_import(Sass_Import_Entry import) - { - free(import->imp_path); - free(import->abs_path); - free(import->source); - free(import->srcmap); - free(import->error); - free(import); - } - - // Getter for callee entry - const char* ADDCALL sass_callee_get_name(Sass_Callee_Entry entry) { return entry->name; } - const char* ADDCALL sass_callee_get_path(Sass_Callee_Entry entry) { return entry->path; } - size_t ADDCALL sass_callee_get_line(Sass_Callee_Entry entry) { return entry->line; } - size_t ADDCALL sass_callee_get_column(Sass_Callee_Entry entry) { return entry->column; } - enum Sass_Callee_Type ADDCALL sass_callee_get_type(Sass_Callee_Entry entry) { return entry->type; } - Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry entry) { return &entry->env; } - - // Getters and Setters for environments (lexical, local and global) - union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame env, const char* name) { - Expression_Ptr ex = Cast((*env->frame)[name]); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_lexical (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - (*env->frame)[name] = sass_value_to_ast_node(val); - } - union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame env, const char* name) { - Expression_Ptr ex = Cast(env->frame->get_local(name)); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_local (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - env->frame->set_local(name, sass_value_to_ast_node(val)); - } - union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame env, const char* name) { - Expression_Ptr ex = Cast(env->frame->get_global(name)); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_global (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - env->frame->set_global(name, sass_value_to_ast_node(val)); - } - - // Getter for import entry - const char* ADDCALL sass_import_get_imp_path(Sass_Import_Entry entry) { return entry->imp_path; } - const char* ADDCALL sass_import_get_abs_path(Sass_Import_Entry entry) { return entry->abs_path; } - const char* ADDCALL sass_import_get_source(Sass_Import_Entry entry) { return entry->source; } - const char* ADDCALL sass_import_get_srcmap(Sass_Import_Entry entry) { return entry->srcmap; } - - // Getter for import error entry - size_t ADDCALL sass_import_get_error_line(Sass_Import_Entry entry) { return entry->line; } - size_t ADDCALL sass_import_get_error_column(Sass_Import_Entry entry) { return entry->column; } - const char* ADDCALL sass_import_get_error_message(Sass_Import_Entry entry) { return entry->error; } - - // Explicit functions to take ownership of the memory - // Resets our own property since we do not know if it is still alive - char* ADDCALL sass_import_take_source(Sass_Import_Entry entry) { char* ptr = entry->source; entry->source = 0; return ptr; } - char* ADDCALL sass_import_take_srcmap(Sass_Import_Entry entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; } - -} diff --git a/src/libsass/src/sass_functions.hpp b/src/libsass/src/sass_functions.hpp deleted file mode 100644 index 3b646d67e..000000000 --- a/src/libsass/src/sass_functions.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef SASS_SASS_FUNCTIONS_H -#define SASS_SASS_FUNCTIONS_H - -#include "sass.h" -#include "environment.hpp" -#include "functions.hpp" - -// Struct to hold custom function callback -struct Sass_Function { - char* signature; - Sass_Function_Fn function; - void* cookie; -}; - -// External import entry -struct Sass_Import { - char* imp_path; // path as found in the import statement - char *abs_path; // path after importer has resolved it - char* source; - char* srcmap; - // error handling - char* error; - size_t line; - size_t column; -}; - -// External environments -struct Sass_Env { - // links to parent frames - Sass::Env* frame; -}; - -// External call entry -struct Sass_Callee { - const char* name; - const char* path; - size_t line; - size_t column; - enum Sass_Callee_Type type; - struct Sass_Env env; -}; - -// Struct to hold importer callback -struct Sass_Importer { - Sass_Importer_Fn importer; - double priority; - void* cookie; -}; - -#endif \ No newline at end of file diff --git a/src/libsass/src/sass_util.cpp b/src/libsass/src/sass_util.cpp deleted file mode 100644 index 3aef2bc72..000000000 --- a/src/libsass/src/sass_util.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "sass.hpp" -#include "node.hpp" - -namespace Sass { - - - /* - # This is the equivalent of ruby's Sass::Util.paths. - # - # Return an array of all possible paths through the given arrays. - # - # @param arrs [NodeCollection>] - # @return [NodeCollection>] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def paths(arrs) - // I changed the inject and maps to an iterative approach to make it easier to implement in C++ - loopStart = [[]] - - for arr in arrs do - permutations = [] - for e in arr do - for path in loopStart do - permutations.push(path + [e]) - end - end - loopStart = permutations - end - end - */ - Node paths(const Node& arrs) { - - Node loopStart = Node::createCollection(); - loopStart.collection()->push_back(Node::createCollection()); - - for (NodeDeque::iterator arrsIter = arrs.collection()->begin(), arrsEndIter = arrs.collection()->end(); - arrsIter != arrsEndIter; ++arrsIter) { - - Node& arr = *arrsIter; - - Node permutations = Node::createCollection(); - - for (NodeDeque::iterator arrIter = arr.collection()->begin(), arrIterEnd = arr.collection()->end(); - arrIter != arrIterEnd; ++arrIter) { - - Node& e = *arrIter; - - for (NodeDeque::iterator loopStartIter = loopStart.collection()->begin(), loopStartIterEnd = loopStart.collection()->end(); - loopStartIter != loopStartIterEnd; ++loopStartIter) { - - Node& path = *loopStartIter; - - Node newPermutation = Node::createCollection(); - newPermutation.got_line_feed = arr.got_line_feed; - newPermutation.plus(path); - newPermutation.collection()->push_back(e); - - permutations.collection()->push_back(newPermutation); - } - } - - loopStart = permutations; - } - - return loopStart; - } - - - /* - This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. - Sass::Util.flatten requires the number of levels to flatten, while - [].flatten doesn't and will flatten the entire array. This function - supports both. - - # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened - # - # @param arr [NodeCollection] The array to flatten - # @param n [int] The number of levels to flatten - # @return [NodeCollection] The flattened array - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def flatten(arr, n = -1) - if n != -1 and n == 0 then - return arr - end - - flattened = [] - - for e in arr do - if e.is_a?(Array) then - flattened.concat(flatten(e, n - 1)) - else - flattened << e - end - end - - return flattened - end - */ - Node flatten(Node& arr, int n) { - if (n != -1 && n == 0) { - return arr; - } - - Node flattened = Node::createCollection(); - if (arr.got_line_feed) flattened.got_line_feed = true; - - for (NodeDeque::iterator iter = arr.collection()->begin(), iterEnd = arr.collection()->end(); - iter != iterEnd; iter++) { - Node& e = *iter; - - // e has the lf set - if (e.isCollection()) { - - // e.collection().got_line_feed = e.got_line_feed; - Node recurseFlattened = flatten(e, n - 1); - - if(e.got_line_feed) { - flattened.got_line_feed = e.got_line_feed; - recurseFlattened.got_line_feed = e.got_line_feed; - } - - for(auto i : (*recurseFlattened.collection())) { - if (recurseFlattened.got_line_feed) { - - i.got_line_feed = true; - } - flattened.collection()->push_back(i); - } - - } else { - flattened.collection()->push_back(e); - } - } - - return flattened; - } -} diff --git a/src/libsass/src/sass_util.hpp b/src/libsass/src/sass_util.hpp deleted file mode 100644 index 816da5fd8..000000000 --- a/src/libsass/src/sass_util.hpp +++ /dev/null @@ -1,256 +0,0 @@ -#ifndef SASS_SASS_UTIL_H -#define SASS_SASS_UTIL_H - -#include "ast.hpp" -#include "node.hpp" -#include "debug.hpp" - -namespace Sass { - - - - - /* - This is for ports of functions in the Sass:Util module. - */ - - - /* - # Return a Node collection of all possible paths through the given Node collection of Node collections. - # - # @param arrs [NodeCollection>] - # @return [NodeCollection>] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - */ - Node paths(const Node& arrs); - - - /* - This class is a default implementation of a Node comparator that can be passed to the lcs function below. - It uses operator== for equality comparision. It then returns one if the Nodes are equal. - */ - class DefaultLcsComparator { - public: - bool operator()(const Node& one, const Node& two, Node& out) const { - // TODO: Is this the correct C++ interpretation? - // block ||= proc {|a, b| a == b && a} - if (one == two) { - out = one; - return true; - } - - return false; - } - }; - - - typedef std::vector > LCSTable; - - - /* - This is the equivalent of ruby's Sass::Util.lcs_backtrace. - - # Computes a single longest common subsequence for arrays x and y. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS - */ - template - Node lcs_backtrace(const LCSTable& c, const Node& x, const Node& y, int i, int j, const ComparatorType& comparator) { - DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) - - if (i == 0 || j == 0) { - DEBUG_PRINTLN(LCS, "RETURNING EMPTY") - return Node::createCollection(); - } - - NodeDeque& xChildren = *(x.collection()); - NodeDeque& yChildren = *(y.collection()); - - Node compareOut = Node::createNil(); - if (comparator(xChildren[i], yChildren[j], compareOut)) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") - Node result = lcs_backtrace(c, x, y, i - 1, j - 1, comparator); - result.collection()->push_back(compareOut); - return result; - } - - if (c[i][j - 1] > c[i - 1][j]) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") - return lcs_backtrace(c, x, y, i, j - 1, comparator); - } - - DEBUG_PRINTLN(LCS, "FINAL RETURN") - return lcs_backtrace(c, x, y, i - 1, j, comparator); - } - - - /* - This is the equivalent of ruby's Sass::Util.lcs_table. - - # Calculates the memoization table for the Least Common Subsequence algorithm. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS - */ - template - void lcs_table(const Node& x, const Node& y, const ComparatorType& comparator, LCSTable& out) { - DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) - - NodeDeque& xChildren = *(x.collection()); - NodeDeque& yChildren = *(y.collection()); - - LCSTable c(xChildren.size(), std::vector(yChildren.size())); - - // These shouldn't be necessary since the vector will be initialized to 0 already. - // x.size.times {|i| c[i][0] = 0} - // y.size.times {|j| c[0][j] = 0} - - for (size_t i = 1; i < xChildren.size(); i++) { - for (size_t j = 1; j < yChildren.size(); j++) { - Node compareOut = Node::createNil(); - - if (comparator(xChildren[i], yChildren[j], compareOut)) { - c[i][j] = c[i - 1][j - 1] + 1; - } else { - c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); - } - } - } - - out = c; - } - - - /* - This is the equivalent of ruby's Sass::Util.lcs. - - # Computes a single longest common subsequence for `x` and `y`. - # If there are more than one longest common subsequences, - # the one returned is that which starts first in `x`. - - # @param x [NodeCollection] - # @param y [NodeCollection] - # @comparator An equality check between elements of `x` and `y`. - # @return [NodeCollection] The LCS - - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - */ - template - Node lcs(Node& x, Node& y, const ComparatorType& comparator) { - DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) - - Node newX = Node::createCollection(); - newX.collection()->push_back(Node::createNil()); - newX.plus(x); - - Node newY = Node::createCollection(); - newY.collection()->push_back(Node::createNil()); - newY.plus(y); - - LCSTable table; - lcs_table(newX, newY, comparator, table); - - return lcs_backtrace(table, newX, newY, static_cast(newX.collection()->size()) - 1, static_cast(newY.collection()->size()) - 1, comparator); - } - - - /* - This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. - Sass::Util.flatten requires the number of levels to flatten, while - [].flatten doesn't and will flatten the entire array. This function - supports both. - - # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened - # - # @param arr [NodeCollection] The array to flatten - # @param n [int] The number of levels to flatten - # @return [NodeCollection] The flattened array - */ - Node flatten(Node& arr, int n = -1); - - - /* - This is the equivalent of ruby's Sass::Util.group_by_to_a. - - # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed - # order. Unlike [#hash_to_a], the resulting order isn't sorted key order; - # instead, it's the same order as `#group_by` has under Ruby 1.9 (key - # appearance order). - # - # @param enum [Enumerable] - # @return [Array<[Object, Array]>] An array of pairs. - - TODO: update @param and @return once I know what those are. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def group_by_to_a(enum, &block) - order = {} - - arr = [] - - grouped = {} - - for e in enum do - key = block[e] - unless order.include?(key) - order[key] = order.size - end - - if not grouped.has_key?(key) then - grouped[key] = [e] - else - grouped[key].push(e) - end - end - - grouped.each do |key, vals| - arr[order[key]] = [key, vals] - end - - arr - end - - */ - template - void group_by_to_a(std::vector& enumeration, KeyFunctorType& keyFunc, std::vector > >& arr /*out*/) { - - std::map order; - - std::map > grouped; - - for (typename std::vector::iterator enumIter = enumeration.begin(), enumIterEnd = enumeration.end(); enumIter != enumIterEnd; enumIter++) { - EnumType& e = *enumIter; - - KeyType key = keyFunc(e); - - if (grouped.find(key->hash()) == grouped.end()) { - order.insert(std::make_pair((unsigned int)order.size(), key)); - - std::vector newCollection; - newCollection.push_back(e); - grouped.insert(std::make_pair(key->hash(), newCollection)); - } else { - std::vector& collection = grouped.at(key->hash()); - collection.push_back(e); - } - } - - for (unsigned int index = 0; index < order.size(); index++) { - KeyType& key = order.at(index); - std::vector& values = grouped.at(key->hash()); - - std::pair > grouping = std::make_pair(key, values); - - arr.push_back(grouping); - } - } - - -} - -#endif diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp deleted file mode 100644 index 34c591a24..000000000 --- a/src/libsass/src/sass_values.cpp +++ /dev/null @@ -1,357 +0,0 @@ -#include "sass.hpp" -#include -#include -#include "util.hpp" -#include "eval.hpp" -#include "values.hpp" -#include "operators.hpp" -#include "sass/values.h" -#include "sass_values.hpp" - -extern "C" { - using namespace Sass; - - // Return the sass tag for a generic sass value - enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; } - - // Check value for specified type - bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; } - bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; } - bool ADDCALL sass_value_is_string(const union Sass_Value* v) { return v->unknown.tag == SASS_STRING; } - bool ADDCALL sass_value_is_boolean(const union Sass_Value* v) { return v->unknown.tag == SASS_BOOLEAN; } - bool ADDCALL sass_value_is_color(const union Sass_Value* v) { return v->unknown.tag == SASS_COLOR; } - bool ADDCALL sass_value_is_list(const union Sass_Value* v) { return v->unknown.tag == SASS_LIST; } - bool ADDCALL sass_value_is_map(const union Sass_Value* v) { return v->unknown.tag == SASS_MAP; } - bool ADDCALL sass_value_is_error(const union Sass_Value* v) { return v->unknown.tag == SASS_ERROR; } - bool ADDCALL sass_value_is_warning(const union Sass_Value* v) { return v->unknown.tag == SASS_WARNING; } - - // Getters and setters for Sass_Number - double ADDCALL sass_number_get_value(const union Sass_Value* v) { return v->number.value; } - void ADDCALL sass_number_set_value(union Sass_Value* v, double value) { v->number.value = value; } - const char* ADDCALL sass_number_get_unit(const union Sass_Value* v) { return v->number.unit; } - void ADDCALL sass_number_set_unit(union Sass_Value* v, char* unit) { v->number.unit = unit; } - - // Getters and setters for Sass_String - const char* ADDCALL sass_string_get_value(const union Sass_Value* v) { return v->string.value; } - void ADDCALL sass_string_set_value(union Sass_Value* v, char* value) { v->string.value = value; } - bool ADDCALL sass_string_is_quoted(const union Sass_Value* v) { return v->string.quoted; } - void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted) { v->string.quoted = quoted; } - - // Getters and setters for Sass_Boolean - bool ADDCALL sass_boolean_get_value(const union Sass_Value* v) { return v->boolean.value; } - void ADDCALL sass_boolean_set_value(union Sass_Value* v, bool value) { v->boolean.value = value; } - - // Getters and setters for Sass_Color - double ADDCALL sass_color_get_r(const union Sass_Value* v) { return v->color.r; } - void ADDCALL sass_color_set_r(union Sass_Value* v, double r) { v->color.r = r; } - double ADDCALL sass_color_get_g(const union Sass_Value* v) { return v->color.g; } - void ADDCALL sass_color_set_g(union Sass_Value* v, double g) { v->color.g = g; } - double ADDCALL sass_color_get_b(const union Sass_Value* v) { return v->color.b; } - void ADDCALL sass_color_set_b(union Sass_Value* v, double b) { v->color.b = b; } - double ADDCALL sass_color_get_a(const union Sass_Value* v) { return v->color.a; } - void ADDCALL sass_color_set_a(union Sass_Value* v, double a) { v->color.a = a; } - - // Getters and setters for Sass_List - size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; } - enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; } - void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; } - bool ADDCALL sass_list_get_is_bracketed(const union Sass_Value* v) { return v->list.is_bracketed; } - void ADDCALL sass_list_set_is_bracketed(union Sass_Value* v, bool is_bracketed) { v->list.is_bracketed = is_bracketed; } - // Getters and setters for Sass_List values - union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; } - void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; } - - // Getters and setters for Sass_Map - size_t ADDCALL sass_map_get_length(const union Sass_Value* v) { return v->map.length; } - // Getters and setters for Sass_List keys and values - union Sass_Value* ADDCALL sass_map_get_key(const union Sass_Value* v, size_t i) { return v->map.pairs[i].key; } - union Sass_Value* ADDCALL sass_map_get_value(const union Sass_Value* v, size_t i) { return v->map.pairs[i].value; } - void ADDCALL sass_map_set_key(union Sass_Value* v, size_t i, union Sass_Value* key) { v->map.pairs[i].key = key; } - void ADDCALL sass_map_set_value(union Sass_Value* v, size_t i, union Sass_Value* val) { v->map.pairs[i].value = val; } - - // Getters and setters for Sass_Error - char* ADDCALL sass_error_get_message(const union Sass_Value* v) { return v->error.message; }; - void ADDCALL sass_error_set_message(union Sass_Value* v, char* msg) { v->error.message = msg; }; - - // Getters and setters for Sass_Warning - char* ADDCALL sass_warning_get_message(const union Sass_Value* v) { return v->warning.message; }; - void ADDCALL sass_warning_set_message(union Sass_Value* v, char* msg) { v->warning.message = msg; }; - - // Creator functions for all value types - - union Sass_Value* ADDCALL sass_make_boolean(bool val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->boolean.tag = SASS_BOOLEAN; - v->boolean.value = val; - return v; - } - - union Sass_Value* ADDCALL sass_make_number(double val, const char* unit) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->number.tag = SASS_NUMBER; - v->number.value = val; - v->number.unit = unit ? sass_copy_c_string(unit) : 0; - if (v->number.unit == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_color(double r, double g, double b, double a) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->color.tag = SASS_COLOR; - v->color.r = r; - v->color.g = g; - v->color.b = b; - v->color.a = a; - return v; - } - - union Sass_Value* ADDCALL sass_make_string(const char* val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->string.quoted = false; - v->string.tag = SASS_STRING; - v->string.value = val ? sass_copy_c_string(val) : 0; - if (v->string.value == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_qstring(const char* val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->string.quoted = true; - v->string.tag = SASS_STRING; - v->string.value = val ? sass_copy_c_string(val) : 0; - if (v->string.value == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep, bool is_bracketed) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->list.tag = SASS_LIST; - v->list.length = len; - v->list.separator = sep; - v->list.is_bracketed = is_bracketed; - v->list.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value*)); - if (v->list.values == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_map(size_t len) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->map.tag = SASS_MAP; - v->map.length = len; - v->map.pairs = (struct Sass_MapPair*) calloc(len, sizeof(struct Sass_MapPair)); - if (v->map.pairs == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_null(void) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->null.tag = SASS_NULL; - return v; - } - - union Sass_Value* ADDCALL sass_make_error(const char* msg) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->error.tag = SASS_ERROR; - v->error.message = msg ? sass_copy_c_string(msg) : 0; - if (v->error.message == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_warning(const char* msg) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->warning.tag = SASS_WARNING; - v->warning.message = msg ? sass_copy_c_string(msg) : 0; - if (v->warning.message == 0) { free(v); return 0; } - return v; - } - - // will free all associated sass values - void ADDCALL sass_delete_value(union Sass_Value* val) { - - size_t i; - if (val == 0) return; - switch(val->unknown.tag) { - case SASS_NULL: { - } break; - case SASS_BOOLEAN: { - } break; - case SASS_NUMBER: { - free(val->number.unit); - } break; - case SASS_COLOR: { - } break; - case SASS_STRING: { - free(val->string.value); - } break; - case SASS_LIST: { - for (i=0; ilist.length; i++) { - sass_delete_value(val->list.values[i]); - } - free(val->list.values); - } break; - case SASS_MAP: { - for (i=0; imap.length; i++) { - sass_delete_value(val->map.pairs[i].key); - sass_delete_value(val->map.pairs[i].value); - } - free(val->map.pairs); - } break; - case SASS_ERROR: { - free(val->error.message); - } break; - case SASS_WARNING: { - free(val->error.message); - } break; - default: break; - } - - free(val); - - } - - // Make a deep cloned copy of the given sass value - union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val) - { - - size_t i; - if (val == 0) return 0; - switch(val->unknown.tag) { - case SASS_NULL: { - return sass_make_null(); - } - case SASS_BOOLEAN: { - return sass_make_boolean(val->boolean.value); - } - case SASS_NUMBER: { - return sass_make_number(val->number.value, val->number.unit); - } - case SASS_COLOR: { - return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a); - } - case SASS_STRING: { - return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); - } - case SASS_LIST: { - union Sass_Value* list = sass_make_list(val->list.length, val->list.separator, val->list.is_bracketed); - for (i = 0; i < list->list.length; i++) { - list->list.values[i] = sass_clone_value(val->list.values[i]); - } - return list; - } - case SASS_MAP: { - union Sass_Value* map = sass_make_map(val->map.length); - for (i = 0; i < val->map.length; i++) { - map->map.pairs[i].key = sass_clone_value(val->map.pairs[i].key); - map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value); - } - return map; - } - case SASS_ERROR: { - return sass_make_error(val->error.message); - } - case SASS_WARNING: { - return sass_make_warning(val->warning.message); - } - default: break; - } - - return 0; - - } - - union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* v, bool compressed, int precision) - { - Value_Obj val = sass_value_to_ast_node(v); - Sass_Inspect_Options options(compressed ? COMPRESSED : NESTED, precision); - std::string str(val->to_string(options)); - return sass_make_qstring(str.c_str()); - } - - union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) - { - - Sass::Value_Ptr rv; - - try { - - Value_Obj lhs = sass_value_to_ast_node(a); - Value_Obj rhs = sass_value_to_ast_node(b); - struct Sass_Inspect_Options options(NESTED, 5); - - // see if it's a relational expression - switch(op) { - case Sass_OP::EQ: return sass_make_boolean(Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return sass_make_boolean(Operators::neq(lhs, rhs)); - case Sass_OP::GT: return sass_make_boolean(Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return sass_make_boolean(Operators::gte(lhs, rhs)); - case Sass_OP::LT: return sass_make_boolean(Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return sass_make_boolean(Operators::lte(lhs, rhs)); - case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); - case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); - default: break; - } - - if (sass_value_is_number(a) && sass_value_is_number(b)) { - Number_Ptr_Const l_n = Cast(lhs); - Number_Ptr_Const r_n = Cast(rhs); - rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); - } - else if (sass_value_is_number(a) && sass_value_is_color(a)) { - Number_Ptr_Const l_n = Cast(lhs); - Color_Ptr_Const r_c = Cast(rhs); - rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); - } - else if (sass_value_is_color(a) && sass_value_is_number(b)) { - Color_Ptr_Const l_c = Cast(lhs); - Number_Ptr_Const r_n = Cast(rhs); - rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); - } - else if (sass_value_is_color(a) && sass_value_is_color(b)) { - Color_Ptr_Const l_c = Cast(lhs); - Color_Ptr_Const r_c = Cast(rhs); - rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate()); - } - else /* convert other stuff to string and apply operation */ { - Value_Ptr l_v = Cast(lhs); - Value_Ptr r_v = Cast(rhs); - rv = Operators::op_strings(op, *l_v, *r_v, options, l_v->pstate()); - } - - // ToDo: maybe we should should return null value? - if (!rv) return sass_make_error("invalid return value"); - - // convert result back to ast node - return ast_node_to_sass_value(rv); - - } - - // simply pass the error message back to the caller for now - catch (Exception::InvalidSass& e) { return sass_make_error(e.what()); } - catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } - catch (std::exception& e) { return sass_make_error(e.what()); } - catch (std::string& e) { return sass_make_error(e.c_str()); } - catch (const char* e) { return sass_make_error(e); } - catch (...) { return sass_make_error("unknown"); } - } - -} diff --git a/src/libsass/src/sass_values.hpp b/src/libsass/src/sass_values.hpp deleted file mode 100644 index 9aa5cdb33..000000000 --- a/src/libsass/src/sass_values.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef SASS_SASS_VALUES_H -#define SASS_SASS_VALUES_H - -#include "sass.h" - -struct Sass_Unknown { - enum Sass_Tag tag; -}; - -struct Sass_Boolean { - enum Sass_Tag tag; - bool value; -}; - -struct Sass_Number { - enum Sass_Tag tag; - double value; - char* unit; -}; - -struct Sass_Color { - enum Sass_Tag tag; - double r; - double g; - double b; - double a; -}; - -struct Sass_String { - enum Sass_Tag tag; - bool quoted; - char* value; -}; - -struct Sass_List { - enum Sass_Tag tag; - enum Sass_Separator separator; - bool is_bracketed; - size_t length; - // null terminated "array" - union Sass_Value** values; -}; - -struct Sass_Map { - enum Sass_Tag tag; - size_t length; - struct Sass_MapPair* pairs; -}; - -struct Sass_Null { - enum Sass_Tag tag; -}; - -struct Sass_Error { - enum Sass_Tag tag; - char* message; -}; - -struct Sass_Warning { - enum Sass_Tag tag; - char* message; -}; - -union Sass_Value { - struct Sass_Unknown unknown; - struct Sass_Boolean boolean; - struct Sass_Number number; - struct Sass_Color color; - struct Sass_String string; - struct Sass_List list; - struct Sass_Map map; - struct Sass_Null null; - struct Sass_Error error; - struct Sass_Warning warning; -}; - -struct Sass_MapPair { - union Sass_Value* key; - union Sass_Value* value; -}; - -#endif diff --git a/src/libsass/src/source_map.cpp b/src/libsass/src/source_map.cpp deleted file mode 100644 index c171a3f68..000000000 --- a/src/libsass/src/source_map.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include - -#include "ast.hpp" -#include "json.hpp" -#include "context.hpp" -#include "position.hpp" -#include "source_map.hpp" - -namespace Sass { - SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin") { } - SourceMap::SourceMap(const std::string& file) : current_position(0, 0, 0), file(file) { } - - std::string SourceMap::render_srcmap(Context &ctx) { - - const bool include_sources = ctx.c_options.source_map_contents; - const std::vector links = ctx.srcmap_links; - const std::vector& sources(ctx.resources); - - JsonNode* json_srcmap = json_mkobject(); - - json_append_member(json_srcmap, "version", json_mknumber(3)); - - const char *file_name = file.c_str(); - JsonNode *json_file_name = json_mkstring(file_name); - json_append_member(json_srcmap, "file", json_file_name); - - // pass-through sourceRoot option - if (!ctx.source_map_root.empty()) { - JsonNode* root = json_mkstring(ctx.source_map_root.c_str()); - json_append_member(json_srcmap, "sourceRoot", root); - } - - JsonNode *json_sources = json_mkarray(); - for (size_t i = 0; i < source_index.size(); ++i) { - std::string source(links[source_index[i]]); - if (ctx.c_options.source_map_file_urls) { - source = File::rel2abs(source); - // check for windows abs path - if (source[0] == '/') { - // ends up with three slashes - source = "file://" + source; - } else { - // needs an additional slash - source = "file:///" + source; - } - } - const char* source_name = source.c_str(); - JsonNode *json_source_name = json_mkstring(source_name); - json_append_element(json_sources, json_source_name); - } - json_append_member(json_srcmap, "sources", json_sources); - - if (include_sources && source_index.size()) { - JsonNode *json_contents = json_mkarray(); - for (size_t i = 0; i < source_index.size(); ++i) { - const Resource& resource(sources[source_index[i]]); - JsonNode *json_content = json_mkstring(resource.contents); - json_append_element(json_contents, json_content); - } - json_append_member(json_srcmap, "sourcesContent", json_contents); - } - - JsonNode *json_names = json_mkarray(); - // so far we have no implementation for names - // no problem as we do not alter any identifiers - json_append_member(json_srcmap, "names", json_names); - - std::string mappings = serialize_mappings(); - JsonNode *json_mappings = json_mkstring(mappings.c_str()); - json_append_member(json_srcmap, "mappings", json_mappings); - - char *str = json_stringify(json_srcmap, "\t"); - std::string result = std::string(str); - free(str); - json_delete(json_srcmap); - return result; - } - - std::string SourceMap::serialize_mappings() { - std::string result = ""; - - size_t previous_generated_line = 0; - size_t previous_generated_column = 0; - size_t previous_original_line = 0; - size_t previous_original_column = 0; - size_t previous_original_file = 0; - for (size_t i = 0; i < mappings.size(); ++i) { - const size_t generated_line = mappings[i].generated_position.line; - const size_t generated_column = mappings[i].generated_position.column; - const size_t original_line = mappings[i].original_position.line; - const size_t original_column = mappings[i].original_position.column; - const size_t original_file = mappings[i].original_position.file; - - if (generated_line != previous_generated_line) { - previous_generated_column = 0; - if (generated_line > previous_generated_line) { - result += std::string(generated_line - previous_generated_line, ';'); - previous_generated_line = generated_line; - } - } - else if (i > 0) { - result += ","; - } - - // generated column - result += base64vlq.encode(static_cast(generated_column) - static_cast(previous_generated_column)); - previous_generated_column = generated_column; - // file - result += base64vlq.encode(static_cast(original_file) - static_cast(previous_original_file)); - previous_original_file = original_file; - // source line - result += base64vlq.encode(static_cast(original_line) - static_cast(previous_original_line)); - previous_original_line = original_line; - // source column - result += base64vlq.encode(static_cast(original_column) - static_cast(previous_original_column)); - previous_original_column = original_column; - } - - return result; - } - - void SourceMap::prepend(const OutputBuffer& out) - { - Offset size(out.smap.current_position); - for (Mapping mapping : out.smap.mappings) { - if (mapping.generated_position.line > size.line) { - throw(std::runtime_error("prepend sourcemap has illegal line")); - } - if (mapping.generated_position.line == size.line) { - if (mapping.generated_position.column > size.column) { - throw(std::runtime_error("prepend sourcemap has illegal column")); - } - } - } - // adjust the buffer offset - prepend(Offset(out.buffer)); - // now add the new mappings - VECTOR_UNSHIFT(mappings, out.smap.mappings); - } - - void SourceMap::append(const OutputBuffer& out) - { - append(Offset(out.buffer)); - } - - void SourceMap::prepend(const Offset& offset) - { - if (offset.line != 0 || offset.column != 0) { - for (Mapping& mapping : mappings) { - // move stuff on the first old line - if (mapping.generated_position.line == 0) { - mapping.generated_position.column += offset.column; - } - // make place for the new lines - mapping.generated_position.line += offset.line; - } - } - if (current_position.line == 0) { - current_position.column += offset.column; - } - current_position.line += offset.line; - } - - void SourceMap::append(const Offset& offset) - { - current_position += offset; - } - - void SourceMap::add_open_mapping(const AST_Node_Ptr node) - { - mappings.push_back(Mapping(node->pstate(), current_position)); - } - - void SourceMap::add_close_mapping(const AST_Node_Ptr node) - { - mappings.push_back(Mapping(node->pstate() + node->pstate().offset, current_position)); - } - - ParserState SourceMap::remap(const ParserState& pstate) { - for (size_t i = 0; i < mappings.size(); ++i) { - if ( - mappings[i].generated_position.file == pstate.file && - mappings[i].generated_position.line == pstate.line && - mappings[i].generated_position.column == pstate.column - ) return ParserState(pstate.path, pstate.src, mappings[i].original_position, pstate.offset); - } - return ParserState(pstate.path, pstate.src, Position(-1, -1, -1), Offset(0, 0)); - - } - -} diff --git a/src/libsass/src/source_map.hpp b/src/libsass/src/source_map.hpp deleted file mode 100644 index 07785640f..000000000 --- a/src/libsass/src/source_map.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef SASS_SOURCE_MAP_H -#define SASS_SOURCE_MAP_H - -#include -#include - -#include "ast_fwd_decl.hpp" -#include "base64vlq.hpp" -#include "position.hpp" -#include "mapping.hpp" - -#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end()) -#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end()) - -namespace Sass { - - class Context; - class OutputBuffer; - - class SourceMap { - - public: - std::vector source_index; - SourceMap(); - SourceMap(const std::string& file); - - void append(const Offset& offset); - void prepend(const Offset& offset); - void append(const OutputBuffer& out); - void prepend(const OutputBuffer& out); - void add_open_mapping(const AST_Node_Ptr node); - void add_close_mapping(const AST_Node_Ptr node); - - std::string render_srcmap(Context &ctx); - ParserState remap(const ParserState& pstate); - - private: - - std::string serialize_mappings(); - - std::vector mappings; - Position current_position; -public: - std::string file; -private: - Base64VLQ base64vlq; - }; - - class OutputBuffer { - public: - OutputBuffer(void) - : buffer(""), - smap() - { } - public: - std::string buffer; - SourceMap smap; - }; - -} - -#endif diff --git a/src/libsass/src/subset_map.cpp b/src/libsass/src/subset_map.cpp deleted file mode 100644 index 24513e498..000000000 --- a/src/libsass/src/subset_map.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "subset_map.hpp" - -namespace Sass { - - void Subset_Map::put(const Compound_Selector_Obj& sel, const SubSetMapPair& value) - { - if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty"); - size_t index = values_.size(); - values_.push_back(value); - for (size_t i = 0, S = sel->length(); i < S; ++i) - { - hash_[(*sel)[i]].push_back(std::make_pair(sel, index)); - } - } - - std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) - { - SimpleSelectorDict dict(sel->begin(), sel->end()); // XXX Set - std::vector indices; - for (size_t i = 0, S = sel->length(); i < S; ++i) { - if (!hash_.count((*sel)[i])) { - continue; - } - const std::vector >& subsets = hash_[(*sel)[i]]; - for (const std::pair& item : subsets) { - bool include = true; - for (const Simple_Selector_Obj& it : item.first->elements()) { - auto found = dict.find(it); - if (found == dict.end()) { - include = false; - break; - } - } - if (include) indices.push_back(item.second); - } - } - sort(indices.begin(), indices.end()); - std::vector::iterator indices_end = unique(indices.begin(), indices.end()); - indices.resize(distance(indices.begin(), indices_end)); - - std::vector results; - for (size_t i = 0, S = indices.size(); i < S; ++i) { - results.push_back(values_[indices[i]]); - } - return results; - } - - std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) - { - return get_kv(sel); - } - -} \ No newline at end of file diff --git a/src/libsass/src/subset_map.hpp b/src/libsass/src/subset_map.hpp deleted file mode 100644 index 5c091e685..000000000 --- a/src/libsass/src/subset_map.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef SASS_SUBSET_MAP_H -#define SASS_SUBSET_MAP_H - -#include -#include -#include -#include -#include - -#include "ast_fwd_decl.hpp" - - -// #include -// #include -// template -// std::string vector_to_string(std::vector v) -// { -// std::stringstream buffer; -// buffer << "["; - -// if (!v.empty()) -// { buffer << v[0]; } -// else -// { buffer << "]"; } - -// if (v.size() == 1) -// { buffer << "]"; } -// else -// { -// for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; -// buffer << "]"; -// } - -// return buffer.str(); -// } - -// template -// std::string set_to_string(set v) -// { -// std::stringstream buffer; -// buffer << "["; -// typename std::set::iterator i = v.begin(); -// if (!v.empty()) -// { buffer << *i; } -// else -// { buffer << "]"; } - -// if (v.size() == 1) -// { buffer << "]"; } -// else -// { -// for (++i; i != v.end(); ++i) buffer << ", " << *i; -// buffer << "]"; -// } - -// return buffer.str(); -// } - -namespace Sass { - - class Subset_Map { - private: - std::vector values_; - std::map >, OrderNodes > hash_; - public: - void put(const Compound_Selector_Obj& sel, const SubSetMapPair& value); - std::vector get_kv(const Compound_Selector_Obj& s); - std::vector get_v(const Compound_Selector_Obj& s); - bool empty() { return values_.empty(); } - void clear() { values_.clear(); hash_.clear(); } - const std::vector values(void) { return values_; } - }; - -} - -#endif diff --git a/src/libsass/src/support/libsass.pc.in b/src/libsass/src/support/libsass.pc.in deleted file mode 100644 index d201bfaaf..000000000 --- a/src/libsass/src/support/libsass.pc.in +++ /dev/null @@ -1,11 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libsass -URL: https://github.com/sass/libsass -Description: A C implementation of a Sass compiler -Version: @VERSION@ -Libs: -L${libdir} -lsass -Cflags: -I${includedir} diff --git a/src/libsass/src/to_c.cpp b/src/libsass/src/to_c.cpp deleted file mode 100644 index 8a6ea8d51..000000000 --- a/src/libsass/src/to_c.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "sass.hpp" -#include "to_c.hpp" -#include "ast.hpp" - -namespace Sass { - - union Sass_Value* To_C::fallback_impl(AST_Node_Ptr n) - { return sass_make_error("unknown type for C-API"); } - - union Sass_Value* To_C::operator()(Boolean_Ptr b) - { return sass_make_boolean(b->value()); } - - union Sass_Value* To_C::operator()(Number_Ptr n) - { return sass_make_number(n->value(), n->unit().c_str()); } - - union Sass_Value* To_C::operator()(Custom_Warning_Ptr w) - { return sass_make_warning(w->message().c_str()); } - - union Sass_Value* To_C::operator()(Custom_Error_Ptr e) - { return sass_make_error(e->message().c_str()); } - - union Sass_Value* To_C::operator()(Color_Ptr c) - { return sass_make_color(c->r(), c->g(), c->b(), c->a()); } - - union Sass_Value* To_C::operator()(String_Constant_Ptr s) - { - if (s->quote_mark()) { - return sass_make_qstring(s->value().c_str()); - } else { - return sass_make_string(s->value().c_str()); - } - } - - union Sass_Value* To_C::operator()(String_Quoted_Ptr s) - { return sass_make_qstring(s->value().c_str()); } - - union Sass_Value* To_C::operator()(List_Ptr l) - { - union Sass_Value* v = sass_make_list(l->length(), l->separator(), l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - sass_list_set_value(v, i, (*l)[i]->perform(this)); - } - return v; - } - - union Sass_Value* To_C::operator()(Map_Ptr m) - { - union Sass_Value* v = sass_make_map(m->length()); - int i = 0; - for (auto key : m->keys()) { - sass_map_set_key(v, i, key->perform(this)); - sass_map_set_value(v, i, m->at(key)->perform(this)); - i++; - } - return v; - } - - union Sass_Value* To_C::operator()(Arguments_Ptr a) - { - union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA, false); - for (size_t i = 0, L = a->length(); i < L; ++i) { - sass_list_set_value(v, i, (*a)[i]->perform(this)); - } - return v; - } - - union Sass_Value* To_C::operator()(Argument_Ptr a) - { return a->value()->perform(this); } - - // not strictly necessary because of the fallback - union Sass_Value* To_C::operator()(Null_Ptr n) - { return sass_make_null(); } - -}; diff --git a/src/libsass/src/to_c.hpp b/src/libsass/src/to_c.hpp deleted file mode 100644 index a5331e3bf..000000000 --- a/src/libsass/src/to_c.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SASS_TO_C_H -#define SASS_TO_C_H - -#include "ast_fwd_decl.hpp" -#include "operation.hpp" -#include "sass/values.h" - -namespace Sass { - - class To_C : public Operation_CRTP { - // override this to define a catch-all - union Sass_Value* fallback_impl(AST_Node_Ptr n); - - public: - - To_C() { } - ~To_C() { } - - union Sass_Value* operator()(Boolean_Ptr); - union Sass_Value* operator()(Number_Ptr); - union Sass_Value* operator()(Color_Ptr); - union Sass_Value* operator()(String_Constant_Ptr); - union Sass_Value* operator()(String_Quoted_Ptr); - union Sass_Value* operator()(Custom_Warning_Ptr); - union Sass_Value* operator()(Custom_Error_Ptr); - union Sass_Value* operator()(List_Ptr); - union Sass_Value* operator()(Map_Ptr); - union Sass_Value* operator()(Null_Ptr); - union Sass_Value* operator()(Arguments_Ptr); - union Sass_Value* operator()(Argument_Ptr); - - // dispatch to fallback implementation - union Sass_Value* fallback(AST_Node_Ptr x) - { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/to_value.cpp b/src/libsass/src/to_value.cpp deleted file mode 100644 index 3912c5510..000000000 --- a/src/libsass/src/to_value.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "to_value.hpp" - -namespace Sass { - - Value_Ptr To_Value::fallback_impl(AST_Node_Ptr n) - { - // throw a runtime error if this happens - // we want a well defined set of possible nodes - throw std::runtime_error("invalid node for to_value"); - } - - // Custom_Error is a valid value - Value_Ptr To_Value::operator()(Custom_Error_Ptr e) - { - return e; - } - - // Custom_Warning is a valid value - Value_Ptr To_Value::operator()(Custom_Warning_Ptr w) - { - return w; - } - - // Boolean is a valid value - Value_Ptr To_Value::operator()(Boolean_Ptr b) - { - return b; - } - - // Number is a valid value - Value_Ptr To_Value::operator()(Number_Ptr n) - { - return n; - } - - // Color is a valid value - Value_Ptr To_Value::operator()(Color_Ptr c) - { - return c; - } - - // String_Constant is a valid value - Value_Ptr To_Value::operator()(String_Constant_Ptr s) - { - return s; - } - - // String_Quoted is a valid value - Value_Ptr To_Value::operator()(String_Quoted_Ptr s) - { - return s; - } - - // List is a valid value - Value_Ptr To_Value::operator()(List_Ptr l) - { - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - return ll.detach(); - } - - // Map is a valid value - Value_Ptr To_Value::operator()(Map_Ptr m) - { - return m; - } - - // Null is a valid value - Value_Ptr To_Value::operator()(Null_Ptr n) - { - return n; - } - - // Function is a valid value - Value_Ptr To_Value::operator()(Function_Ptr n) - { - return n; - } - - // Argument returns its value - Value_Ptr To_Value::operator()(Argument_Ptr arg) - { - if (!arg->name().empty()) return 0; - return arg->value()->perform(this); - } - - // Selector_List is converted to a string - Value_Ptr To_Value::operator()(Selector_List_Ptr s) - { - return SASS_MEMORY_NEW(String_Quoted, - s->pstate(), - s->to_string(ctx.c_options)); - } - - // Binary_Expression is converted to a string - Value_Ptr To_Value::operator()(Binary_Expression_Ptr s) - { - return SASS_MEMORY_NEW(String_Quoted, - s->pstate(), - s->to_string(ctx.c_options)); - } - -}; diff --git a/src/libsass/src/to_value.hpp b/src/libsass/src/to_value.hpp deleted file mode 100644 index 8f64128c4..000000000 --- a/src/libsass/src/to_value.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef SASS_TO_VALUE_H -#define SASS_TO_VALUE_H - -#include "operation.hpp" -#include "sass/values.h" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - class To_Value : public Operation_CRTP { - - Value_Ptr fallback_impl(AST_Node_Ptr n); - - private: - - Context& ctx; - - public: - - To_Value(Context& ctx) - : ctx(ctx) - { } - ~To_Value() { } - using Operation::operator(); - - Value_Ptr operator()(Argument_Ptr); - Value_Ptr operator()(Boolean_Ptr); - Value_Ptr operator()(Number_Ptr); - Value_Ptr operator()(Color_Ptr); - Value_Ptr operator()(String_Constant_Ptr); - Value_Ptr operator()(String_Quoted_Ptr); - Value_Ptr operator()(Custom_Warning_Ptr); - Value_Ptr operator()(Custom_Error_Ptr); - Value_Ptr operator()(List_Ptr); - Value_Ptr operator()(Map_Ptr); - Value_Ptr operator()(Null_Ptr); - Value_Ptr operator()(Function_Ptr); - - // convert to string via `To_String` - Value_Ptr operator()(Selector_List_Ptr); - Value_Ptr operator()(Binary_Expression_Ptr); - - // fallback throws error - template - Value_Ptr fallback(U x) { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/units.cpp b/src/libsass/src/units.cpp deleted file mode 100644 index 779f1d2b4..000000000 --- a/src/libsass/src/units.cpp +++ /dev/null @@ -1,501 +0,0 @@ -#include "sass.hpp" -#include -#include "units.hpp" -#include "error_handling.hpp" - -namespace Sass { - - /* the conversion matrix can be readed the following way */ - /* if you go down, the factor is for the numerator (multiply) */ - /* if you go right, the factor is for the denominator (divide) */ - /* and yes, we actually use both, not sure why, but why not!? */ - - const double size_conversion_factors[6][6] = - { - /* in cm pc mm pt px */ - /* in */ { 1, 2.54, 6, 25.4, 72, 96, }, - /* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54 }, - /* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0 }, - /* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4 }, - /* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0 }, - /* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, } - }; - - const double angle_conversion_factors[4][4] = - { - /* deg grad rad turn */ - /* deg */ { 1, 40.0/36.0, PI/180.0, 1.0/360.0 }, - /* grad */ { 36.0/40.0, 1, PI/200.0, 1.0/400.0 }, - /* rad */ { 180.0/PI, 200.0/PI, 1, 0.5/PI }, - /* turn */ { 360.0, 400.0, 2.0*PI, 1 } - }; - - const double time_conversion_factors[2][2] = - { - /* s ms */ - /* s */ { 1, 1000.0 }, - /* ms */ { 1/1000.0, 1 } - }; - const double frequency_conversion_factors[2][2] = - { - /* Hz kHz */ - /* Hz */ { 1, 1/1000.0 }, - /* kHz */ { 1000.0, 1 } - }; - const double resolution_conversion_factors[3][3] = - { - /* dpi dpcm dppx */ - /* dpi */ { 1, 1/2.54, 1/96.0 }, - /* dpcm */ { 2.54, 1, 2.54/96 }, - /* dppx */ { 96, 96/2.54, 1 } - }; - - UnitClass get_unit_type(UnitType unit) - { - switch (unit & 0xFF00) - { - case UnitClass::LENGTH: return UnitClass::LENGTH; - case UnitClass::ANGLE: return UnitClass::ANGLE; - case UnitClass::TIME: return UnitClass::TIME; - case UnitClass::FREQUENCY: return UnitClass::FREQUENCY; - case UnitClass::RESOLUTION: return UnitClass::RESOLUTION; - default: return UnitClass::INCOMMENSURABLE; - } - }; - - std::string get_unit_class(UnitType unit) - { - switch (unit & 0xFF00) - { - case UnitClass::LENGTH: return "LENGTH"; - case UnitClass::ANGLE: return "ANGLE"; - case UnitClass::TIME: return "TIME"; - case UnitClass::FREQUENCY: return "FREQUENCY"; - case UnitClass::RESOLUTION: return "RESOLUTION"; - default: return "INCOMMENSURABLE"; - } - }; - - UnitType get_main_unit(const UnitClass unit) - { - switch (unit) - { - case UnitClass::LENGTH: return UnitType::PX; - case UnitClass::ANGLE: return UnitType::DEG; - case UnitClass::TIME: return UnitType::SEC; - case UnitClass::FREQUENCY: return UnitType::HERTZ; - case UnitClass::RESOLUTION: return UnitType::DPI; - default: return UnitType::UNKNOWN; - } - }; - - UnitType string_to_unit(const std::string& s) - { - // size units - if (s == "px") return UnitType::PX; - else if (s == "pt") return UnitType::PT; - else if (s == "pc") return UnitType::PC; - else if (s == "mm") return UnitType::MM; - else if (s == "cm") return UnitType::CM; - else if (s == "in") return UnitType::IN; - // angle units - else if (s == "deg") return UnitType::DEG; - else if (s == "grad") return UnitType::GRAD; - else if (s == "rad") return UnitType::RAD; - else if (s == "turn") return UnitType::TURN; - // time units - else if (s == "s") return UnitType::SEC; - else if (s == "ms") return UnitType::MSEC; - // frequency units - else if (s == "Hz") return UnitType::HERTZ; - else if (s == "kHz") return UnitType::KHERTZ; - // resolutions units - else if (s == "dpi") return UnitType::DPI; - else if (s == "dpcm") return UnitType::DPCM; - else if (s == "dppx") return UnitType::DPPX; - // for unknown units - else return UnitType::UNKNOWN; - } - - const char* unit_to_string(UnitType unit) - { - switch (unit) { - // size units - case UnitType::PX: return "px"; - case UnitType::PT: return "pt"; - case UnitType::PC: return "pc"; - case UnitType::MM: return "mm"; - case UnitType::CM: return "cm"; - case UnitType::IN: return "in"; - // angle units - case UnitType::DEG: return "deg"; - case UnitType::GRAD: return "grad"; - case UnitType::RAD: return "rad"; - case UnitType::TURN: return "turn"; - // time units - case UnitType::SEC: return "s"; - case UnitType::MSEC: return "ms"; - // frequency units - case UnitType::HERTZ: return "Hz"; - case UnitType::KHERTZ: return "kHz"; - // resolutions units - case UnitType::DPI: return "dpi"; - case UnitType::DPCM: return "dpcm"; - case UnitType::DPPX: return "dppx"; - // for unknown units - default: return ""; - } - } - - std::string unit_to_class(const std::string& s) - { - if (s == "px") return "LENGTH"; - else if (s == "pt") return "LENGTH"; - else if (s == "pc") return "LENGTH"; - else if (s == "mm") return "LENGTH"; - else if (s == "cm") return "LENGTH"; - else if (s == "in") return "LENGTH"; - // angle units - else if (s == "deg") return "ANGLE"; - else if (s == "grad") return "ANGLE"; - else if (s == "rad") return "ANGLE"; - else if (s == "turn") return "ANGLE"; - // time units - else if (s == "s") return "TIME"; - else if (s == "ms") return "TIME"; - // frequency units - else if (s == "Hz") return "FREQUENCY"; - else if (s == "kHz") return "FREQUENCY"; - // resolutions units - else if (s == "dpi") return "RESOLUTION"; - else if (s == "dpcm") return "RESOLUTION"; - else if (s == "dppx") return "RESOLUTION"; - // for unknown units - return "CUSTOM:" + s; - } - - // throws incompatibleUnits exceptions - double conversion_factor(const std::string& s1, const std::string& s2) - { - // assert for same units - if (s1 == s2) return 1; - // get unit enum from string - UnitType u1 = string_to_unit(s1); - UnitType u2 = string_to_unit(s2); - // query unit group types - UnitClass t1 = get_unit_type(u1); - UnitClass t2 = get_unit_type(u2); - // return the conversion factor - return conversion_factor(u1, u2, t1, t2); - } - - // throws incompatibleUnits exceptions - double conversion_factor(UnitType u1, UnitType u2, UnitClass t1, UnitClass t2) - { - // can't convert between groups - if (t1 != t2) return 0; - // get absolute offset - // used for array acces - size_t i1 = u1 - t1; - size_t i2 = u2 - t2; - // process known units - switch (t1) { - case LENGTH: - return size_conversion_factors[i1][i2]; - case ANGLE: - return angle_conversion_factors[i1][i2]; - case TIME: - return time_conversion_factors[i1][i2]; - case FREQUENCY: - return frequency_conversion_factors[i1][i2]; - case RESOLUTION: - return resolution_conversion_factors[i1][i2]; - case INCOMMENSURABLE: - return 0; - } - // fallback - return 0; - } - - double convert_units(const std::string& lhs, const std::string& rhs, int& lhsexp, int& rhsexp) - { - double f = 0; - // do not convert same ones - if (lhs == rhs) return 0; - // skip already canceled out unit - if (lhsexp == 0) return 0; - if (rhsexp == 0) return 0; - // check if it can be converted - UnitType ulhs = string_to_unit(lhs); - UnitType urhs = string_to_unit(rhs); - // skip units we cannot convert - if (ulhs == UNKNOWN) return 0; - if (urhs == UNKNOWN) return 0; - // query unit group types - UnitClass clhs = get_unit_type(ulhs); - UnitClass crhs = get_unit_type(urhs); - // skip units we cannot convert - if (clhs != crhs) return 0; - // if right denominator is bigger than lhs, we want to keep it in rhs unit - if (rhsexp < 0 && lhsexp > 0 && - rhsexp > lhsexp) { - // get the conversion factor for units - f = conversion_factor(urhs, ulhs, clhs, crhs); - // left hand side has been consumned - f = std::pow(f, lhsexp); - rhsexp += lhsexp; - lhsexp = 0; - } - else { - // get the conversion factor for units - f = conversion_factor(ulhs, urhs, clhs, crhs); - // right hand side has been consumned - f = std::pow(f, rhsexp); - lhsexp += rhsexp; - rhsexp = 0; - } - return f; - } - - bool Units::operator< (const Units& rhs) const - { - return (numerators < rhs.numerators) && - (denominators < rhs.denominators); - } - bool Units::operator== (const Units& rhs) const - { - return (numerators == rhs.numerators) && - (denominators == rhs.denominators); - } - - double Units::normalize() - { - - size_t iL = numerators.size(); - size_t nL = denominators.size(); - - // the final conversion factor - double factor = 1; - - for (size_t i = 0; i < iL; i++) { - std::string &lhs = numerators[i]; - UnitType ulhs = string_to_unit(lhs); - if (ulhs == UNKNOWN) continue; - UnitClass clhs = get_unit_type(ulhs); - UnitType umain = get_main_unit(clhs); - if (ulhs == umain) continue; - double f(conversion_factor(umain, ulhs, clhs, clhs)); - if (f == 0) throw std::runtime_error("INVALID"); - numerators[i] = unit_to_string(umain); - factor /= f; - } - - for (size_t n = 0; n < nL; n++) { - std::string &rhs = denominators[n]; - UnitType urhs = string_to_unit(rhs); - if (urhs == UNKNOWN) continue; - UnitClass crhs = get_unit_type(urhs); - UnitType umain = get_main_unit(crhs); - if (urhs == umain) continue; - double f(conversion_factor(umain, urhs, crhs, crhs)); - if (f == 0) throw std::runtime_error("INVALID"); - denominators[n] = unit_to_string(umain); - factor /= f; - } - - std::sort (numerators.begin(), numerators.end()); - std::sort (denominators.begin(), denominators.end()); - - // return for conversion - return factor; - } - - double Units::reduce() - { - - size_t iL = numerators.size(); - size_t nL = denominators.size(); - - // have less than two units? - if (iL + nL < 2) return 1; - - // first make sure same units cancel each other out - // it seems that a map table will fit nicely to do this - // we basically construct exponents for each unit - // has the advantage that they will be pre-sorted - std::map exponents; - - // initialize by summing up occurences in unit vectors - // this will already cancel out equivalent units (e.q. px/px) - for (size_t i = 0; i < iL; i ++) exponents[numerators[i]] += 1; - for (size_t n = 0; n < nL; n ++) exponents[denominators[n]] -= 1; - - // the final conversion factor - double factor = 1; - - // convert between compatible units - for (size_t i = 0; i < iL; i++) { - for (size_t n = 0; n < nL; n++) { - std::string &lhs = numerators[i], &rhs = denominators[n]; - int &lhsexp = exponents[lhs], &rhsexp = exponents[rhs]; - double f(convert_units(lhs, rhs, lhsexp, rhsexp)); - if (f == 0) continue; - factor /= f; - } - } - - // now we can build up the new unit arrays - numerators.clear(); - denominators.clear(); - - // recreate sorted units vectors - for (auto exp : exponents) { - int &exponent = exp.second; - while (exponent > 0 && exponent --) - numerators.push_back(exp.first); - while (exponent < 0 && exponent ++) - denominators.push_back(exp.first); - } - - // return for conversion - return factor; - - } - - std::string Units::unit() const - { - std::string u; - size_t iL = numerators.size(); - size_t nL = denominators.size(); - for (size_t i = 0; i < iL; i += 1) { - if (i) u += '*'; - u += numerators[i]; - } - if (nL != 0) u += '/'; - for (size_t n = 0; n < nL; n += 1) { - if (n) u += '*'; - u += denominators[n]; - } - return u; - } - - bool Units::is_unitless() const - { - return numerators.empty() && - denominators.empty(); - } - - bool Units::is_valid_css_unit() const - { - return numerators.size() <= 1 && - denominators.size() == 0; - } - - // this does not cover all cases (multiple prefered units) - double Units::convert_factor(const Units& r) const - { - - std::vector miss_nums(0); - std::vector miss_dens(0); - // create copy since we need these for state keeping - std::vector r_nums(r.numerators); - std::vector r_dens(r.denominators); - - auto l_num_it = numerators.begin(); - auto l_num_end = numerators.end(); - - bool l_unitless = is_unitless(); - auto r_unitless = r.is_unitless(); - - // overall conversion - double factor = 1; - - // process all left numerators - while (l_num_it != l_num_end) - { - // get and increment afterwards - const std::string l_num = *(l_num_it ++); - - auto r_num_it = r_nums.begin(), r_num_end = r_nums.end(); - - bool found = false; - // search for compatible numerator - while (r_num_it != r_num_end) - { - // get and increment afterwards - const std::string r_num = *(r_num_it); - // get possible conversion factor for units - double conversion = conversion_factor(l_num, r_num); - // skip incompatible numerator - if (conversion == 0) { - ++ r_num_it; - continue; - } - // apply to global factor - factor *= conversion; - // remove item from vector - r_nums.erase(r_num_it); - // found numerator - found = true; - break; - } - // maybe we did not find any - // left numerator is leftover - if (!found) miss_nums.push_back(l_num); - } - - auto l_den_it = denominators.begin(); - auto l_den_end = denominators.end(); - - // process all left denominators - while (l_den_it != l_den_end) - { - // get and increment afterwards - const std::string l_den = *(l_den_it ++); - - auto r_den_it = r_dens.begin(); - auto r_den_end = r_dens.end(); - - bool found = false; - // search for compatible denominator - while (r_den_it != r_den_end) - { - // get and increment afterwards - const std::string r_den = *(r_den_it); - // get possible converstion factor for units - double conversion = conversion_factor(l_den, r_den); - // skip incompatible denominator - if (conversion == 0) { - ++ r_den_it; - continue; - } - // apply to global factor - factor /= conversion; - // remove item from vector - r_dens.erase(r_den_it); - // found denominator - found = true; - break; - } - // maybe we did not find any - // left denominator is leftover - if (!found) miss_dens.push_back(l_den); - } - - // check left-overs (ToDo: might cancel out?) - if (miss_nums.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(r, *this); - } - else if (miss_dens.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(r, *this); - } - else if (r_nums.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(r, *this); - } - else if (r_dens.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(r, *this); - } - - return factor; - } - -} diff --git a/src/libsass/src/units.hpp b/src/libsass/src/units.hpp deleted file mode 100644 index 306f5349b..000000000 --- a/src/libsass/src/units.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef SASS_UNITS_H -#define SASS_UNITS_H - -#include -#include -#include -#include - -namespace Sass { - - const double PI = std::acos(-1); - - enum UnitClass { - LENGTH = 0x000, - ANGLE = 0x100, - TIME = 0x200, - FREQUENCY = 0x300, - RESOLUTION = 0x400, - INCOMMENSURABLE = 0x500 - }; - - enum UnitType { - - // size units - IN = UnitClass::LENGTH, - CM, - PC, - MM, - PT, - PX, - - // angle units - DEG = ANGLE, - GRAD, - RAD, - TURN, - - // time units - SEC = TIME, - MSEC, - - // frequency units - HERTZ = FREQUENCY, - KHERTZ, - - // resolutions units - DPI = RESOLUTION, - DPCM, - DPPX, - - // for unknown units - UNKNOWN = INCOMMENSURABLE - - }; - - class Units { - public: - std::vector numerators; - std::vector denominators; - public: - // default constructor - Units() : - numerators(), - denominators() - { } - // copy constructor - Units(const Units* ptr) : - numerators(ptr->numerators), - denominators(ptr->denominators) - { } - // convert to string - std::string unit() const; - // get if units are empty - bool is_unitless() const; - // return if valid for css - bool is_valid_css_unit() const; - // reduce units for output - // returns conversion factor - double reduce(); - // normalize units for compare - // returns conversion factor - double normalize(); - // compare operations - bool operator< (const Units& rhs) const; - bool operator== (const Units& rhs) const; - // factor to convert into given units - double convert_factor(const Units&) const; - }; - - extern const double size_conversion_factors[6][6]; - extern const double angle_conversion_factors[4][4]; - extern const double time_conversion_factors[2][2]; - extern const double frequency_conversion_factors[2][2]; - extern const double resolution_conversion_factors[3][3]; - - UnitType get_main_unit(const UnitClass unit); - enum Sass::UnitType string_to_unit(const std::string&); - const char* unit_to_string(Sass::UnitType unit); - enum Sass::UnitClass get_unit_type(Sass::UnitType unit); - std::string get_unit_class(Sass::UnitType unit); - std::string unit_to_class(const std::string&); - // throws incompatibleUnits exceptions - double conversion_factor(const std::string&, const std::string&); - double conversion_factor(UnitType, UnitType, UnitClass, UnitClass); - double convert_units(const std::string&, const std::string&, int&, int&); - -} - -#endif diff --git a/src/libsass/src/utf8.h b/src/libsass/src/utf8.h deleted file mode 100644 index 82b13f59f..000000000 --- a/src/libsass/src/utf8.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 - -#include "utf8/checked.h" -#include "utf8/unchecked.h" - -#endif // header guard diff --git a/src/libsass/src/utf8/checked.h b/src/libsass/src/utf8/checked.h deleted file mode 100644 index 693aee964..000000000 --- a/src/libsass/src/utf8/checked.h +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 - -#include "core.h" -#include - -namespace utf8 -{ - // Base for the exceptions that may be thrown from the library - class exception : public ::std::exception { - }; - - // Exceptions that may be thrown from the library functions. - class invalid_code_point : public exception { - uint32_t cp; - public: - invalid_code_point(uint32_t cp) : cp(cp) {} - virtual const char* what() const throw() { return "Invalid code point"; } - uint32_t code_point() const {return cp;} - }; - - class invalid_utf8 : public exception { - uint8_t u8; - public: - invalid_utf8 (uint8_t u) : u8(u) {} - virtual const char* what() const throw() { return "Invalid UTF-8"; } - uint8_t utf8_octet() const {return u8;} - }; - - class invalid_utf16 : public exception { - uint16_t u16; - public: - invalid_utf16 (uint16_t u) : u16(u) {} - virtual const char* what() const throw() { return "Invalid UTF-16"; } - uint16_t utf16_word() const {return u16;} - }; - - class not_enough_room : public exception { - public: - virtual const char* what() const throw() { return "Not enough space"; } - }; - - /// The library API - functions intended to be called by the users - - template - octet_iterator append(uint32_t cp, octet_iterator result) - { - if (!utf8::internal::is_code_point_valid(cp)) - throw invalid_code_point(cp); - - if (cp < 0x80) // one octet - *(result++) = static_cast(cp); - else if (cp < 0x800) { // two octets - *(result++) = static_cast((cp >> 6) | 0xc0); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - else if (cp < 0x10000) { // three octets - *(result++) = static_cast((cp >> 12) | 0xe0); - *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - else { // four octets - *(result++) = static_cast((cp >> 18) | 0xf0); - *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); - *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - return result; - } - - template - output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) - { - while (start != end) { - octet_iterator sequence_start = start; - internal::utf_error err_code = utf8::internal::validate_next(start, end); - switch (err_code) { - case internal::UTF8_OK : - for (octet_iterator it = sequence_start; it != start; ++it) - *out++ = *it; - break; - case internal::NOT_ENOUGH_ROOM: - throw not_enough_room(); - case internal::INVALID_LEAD: - out = utf8::append (replacement, out); - ++start; - break; - case internal::INCOMPLETE_SEQUENCE: - case internal::OVERLONG_SEQUENCE: - case internal::INVALID_CODE_POINT: - out = utf8::append (replacement, out); - ++start; - // just one replacement mark for the sequence - while (start != end && utf8::internal::is_trail(*start)) - ++start; - break; - } - } - return out; - } - - template - inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) - { - static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); - return utf8::replace_invalid(start, end, out, replacement_marker); - } - - template - uint32_t next(octet_iterator& it, octet_iterator end) - { - uint32_t cp = 0; - internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); - switch (err_code) { - case internal::UTF8_OK : - break; - case internal::NOT_ENOUGH_ROOM : - throw not_enough_room(); - case internal::INVALID_LEAD : - case internal::INCOMPLETE_SEQUENCE : - case internal::OVERLONG_SEQUENCE : - throw invalid_utf8(*it); - case internal::INVALID_CODE_POINT : - throw invalid_code_point(cp); - } - return cp; - } - - template - uint32_t peek_next(octet_iterator it, octet_iterator end) - { - return utf8::next(it, end); - } - - template - uint32_t prior(octet_iterator& it, octet_iterator start) - { - // can't do much if it == start - if (it == start) - throw not_enough_room(); - - octet_iterator end = it; - // Go back until we hit either a lead octet or start - while (utf8::internal::is_trail(*(--it))) - if (it == start) - throw invalid_utf8(*it); // error - no lead byte in the sequence - return utf8::peek_next(it, end); - } - - /// Deprecated in versions that include "prior" - template - uint32_t previous(octet_iterator& it, octet_iterator pass_start) - { - octet_iterator end = it; - while (utf8::internal::is_trail(*(--it))) - if (it == pass_start) - throw invalid_utf8(*it); // error - no lead byte in the sequence - octet_iterator temp = it; - return utf8::next(temp, end); - } - - template - void advance (octet_iterator& it, distance_type n, octet_iterator end) - { - for (distance_type i = 0; i < n; ++i) - utf8::next(it, end); - } - - template - void retreat (octet_iterator& it, distance_type n, octet_iterator start) - { - for (distance_type i = 0; i < n; ++i) - utf8::prior(it, start); - } - - template - typename std::iterator_traits::difference_type - distance (octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::next(first, last); - return dist; - } - - template - octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) { - uint32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) { - if (start != end) { - uint32_t trail_surrogate = utf8::internal::mask16(*start++); - if (utf8::internal::is_trail_surrogate(trail_surrogate)) - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - else - throw invalid_utf16(static_cast(trail_surrogate)); - } - else - throw invalid_utf16(static_cast(cp)); - - } - // Lone trail surrogate - else if (utf8::internal::is_trail_surrogate(cp)) - throw invalid_utf16(static_cast(cp)); - - result = utf8::append(cp, result); - } - return result; - } - - template - u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start != end) { - uint32_t cp = utf8::next(start, end); - if (cp > 0xffff) { //make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } - - template - octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::append(*(start++), result); - - return result; - } - - template - u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start != end) - (*result++) = utf8::next(start, end); - - return result; - } - - // The iterator class - template - class iterator : public std::iterator { - octet_iterator it; - octet_iterator range_start; - octet_iterator range_end; - public: - iterator () {} - explicit iterator (const octet_iterator& octet_it, - const octet_iterator& range_start, - const octet_iterator& range_end) : - it(octet_it), range_start(range_start), range_end(range_end) - { - if (it < range_start || it > range_end) - throw std::out_of_range("Invalid utf-8 iterator position"); - } - // the default "big three" are OK - octet_iterator base () const { return it; } - uint32_t operator * () const - { - octet_iterator temp = it; - return utf8::next(temp, range_end); - } - bool operator == (const iterator& rhs) const - { - if (range_start != rhs.range_start || range_end != rhs.range_end) - throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); - return (it == rhs.it); - } - bool operator != (const iterator& rhs) const - { - return !(operator == (rhs)); - } - iterator& operator ++ () - { - utf8::next(it, range_end); - return *this; - } - iterator operator ++ (int) - { - iterator temp = *this; - utf8::next(it, range_end); - return temp; - } - iterator& operator -- () - { - utf8::prior(it, range_start); - return *this; - } - iterator operator -- (int) - { - iterator temp = *this; - utf8::prior(it, range_start); - return temp; - } - }; // class iterator - -} // namespace utf8 - -#endif //header guard - - diff --git a/src/libsass/src/utf8/core.h b/src/libsass/src/utf8/core.h deleted file mode 100644 index f85081f8f..000000000 --- a/src/libsass/src/utf8/core.h +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 - -#include - -namespace utf8 -{ - // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers - // You may need to change them to match your system. - // These typedefs have the same names as ones from cstdint, or boost/cstdint - typedef unsigned char uint8_t; - typedef unsigned short uint16_t; - typedef unsigned int uint32_t; - -// Helper code - not intended to be directly called by the library users. May be changed at any time -namespace internal -{ - // Unicode constants - // Leading (high) surrogates: 0xd800 - 0xdbff - // Trailing (low) surrogates: 0xdc00 - 0xdfff - const uint16_t LEAD_SURROGATE_MIN = 0xd800u; - const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; - const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; - const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; - const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); - const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; - - // Maximum valid value for a Unicode code point - const uint32_t CODE_POINT_MAX = 0x0010ffffu; - - template - inline uint8_t mask8(octet_type oc) - { - return static_cast(0xff & oc); - } - template - inline uint16_t mask16(u16_type oc) - { - return static_cast(0xffff & oc); - } - template - inline bool is_trail(octet_type oc) - { - return ((utf8::internal::mask8(oc) >> 6) == 0x2); - } - - template - inline bool is_lead_surrogate(u16 cp) - { - return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); - } - - template - inline bool is_trail_surrogate(u16 cp) - { - return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); - } - - template - inline bool is_surrogate(u16 cp) - { - return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); - } - - template - inline bool is_code_point_valid(u32 cp) - { - return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); - } - - template - inline typename std::iterator_traits::difference_type - sequence_length(octet_iterator lead_it) - { - uint8_t lead = utf8::internal::mask8(*lead_it); - if (lead < 0x80) - return 1; - else if ((lead >> 5) == 0x6) - return 2; - else if ((lead >> 4) == 0xe) - return 3; - else if ((lead >> 3) == 0x1e) - return 4; - else - return 0; - } - - template - inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) - { - if (cp < 0x80) { - if (length != 1) - return true; - } - else if (cp < 0x800) { - if (length != 2) - return true; - } - else if (cp < 0x10000) { - if (length != 3) - return true; - } - - return false; - } - - enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; - - /// Helper for get_sequence_x - template - utf_error increase_safely(octet_iterator& it, octet_iterator end) - { - if (++it == end) - return NOT_ENOUGH_ROOM; - - if (!utf8::internal::is_trail(*it)) - return INCOMPLETE_SEQUENCE; - - return UTF8_OK; - } - - #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} - - /// get_sequence_x functions decode utf-8 sequences of the length x - template - utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) - { - if (it == end) - return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); - - return UTF8_OK; - } - - template - utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) - { - if (it == end) - return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); - - UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - - code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); - - return UTF8_OK; - } - - template - utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) - { - if (it == end) - return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); - - UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - - code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); - - UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - - code_point += (*it) & 0x3f; - - return UTF8_OK; - } - - template - utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) - { - if (it == end) - return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); - - UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - - code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); - - UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - - code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; - - UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - - code_point += (*it) & 0x3f; - - return UTF8_OK; - } - - #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR - - template - utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) - { - // Save the original value of it so we can go back in case of failure - // Of course, it does not make much sense with i.e. stream iterators - octet_iterator original_it = it; - - uint32_t cp = 0; - // Determine the sequence length based on the lead octet - typedef typename std::iterator_traits::difference_type octet_difference_type; - const octet_difference_type length = utf8::internal::sequence_length(it); - - // Get trail octets and calculate the code point - utf_error err = UTF8_OK; - switch (length) { - case 0: - return INVALID_LEAD; - case 1: - err = utf8::internal::get_sequence_1(it, end, cp); - break; - case 2: - err = utf8::internal::get_sequence_2(it, end, cp); - break; - case 3: - err = utf8::internal::get_sequence_3(it, end, cp); - break; - case 4: - err = utf8::internal::get_sequence_4(it, end, cp); - break; - } - - if (err == UTF8_OK) { - // Decoding succeeded. Now, security checks... - if (utf8::internal::is_code_point_valid(cp)) { - if (!utf8::internal::is_overlong_sequence(cp, length)){ - // Passed! Return here. - code_point = cp; - ++it; - return UTF8_OK; - } - else - err = OVERLONG_SEQUENCE; - } - else - err = INVALID_CODE_POINT; - } - - // Failure branch - restore the original value of the iterator - it = original_it; - return err; - } - - template - inline utf_error validate_next(octet_iterator& it, octet_iterator end) { - uint32_t ignored; - return utf8::internal::validate_next(it, end, ignored); - } - -} // namespace internal - - /// The library API - functions intended to be called by the users - - // Byte order mark - const uint8_t bom[] = {0xef, 0xbb, 0xbf}; - - template - octet_iterator find_invalid(octet_iterator start, octet_iterator end) - { - octet_iterator result = start; - while (result != end) { - utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); - if (err_code != internal::UTF8_OK) - return result; - } - return result; - } - - template - inline bool is_valid(octet_iterator start, octet_iterator end) - { - return (utf8::find_invalid(start, end) == end); - } - - template - inline bool starts_with_bom (octet_iterator it, octet_iterator end) - { - return ( - ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && - ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && - ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) - ); - } - - //Deprecated in release 2.3 - template - inline bool is_bom (octet_iterator it) - { - return ( - (utf8::internal::mask8(*it++)) == bom[0] && - (utf8::internal::mask8(*it++)) == bom[1] && - (utf8::internal::mask8(*it)) == bom[2] - ); - } -} // namespace utf8 - -#endif // header guard - - diff --git a/src/libsass/src/utf8/unchecked.h b/src/libsass/src/utf8/unchecked.h deleted file mode 100644 index 01bdd076a..000000000 --- a/src/libsass/src/utf8/unchecked.h +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 - -#include "core.h" - -namespace utf8 -{ - namespace unchecked - { - template - octet_iterator append(uint32_t cp, octet_iterator result) - { - if (cp < 0x80) // one octet - *(result++) = static_cast(cp); - else if (cp < 0x800) { // two octets - *(result++) = static_cast((cp >> 6) | 0xc0); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - else if (cp < 0x10000) { // three octets - *(result++) = static_cast((cp >> 12) | 0xe0); - *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - else { // four octets - *(result++) = static_cast((cp >> 18) | 0xf0); - *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); - *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); - *(result++) = static_cast((cp & 0x3f) | 0x80); - } - return result; - } - - template - uint32_t next(octet_iterator& it) - { - uint32_t cp = utf8::internal::mask8(*it); - typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); - switch (length) { - case 1: - break; - case 2: - it++; - cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); - break; - case 3: - ++it; - cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); - ++it; - cp += (*it) & 0x3f; - break; - case 4: - ++it; - cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); - ++it; - cp += (utf8::internal::mask8(*it) << 6) & 0xfff; - ++it; - cp += (*it) & 0x3f; - break; - } - ++it; - return cp; - } - - template - uint32_t peek_next(octet_iterator it) - { - return utf8::unchecked::next(it); - } - - template - uint32_t prior(octet_iterator& it) - { - while (utf8::internal::is_trail(*(--it))) ; - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } - - // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) - template - inline uint32_t previous(octet_iterator& it) - { - return utf8::unchecked::prior(it); - } - - template - void advance (octet_iterator& it, distance_type n) - { - for (distance_type i = 0; i < n; ++i) - utf8::unchecked::next(it); - } - - template - void retreat (octet_iterator& it, distance_type n) - { - for (distance_type i = 0; i < n; ++i) - utf8::unchecked::prior(it); - } - - template - typename std::iterator_traits::difference_type - distance (octet_iterator first, octet_iterator last) - { - typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) - utf8::unchecked::next(first); - return dist; - } - - template - octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) - { - while (start != end) { - uint32_t cp = utf8::internal::mask16(*start++); - // Take care of surrogate pairs first - if (utf8::internal::is_lead_surrogate(cp)) { - uint32_t trail_surrogate = utf8::internal::mask16(*start++); - cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; - } - result = utf8::unchecked::append(cp, result); - } - return result; - } - - template - u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) - { - while (start < end) { - uint32_t cp = utf8::unchecked::next(start); - if (cp > 0xffff) { //make a surrogate pair - *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); - *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); - } - else - *result++ = static_cast(cp); - } - return result; - } - - template - octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) - { - while (start != end) - result = utf8::unchecked::append(*(start++), result); - - return result; - } - - template - u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) - { - while (start < end) - (*result++) = utf8::unchecked::next(start); - - return result; - } - - // The iterator class - template - class iterator : public std::iterator { - octet_iterator it; - public: - iterator () {} - explicit iterator (const octet_iterator& octet_it): it(octet_it) {} - // the default "big three" are OK - octet_iterator base () const { return it; } - uint32_t operator * () const - { - octet_iterator temp = it; - return utf8::unchecked::next(temp); - } - bool operator == (const iterator& rhs) const - { - return (it == rhs.it); - } - bool operator != (const iterator& rhs) const - { - return !(operator == (rhs)); - } - iterator& operator ++ () - { - ::std::advance(it, utf8::internal::sequence_length(it)); - return *this; - } - iterator operator ++ (int) - { - iterator temp = *this; - ::std::advance(it, utf8::internal::sequence_length(it)); - return temp; - } - iterator& operator -- () - { - utf8::unchecked::prior(it); - return *this; - } - iterator operator -- (int) - { - iterator temp = *this; - utf8::unchecked::prior(it); - return temp; - } - }; // class iterator - - } // namespace utf8::unchecked -} // namespace utf8 - - -#endif // header guard - diff --git a/src/libsass/src/utf8_string.cpp b/src/libsass/src/utf8_string.cpp deleted file mode 100644 index 19425521c..000000000 --- a/src/libsass/src/utf8_string.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "sass.hpp" -#include -#include -#include -#include - -#include "utf8.h" - -namespace Sass { - namespace UTF_8 { - using std::string; - - // naming conventions: - // offset: raw byte offset (0 based) - // position: code point offset (0 based) - // index: code point offset (1 based or negative) - - // function that will count the number of code points (utf-8 characters) from the given beginning to the given end - size_t code_point_count(const string& str, size_t start, size_t end) { - return utf8::distance(str.begin() + start, str.begin() + end); - } - - size_t code_point_count(const string& str) { - return utf8::distance(str.begin(), str.end()); - } - - // function that will return the byte offset at a code point position - size_t offset_at_position(const string& str, size_t position) { - string::const_iterator it = str.begin(); - utf8::advance(it, position, str.end()); - return std::distance(str.begin(), it); - } - - // function that returns number of bytes in a character at offset - size_t code_point_size_at_offset(const string& str, size_t offset) { - // get iterator from string and forward by offset - string::const_iterator stop = str.begin() + offset; - // check if beyond boundary - if (stop == str.end()) return 0; - // advance by one code point - utf8::advance(stop, 1, str.end()); - // calculate offset for code point - return stop - str.begin() - offset; - } - - // function that will return a normalized index, given a crazy one - size_t normalize_index(int index, size_t len) { - long signed_len = static_cast(len); - // assuming the index is 1-based - // we are returning a 0-based index - if (index > 0 && index <= signed_len) { - // positive and within string length - return index-1; - } - else if (index > signed_len) { - // positive and past string length - return len; - } - else if (index == 0) { - return 0; - } - else if (std::abs((double)index) <= signed_len) { - // negative and within string length - return index + signed_len; - } - else { - // negative and past string length - return 0; - } - } - - #ifdef _WIN32 - - // utf16 functions - using std::wstring; - - // convert from utf16/wide string to utf8 string - string convert_from_utf16(const wstring& utf16) - { - string utf8; - // pre-allocate expected memory - utf8.reserve(sizeof(utf16)/2); - utf8::utf16to8(utf16.begin(), utf16.end(), - back_inserter(utf8)); - return utf8; - } - - // convert from utf8 string to utf16/wide string - wstring convert_to_utf16(const string& utf8) - { - wstring utf16; - // pre-allocate expected memory - utf16.reserve(code_point_count(utf8)*2); - utf8::utf8to16(utf8.begin(), utf8.end(), - back_inserter(utf16)); - return utf16; - } - - #endif - - } -} diff --git a/src/libsass/src/utf8_string.hpp b/src/libsass/src/utf8_string.hpp deleted file mode 100644 index 5e879bec3..000000000 --- a/src/libsass/src/utf8_string.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef SASS_UTF8_STRING_H -#define SASS_UTF8_STRING_H - -#include -#include "utf8.h" - -namespace Sass { - namespace UTF_8 { - - // naming conventions: - // offset: raw byte offset (0 based) - // position: code point offset (0 based) - // index: code point offset (1 based or negative) - - // function that will count the number of code points (utf-8 characters) from the beginning to the given end - size_t code_point_count(const std::string& str, size_t start, size_t end); - size_t code_point_count(const std::string& str); - - // function that will return the byte offset of a code point in a - size_t offset_at_position(const std::string& str, size_t position); - - // function that returns number of bytes in a character in a string - size_t code_point_size_at_offset(const std::string& str, size_t offset); - - // function that will return a normalized index, given a crazy one - size_t normalize_index(int index, size_t len); - - #ifdef _WIN32 - // functions to handle unicode paths on windows - std::string convert_from_utf16(const std::wstring& wstr); - std::wstring convert_to_utf16(const std::string& str); - #endif - - } -} - -#endif diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp deleted file mode 100644 index 60f69ab76..000000000 --- a/src/libsass/src/util.cpp +++ /dev/null @@ -1,733 +0,0 @@ -#include "sass.hpp" -#include "sass.h" -#include "ast.hpp" -#include "util.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "constants.hpp" -#include "utf8/checked.h" - -#include -#include -#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) -#include -#endif - -namespace Sass { - - double round(double val, size_t precision) - { - // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets - // See https://github.com/sass/node-sass/issues/1854 for details - // FIXME: Remove this workaround when we switch to VS2015+ - #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) - static std::once_flag flag; - std::call_once(flag, []() { _set_FMA3_enable(0); }); - #endif - - // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 - if (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); - else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); - // work around some compiler issue - // cygwin has it not defined in std - using namespace std; - return ::round(val); - } - - /* Locale unspecific atof function. */ - double sass_strtod(const char *str) - { - char separator = *(localeconv()->decimal_point); - if(separator != '.'){ - // The current locale specifies another - // separator. convert the separator to the - // one understood by the locale if needed - const char *found = strchr(str, '.'); - if(found != NULL){ - // substitution is required. perform the substitution on a copy - // of the string. This is slower but it is thread safe. - char *copy = sass_copy_c_string(str); - *(copy + (found - str)) = separator; - double res = strtod(copy, NULL); - free(copy); - return res; - } - } - - return strtod(str, NULL); - } - - // helper for safe access to c_ctx - const char* safe_str (const char* str, const char* alt) { - return str == NULL ? alt : str; - } - - void free_string_array(char ** arr) { - if(!arr) - return; - - char **it = arr; - while (it && (*it)) { - free(*it); - ++it; - } - - free(arr); - } - - char **copy_strings(const std::vector& strings, char*** array, int skip) { - int num = static_cast(strings.size()) - skip; - char** arr = (char**) calloc(num + 1, sizeof(char*)); - if (arr == 0) - return *array = (char **)NULL; - - for(int i = 0; i < num; i++) { - arr[i] = (char*) malloc(sizeof(char) * (strings[i + skip].size() + 1)); - if (arr[i] == 0) { - free_string_array(arr); - return *array = (char **)NULL; - } - std::copy(strings[i + skip].begin(), strings[i + skip].end(), arr[i]); - arr[i][strings[i + skip].size()] = '\0'; - } - - arr[num] = 0; - return *array = arr; - } - - // read css string (handle multiline DELIM) - std::string read_css_string(const std::string& str, bool css) - { - if (!css) return str; - std::string out(""); - bool esc = false; - for (auto i : str) { - if (i == '\\') { - esc = ! esc; - } else if (esc && i == '\r') { - continue; - } else if (esc && i == '\n') { - out.resize (out.size () - 1); - esc = false; - continue; - } else { - esc = false; - } - out.push_back(i); - } - // happens when parsing does not correctly skip - // over escaped sequences for ie. interpolations - // one example: foo\#{interpolate} - // if (esc) out += '\\'; - return out; - } - - // double escape all escape sequences - // keep unescaped quotes and backslashes - std::string evacuate_escapes(const std::string& str) - { - std::string out(""); - bool esc = false; - for (auto i : str) { - if (i == '\\' && !esc) { - out += '\\'; - out += '\\'; - esc = true; - } else if (esc && i == '"') { - out += '\\'; - out += i; - esc = false; - } else if (esc && i == '\'') { - out += '\\'; - out += i; - esc = false; - } else if (esc && i == '\\') { - out += '\\'; - out += i; - esc = false; - } else { - esc = false; - out += i; - } - } - // happens when parsing does not correctly skip - // over escaped sequences for ie. interpolations - // one example: foo\#{interpolate} - // if (esc) out += '\\'; - return out; - } - - // bell characters are replaced with spaces - void newline_to_space(std::string& str) - { - std::replace(str.begin(), str.end(), '\n', ' '); - } - - // bell characters are replaced with spaces - // also eats spaces after line-feeds (ltrim) - std::string string_to_output(const std::string& str) - { - std::string out(""); - bool lf = false; - for (auto i : str) { - if (i == '\n') { - out += ' '; - lf = true; - } else if (!(lf && isspace(i))) { - out += i; - lf = false; - } - } - return out; - } - - std::string escape_string(const std::string& str) - { - std::string out(""); - for (auto i : str) { - if (i == '\n') { - out += "\\n"; - } else if (i == '\r') { - out += "\\r"; - } else if (i == '\t') { - out += "\\t"; - } else { - out += i; - } - } - return out; - } - - std::string comment_to_string(const std::string& text) - { - std::string str = ""; - size_t has = 0; - char prev = 0; - bool clean = false; - for (auto i : text) { - if (clean) { - if (i == '\n') { has = 0; } - else if (i == '\r') { has = 0; } - else if (i == '\t') { ++ has; } - else if (i == ' ') { ++ has; } - else if (i == '*') {} - else { - clean = false; - str += ' '; - if (prev == '*' && i == '/') str += "*/"; - else str += i; - } - } else if (i == '\n') { - clean = true; - } else if (i == '\r') { - clean = true; - } else { - str += i; - } - prev = i; - } - if (has) return str; - else return text; - } - - // find best quote_mark by detecting if the string contains any single - // or double quotes. When a single quote is found, we not we want a double - // quote as quote_mark. Otherwise we check if the string cotains any double - // quotes, which will trigger the use of single quotes as best quote_mark. - char detect_best_quotemark(const char* s, char qm) - { - // ensure valid fallback quote_mark - char quote_mark = qm && qm != '*' ? qm : '"'; - while (*s) { - // force double quotes as soon - // as one single quote is found - if (*s == '\'') { return '"'; } - // a single does not force quote_mark - // maybe we see a double quote later - else if (*s == '"') { quote_mark = '\''; } - ++ s; - } - return quote_mark; - } - - std::string read_hex_escapes(const std::string& s) - { - - std::string result; - bool skipped = false; - - for (size_t i = 0, L = s.length(); i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\' && !skipped) { - - // remember - skipped = true; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; - - if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (s[i + len] == ' ') ++ len; - - // assert invalid code points - if (cp == 0) cp = 0xFFFD; - // replace bell character - // if (cp == '\n') cp = 32; - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we shoud - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(u[m]); - - // skip some more chars? - i += len - 1; skipped = false; - - } - - else { - - skipped = false; - - result.push_back(s[i]); - - } - - } - - else { - - result.push_back(s[i]); - - } - - } - - return result; - - } - - std::string unquote(const std::string& s, char* qd, bool keep_utf8_sequences, bool strict) - { - - // not enough room for quotes - // no possibility to unquote - if (s.length() < 2) return s; - - char q; - bool skipped = false; - - // this is no guarantee that the unquoting will work - // what about whitespace before/after the quote_mark? - if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; - else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; - else return s; - - std::string unq; - unq.reserve(s.length()-2); - - for (size_t i = 1, L = s.length() - 1; i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\' && !skipped) { - // remember - skipped = true; - - // skip it - // ++ i; - - // if (i == L) break; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; - - // hex string? - if (keep_utf8_sequences) { - unq.push_back(s[i]); - } else if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (s[i + len] == ' ') ++ len; - - // assert invalid code points - if (cp == 0) cp = 0xFFFD; - // replace bell character - // if (cp == '\n') cp = 32; - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we shoud - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(u[m]); - - // skip some more chars? - i += len - 1; skipped = false; - - } - - - } - // check for unexpected delimiter - // be strict and throw error back - // else if (!skipped && q == s[i]) { - // // don't be that strict - // return s; - // // this basically always means an internal error and not users fault - // error("Unescaped delimiter in string to unquote found. [" + s + "]", ParserState("[UNQUOTE]")); - // } - else { - if (strict && !skipped) { - if (s[i] == q) return s; - } - skipped = false; - unq.push_back(s[i]); - } - - } - if (skipped) { return s; } - if (qd) *qd = q; - return unq; - - } - - std::string quote(const std::string& s, char q) - { - - // autodetect with fallback to given quote - q = detect_best_quotemark(s.c_str(), q); - - // return an empty quoted string - if (s.empty()) return std::string(2, q ? q : '"'); - - std::string quoted; - quoted.reserve(s.length()+2); - quoted.push_back(q); - - const char* it = s.c_str(); - const char* end = it + strlen(it) + 1; - while (*it && it < end) { - const char* now = it; - - if (*it == q) { - quoted.push_back('\\'); - } else if (*it == '\\') { - quoted.push_back('\\'); - } - - int cp = utf8::next(it, end); - - // in case of \r, check if the next in sequence - // is \n and then advance the iterator and skip \r - if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { - cp = utf8::next(it, end); - } - - if (cp == '\n') { - quoted.push_back('\\'); - quoted.push_back('a'); - // we hope we can remove this flag once we figure out - // why ruby sass has these different output behaviors - // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") - using namespace Prelexer; - if (alternatives < - Prelexer::char_range<'a', 'f'>, - Prelexer::char_range<'A', 'F'>, - Prelexer::char_range<'0', '9'>, - space - >(it) != NULL) { - quoted.push_back(' '); - } - } else if (cp < 127) { - quoted.push_back((char) cp); - } else { - while (now < it) { - quoted.push_back(*now); - ++ now; - } - } - } - - quoted.push_back(q); - return quoted; - } - - bool is_hex_doublet(double n) - { - return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || - n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || - n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || - n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; - } - - bool is_color_doublet(double r, double g, double b) - { - return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); - } - - bool peek_linefeed(const char* start) - { - using namespace Prelexer; - using namespace Constants; - return sequence < - zero_plus < - alternatives < - exactly <' '>, - exactly <'\t'>, - line_comment, - block_comment, - delimited_by < - slash_star, - star_slash, - false - > - > - >, - re_linebreak - >(start) != 0; - } - - namespace Util { - using std::string; - - std::string rtrim(const std::string &str) { - std::string trimmed = str; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) - { trimmed.erase(pos_ws + 1); } - else { trimmed.clear(); } - return trimmed; - } - - std::string normalize_underscores(const std::string& str) { - std::string normalized = str; - for(size_t i = 0, L = normalized.length(); i < L; ++i) { - if(normalized[i] == '_') { - normalized[i] = '-'; - } - } - return normalized; - } - - std::string normalize_decimals(const std::string& str) { - std::string prefix = "0"; - std::string normalized = str; - - return normalized[0] == '.' ? normalized.insert(0, prefix) : normalized; - } - - bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style) { - if (r == NULL) { - return false; - } - - Block_Obj b = r->block(); - - Selector_List_Ptr sl = Cast(r->selector()); - bool hasSelectors = sl ? sl->length() > 0 : false; - - if (!hasSelectors) { - return false; - } - - bool hasDeclarations = false; - bool hasPrintableChildBlocks = false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - return true; - } else if (Declaration_Ptr d = Cast(stm)) { - return isPrintable(d, style); - } else if (Has_Block_Ptr p = Cast(stm)) { - Block_Obj pChildBlock = p->block(); - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; - } - } else if (Comment_Ptr c = Cast(stm)) { - // keep for uncompressed - if (style != COMPRESSED) { - hasDeclarations = true; - } - // output style compressed - if (c->is_important()) { - hasDeclarations = c->is_important(); - } - } else { - hasDeclarations = true; - } - - if (hasDeclarations || hasPrintableChildBlocks) { - return true; - } - } - - return false; - } - - bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style) - { - return ! s->value().empty(); - } - - bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style) - { - return true; - } - - bool isPrintable(Declaration_Ptr d, Sass_Output_Style style) - { - Expression_Obj val = d->value(); - if (String_Quoted_Obj sq = Cast(val)) return isPrintable(sq.ptr(), style); - if (String_Constant_Obj sc = Cast(val)) return isPrintable(sc.ptr(), style); - return true; - } - - bool isPrintable(Supports_Block_Ptr f, Sass_Output_Style style) { - if (f == NULL) { - return false; - } - - Block_Obj b = f->block(); - - bool hasDeclarations = false; - bool hasPrintableChildBlocks = false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm) || Cast(stm)) { - hasDeclarations = true; - } - else if (Has_Block_Ptr b = Cast(stm)) { - Block_Obj pChildBlock = b->block(); - if (!b->is_invisible()) { - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; - } - } - } - - if (hasDeclarations || hasPrintableChildBlocks) { - return true; - } - } - - return false; - } - - bool isPrintable(Media_Block_Ptr m, Sass_Output_Style style) - { - if (m == 0) return false; - Block_Obj b = m->block(); - if (b == 0) return false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) return true; - else if (Cast(stm)) return true; - else if (Comment_Ptr c = Cast(stm)) { - if (isPrintable(c, style)) { - return true; - } - } - else if (Ruleset_Ptr r = Cast(stm)) { - if (isPrintable(r, style)) { - return true; - } - } - else if (Supports_Block_Ptr f = Cast(stm)) { - if (isPrintable(f, style)) { - return true; - } - } - else if (Media_Block_Ptr mb = Cast(stm)) { - if (isPrintable(mb, style)) { - return true; - } - } - else if (Has_Block_Ptr b = Cast(stm)) { - if (isPrintable(b->block(), style)) { - return true; - } - } - } - return false; - } - - bool isPrintable(Comment_Ptr c, Sass_Output_Style style) - { - // keep for uncompressed - if (style != COMPRESSED) { - return true; - } - // output style compressed - if (c->is_important()) { - return true; - } - // not printable - return false; - }; - - bool isPrintable(Block_Obj b, Sass_Output_Style style) { - if (!b) { - return false; - } - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm) || Cast(stm)) { - return true; - } - else if (Comment_Ptr c = Cast(stm)) { - if (isPrintable(c, style)) { - return true; - } - } - else if (Ruleset_Ptr r = Cast(stm)) { - if (isPrintable(r, style)) { - return true; - } - } - else if (Supports_Block_Ptr f = Cast(stm)) { - if (isPrintable(f, style)) { - return true; - } - } - else if (Media_Block_Ptr m = Cast(stm)) { - if (isPrintable(m, style)) { - return true; - } - } - else if (Has_Block_Ptr b = Cast(stm)) { - if (isPrintable(b->block(), style)) { - return true; - } - } - } - - return false; - } - - bool isAscii(const char chr) { - return unsigned(chr) < 128; - } - - } -} diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp deleted file mode 100644 index f23475fe0..000000000 --- a/src/libsass/src/util.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef SASS_UTIL_H -#define SASS_UTIL_H - -#include -#include -#include -#include "sass.hpp" -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#define SASS_ASSERT(cond, msg) assert(cond && msg) - -namespace Sass { - - double round(double val, size_t precision = 0); - double sass_strtod(const char* str); - const char* safe_str(const char *, const char* = ""); - void free_string_array(char **); - char **copy_strings(const std::vector&, char ***, int = 0); - std::string read_css_string(const std::string& str, bool css = true); - std::string evacuate_escapes(const std::string& str); - std::string string_to_output(const std::string& str); - std::string comment_to_string(const std::string& text); - std::string read_hex_escapes(const std::string& str); - std::string escape_string(const std::string& str); - void newline_to_space(std::string& str); - - std::string quote(const std::string&, char q = 0); - std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); - char detect_best_quotemark(const char* s, char qm = '"'); - - bool is_hex_doublet(double n); - bool is_color_doublet(double r, double g, double b); - - bool peek_linefeed(const char* start); - - namespace Util { - - std::string rtrim(const std::string& str); - - std::string normalize_underscores(const std::string& str); - std::string normalize_decimals(const std::string& str); - - bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); - bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); - bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); - bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); - bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); - bool isAscii(const char chr); - - } -} -#endif diff --git a/src/libsass/src/values.cpp b/src/libsass/src/values.cpp deleted file mode 100644 index 0f2fd48d7..000000000 --- a/src/libsass/src/values.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "sass.hpp" -#include "sass.h" -#include "values.hpp" - -#include - -namespace Sass { - - // convert value from C++ side to C-API - union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val) - { - if (val->concrete_type() == Expression::NUMBER) - { - Number_Ptr_Const res = Cast(val); - return sass_make_number(res->value(), res->unit().c_str()); - } - else if (val->concrete_type() == Expression::COLOR) - { - Color_Ptr_Const col = Cast(val); - return sass_make_color(col->r(), col->g(), col->b(), col->a()); - } - else if (val->concrete_type() == Expression::LIST) - { - List_Ptr_Const l = Cast(val); - union Sass_Value* list = sass_make_list(l->size(), l->separator(), l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - Expression_Obj obj = l->at(i); - auto val = ast_node_to_sass_value(obj); - sass_list_set_value(list, i, val); - } - return list; - } - else if (val->concrete_type() == Expression::MAP) - { - Map_Ptr_Const m = Cast(val); - union Sass_Value* map = sass_make_map(m->length()); - size_t i = 0; for (Expression_Obj key : m->keys()) { - sass_map_set_key(map, i, ast_node_to_sass_value(key)); - sass_map_set_value(map, i, ast_node_to_sass_value(m->at(key))); - ++ i; - } - return map; - } - else if (val->concrete_type() == Expression::NULL_VAL) - { - return sass_make_null(); - } - else if (val->concrete_type() == Expression::BOOLEAN) - { - Boolean_Ptr_Const res = Cast(val); - return sass_make_boolean(res->value()); - } - else if (val->concrete_type() == Expression::STRING) - { - if (String_Quoted_Ptr_Const qstr = Cast(val)) - { - return sass_make_qstring(qstr->value().c_str()); - } - else if (String_Constant_Ptr_Const cstr = Cast(val)) - { - return sass_make_string(cstr->value().c_str()); - } - } - return sass_make_error("unknown sass value type"); - } - - // convert value from C-API to C++ side - Value_Ptr sass_value_to_ast_node (const union Sass_Value* val) - { - switch (sass_value_get_tag(val)) { - case SASS_NUMBER: - return SASS_MEMORY_NEW(Number, - ParserState("[C-VALUE]"), - sass_number_get_value(val), - sass_number_get_unit(val)); - case SASS_BOOLEAN: - return SASS_MEMORY_NEW(Boolean, - ParserState("[C-VALUE]"), - sass_boolean_get_value(val)); - case SASS_COLOR: - return SASS_MEMORY_NEW(Color, - ParserState("[C-VALUE]"), - sass_color_get_r(val), - sass_color_get_g(val), - sass_color_get_b(val), - sass_color_get_a(val)); - case SASS_STRING: - if (sass_string_is_quoted(val)) { - return SASS_MEMORY_NEW(String_Quoted, - ParserState("[C-VALUE]"), - sass_string_get_value(val)); - } - return SASS_MEMORY_NEW(String_Constant, - ParserState("[C-VALUE]"), - sass_string_get_value(val)); - case SASS_LIST: { - List_Ptr l = SASS_MEMORY_NEW(List, - ParserState("[C-VALUE]"), - sass_list_get_length(val), - sass_list_get_separator(val)); - for (size_t i = 0, L = sass_list_get_length(val); i < L; ++i) { - l->append(sass_value_to_ast_node(sass_list_get_value(val, i))); - } - l->is_bracketed(sass_list_get_is_bracketed(val)); - return l; - } - case SASS_MAP: { - Map_Ptr m = SASS_MEMORY_NEW(Map, ParserState("[C-VALUE]")); - for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) { - *m << std::make_pair( - sass_value_to_ast_node(sass_map_get_key(val, i)), - sass_value_to_ast_node(sass_map_get_value(val, i))); - } - return m; - } - case SASS_NULL: - return SASS_MEMORY_NEW(Null, ParserState("[C-VALUE]")); - case SASS_ERROR: - return SASS_MEMORY_NEW(Custom_Error, - ParserState("[C-VALUE]"), - sass_error_get_message(val)); - case SASS_WARNING: - return SASS_MEMORY_NEW(Custom_Warning, - ParserState("[C-VALUE]"), - sass_warning_get_message(val)); - default: break; - } - return 0; - } - -} diff --git a/src/libsass/src/values.hpp b/src/libsass/src/values.hpp deleted file mode 100644 index f78ca1281..000000000 --- a/src/libsass/src/values.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SASS_VALUES_H -#define SASS_VALUES_H - -#include "ast.hpp" - -namespace Sass { - - union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val); - Value_Ptr sass_value_to_ast_node (const union Sass_Value* val); - -} -#endif diff --git a/src/libsass/test/test_node.cpp b/src/libsass/test/test_node.cpp deleted file mode 100644 index 905dc1899..000000000 --- a/src/libsass/test/test_node.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -#include "node.hpp" -#include "parser.hpp" - - -#define STATIC_ARRAY_SIZE(array) (sizeof((array))/sizeof((array[0]))) - - -namespace Sass { - - Context ctx = Context::Data(); - - const char* const ROUNDTRIP_TESTS[] = { - NULL, - "~", - "CMPD", - "~ CMPD", - "CMPD >", - "> > CMPD", - "CMPD ~ ~", - "> + CMPD1.CMPD2 > ~", - "> + CMPD1.CMPD2 CMPD3.CMPD4 > ~", - "+ CMPD1 CMPD2 ~ CMPD3 + CMPD4 > CMPD5 > ~" - }; - - - - static Complex_Selector* createComplexSelector(std::string src) { - std::string temp(src); - temp += ";"; - return (*Parser::from_c_str(temp.c_str(), ctx, "", Position()).parse_selector_list())[0]; - } - - - void roundtripTest(const char* toTest) { - - // Create the initial selector - - Complex_Selector* pOrigSelector = NULL; - if (toTest) { - pOrigSelector = createComplexSelector(toTest); - } - - std::string expected(pOrigSelector ? pOrigSelector->to_string() : "NULL"); - - - // Roundtrip the selector into a node and back - - Node node = complexSelectorToNode(pOrigSelector, ctx); - - std::stringstream nodeStringStream; - nodeStringStream << node; - std::string nodeString = nodeStringStream.str(); - cout << "ASNODE: " << node << endl; - - Complex_Selector* pNewSelector = nodeToComplexSelector(node, ctx); - - // Show the result - - std::string result(pNewSelector ? pNewSelector->to_string() : "NULL"); - - cout << "SELECTOR: " << expected << endl; - cout << "NEW SELECTOR: " << result << endl; - - - // Test that they are equal using the equality operator - - assert( (!pOrigSelector && !pNewSelector ) || (pOrigSelector && pNewSelector) ); - if (pOrigSelector) { - assert( *pOrigSelector == *pNewSelector ); - } - - - // Test that they are equal by comparing the string versions of the selectors - - assert(expected == result); - - } - - - int main() { - for (int index = 0; index < STATIC_ARRAY_SIZE(ROUNDTRIP_TESTS); index++) { - const char* const toTest = ROUNDTRIP_TESTS[index]; - cout << "\nINPUT STRING: " << (toTest ? toTest : "NULL") << endl; - roundtripTest(toTest); - } - - cout << "\nTesting Done.\n"; - } - - -} diff --git a/src/libsass/test/test_paths.cpp b/src/libsass/test/test_paths.cpp deleted file mode 100644 index bfcf8ec6d..000000000 --- a/src/libsass/test/test_paths.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include "../paths.hpp" - -using namespace Sass; - -template -std::vector& operator<<(std::vector& v, const T& e) -{ - v.push_back(e); - return v; -} - -int main() -{ - std::vector v1, v2, v3; - v1 << 1 << 2; - v2 << 3; - v3 << 4 << 5 << 6; - - std::vector > ss; - ss << v1 << v2 << v3; - - std::vector > ps = paths(ss); - for (size_t i = 0, S = ps.size(); i < S; ++i) { - std::cout << vector_to_string(ps[i]) << std::endl; - } - return 0; -} diff --git a/src/libsass/test/test_selector_difference.cpp b/src/libsass/test/test_selector_difference.cpp deleted file mode 100644 index e2880c0b0..000000000 --- a/src/libsass/test/test_selector_difference.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include -#include - -using namespace Sass; - -Context ctx = Context::Data(); - -Compound_Selector* selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } - -void diff(std::string s, std::string t) -{ - std::cout << s << " - " << t << " = " << selector(s + ";")->minus(selector(t + ";"), ctx)->to_string() << std::endl; -} - -int main() -{ - diff(".a.b.c", ".c.b"); - diff(".a.b.c", ".fludge.b"); - - return 0; -} diff --git a/src/libsass/test/test_specificity.cpp b/src/libsass/test/test_specificity.cpp deleted file mode 100644 index ba9bbfc46..000000000 --- a/src/libsass/test/test_specificity.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include -#include - -using namespace Sass; - -Context ctx = Context::Data(); - -Selector* selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_selector_list(); } - -void spec(std::string sel) -{ std::cout << sel << "\t::\t" << selector(sel + ";")->specificity() << std::endl; } - -int main() -{ - spec("foo bar hux"); - spec(".foo .bar hux"); - spec("#foo .bar[hux='mux']"); - spec("a b c d e f"); - - return 0; -} diff --git a/src/libsass/test/test_subset_map.cpp b/src/libsass/test/test_subset_map.cpp deleted file mode 100644 index 37945143f..000000000 --- a/src/libsass/test/test_subset_map.cpp +++ /dev/null @@ -1,472 +0,0 @@ -#include -#include -#include -#include "../subset_map.hpp" - -Subset_Map ssm; - -string toString(std::vector v); -string toString(std::vector>> v); -void assertEqual(string std::sExpected, std::string sResult); - -void setup() { - ssm.clear(); - - //@ssm[Set[1, 2]] = "Foo" - std::vector s1; - s1.push_back("1"); - s1.push_back("2"); - ssm.put(s1, "Foo"); - - //@ssm[Set["fizz", "fazz"]] = "Bar" - std::vector s2; - s2.push_back("fizz"); - s2.push_back("fazz"); - ssm.put(s2, "Bar"); - - //@ssm[Set[:foo, :bar]] = "Baz" - std::vector s3; - s3.push_back(":foo"); - s3.push_back(":bar"); - ssm.put(s3, "Baz"); - - //@ssm[Set[:foo, :bar, :baz]] = "Bang" - std::vector s4; - s4.push_back(":foo"); - s4.push_back(":bar"); - s4.push_back(":baz"); - ssm.put(s4, "Bang"); - - //@ssm[Set[:bip, :bop, :blip]] = "Qux" - std::vector s5; - s5.push_back(":bip"); - s5.push_back(":bop"); - s5.push_back(":blip"); - ssm.put(s5, "Qux"); - - //@ssm[Set[:bip, :bop]] = "Thram" - std::vector s6; - s6.push_back(":bip"); - s6.push_back(":bop"); - ssm.put(s6, "Thram"); -} - -void testEqualKeys() { - std::cout << "testEqualKeys" << std::endl; - - //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2]) - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); - - //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz"]) - std::vector k2; - k2.push_back("fizz"); - k2.push_back("fazz"); - assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); - - std::cout << std::endl; -} - -void testSubsetKeys() { - std::cout << "testSubsetKeys" << std::endl; - - //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2, "fuzz"]) - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - k1.push_back("fuzz"); - assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); - - //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz", 3]) - std::vector k2; - k2.push_back("fizz"); - k2.push_back("fazz"); - k2.push_back("3"); - assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); - - std::cout << std::endl; -} - -void testSupersetKeys() { - std::cout << "testSupersetKeys" << std::endl; - - //assert_equal [], @ssm.get(Set[1]) - std::vector k1; - k1.push_back("1"); - assertEqual("[]", toString(ssm.get_kv(k1))); - - //assert_equal [], @ssm.get(Set[2]) - std::vector k2; - k2.push_back("2"); - assertEqual("[]", toString(ssm.get_kv(k2))); - - //assert_equal [], @ssm.get(Set["fizz"]) - std::vector k3; - k3.push_back("fizz"); - assertEqual("[]", toString(ssm.get_kv(k3))); - - //assert_equal [], @ssm.get(Set["fazz"]) - std::vector k4; - k4.push_back("fazz"); - assertEqual("[]", toString(ssm.get_kv(k4))); - - std::cout << std::endl; -} - -void testDisjointKeys() { - std::cout << "testDisjointKeys" << std::endl; - - //assert_equal [], @ssm.get(Set[3, 4]) - std::vector k1; - k1.push_back("3"); - k1.push_back("4"); - assertEqual("[]", toString(ssm.get_kv(k1))); - - //assert_equal [], @ssm.get(Set["fuzz", "frizz"]) - std::vector k2; - k2.push_back("fuzz"); - k2.push_back("frizz"); - assertEqual("[]", toString(ssm.get_kv(k2))); - - //assert_equal [], @ssm.get(Set["gran", 15]) - std::vector k3; - k3.push_back("gran"); - k3.push_back("15"); - assertEqual("[]", toString(ssm.get_kv(k3))); - - std::cout << std::endl; -} - -void testSemiDisjointKeys() { - std::cout << "testSemiDisjointKeys" << std::endl; - - //assert_equal [], @ssm.get(Set[2, 3]) - std::vector k1; - k1.push_back("2"); - k1.push_back("3"); - assertEqual("[]", toString(ssm.get_kv(k1))); - - //assert_equal [], @ssm.get(Set["fizz", "fuzz"]) - std::vector k2; - k2.push_back("fizz"); - k2.push_back("fuzz"); - assertEqual("[]", toString(ssm.get_kv(k2))); - - //assert_equal [], @ssm.get(Set[1, "fazz"]) - std::vector k3; - k3.push_back("1"); - k3.push_back("fazz"); - assertEqual("[]", toString(ssm.get_kv(k3))); - - std::cout << std::endl; -} - -void testEmptyKeySet() { - std::cout << "testEmptyKeySet" << std::endl; - - //assert_raises(ArgumentError) {@ssm[Set[]] = "Fail"} - std::vector s1; - try { - ssm.put(s1, "Fail"); - } - catch (const char* &e) { - assertEqual("internal error: subset map keys may not be empty", e); - } -} - -void testEmptyKeyGet() { - std::cout << "testEmptyKeyGet" << std::endl; - - //assert_equal [], @ssm.get(Set[]) - std::vector k1; - assertEqual("[]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} -void testMultipleSubsets() { - std::cout << "testMultipleSubsets" << std::endl; - - //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, "fizz", "fazz"]) - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - k1.push_back("fizz"); - k1.push_back("fazz"); - assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k1))); - - //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, 3, "fizz", "fazz", "fuzz"]) - std::vector k2; - k2.push_back("1"); - k2.push_back("2"); - k2.push_back("3"); - k2.push_back("fizz"); - k2.push_back("fazz"); - k2.push_back("fuzz"); - assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); - - //assert_equal [["Baz", Set[:foo, :bar]]], @ssm.get(Set[:foo, :bar]) - std::vector k3; - k3.push_back(":foo"); - k3.push_back(":bar"); - assertEqual("[[Baz, Set[:foo, :bar]]]", toString(ssm.get_kv(k3))); - - //assert_equal [["Baz", Set[:foo, :bar]], ["Bang", Set[:foo, :bar, :baz]]], @ssm.get(Set[:foo, :bar, :baz]) - std::vector k4; - k4.push_back(":foo"); - k4.push_back(":bar"); - k4.push_back(":baz"); - assertEqual("[[Baz, Set[:foo, :bar]], [Bang, Set[:foo, :bar, :baz]]]", toString(ssm.get_kv(k4))); - - std::cout << std::endl; -} -void testBracketBracket() { - std::cout << "testBracketBracket" << std::endl; - - //assert_equal ["Foo"], @ssm[Set[1, 2, "fuzz"]] - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - k1.push_back("fuzz"); - assertEqual("[Foo]", toString(ssm.get_v(k1))); - - //assert_equal ["Baz", "Bang"], @ssm[Set[:foo, :bar, :baz]] - std::vector k2; - k2.push_back(":foo"); - k2.push_back(":bar"); - k2.push_back(":baz"); - assertEqual("[Baz, Bang]", toString(ssm.get_v(k2))); - - std::cout << std::endl; -} - -void testKeyOrder() { - std::cout << "testEqualKeys" << std::endl; - - //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[2, 1]) - std::vector k1; - k1.push_back("2"); - k1.push_back("1"); - assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} - -void testOrderPreserved() { - std::cout << "testOrderPreserved" << std::endl; - //@ssm[Set[10, 11, 12]] = 1 - std::vector s1; - s1.push_back("10"); - s1.push_back("11"); - s1.push_back("12"); - ssm.put(s1, "1"); - - //@ssm[Set[10, 11]] = 2 - std::vector s2; - s2.push_back("10"); - s2.push_back("11"); - ssm.put(s2, "2"); - - //@ssm[Set[11]] = 3 - std::vector s3; - s3.push_back("11"); - ssm.put(s3, "3"); - - //@ssm[Set[11, 12]] = 4 - std::vector s4; - s4.push_back("11"); - s4.push_back("12"); - ssm.put(s4, "4"); - - //@ssm[Set[9, 10, 11, 12, 13]] = 5 - std::vector s5; - s5.push_back("9"); - s5.push_back("10"); - s5.push_back("11"); - s5.push_back("12"); - s5.push_back("13"); - ssm.put(s5, "5"); - - //@ssm[Set[10, 13]] = 6 - std::vector s6; - s6.push_back("10"); - s6.push_back("13"); - ssm.put(s6, "6"); - - //assert_equal([[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]], @ssm.get(Set[9, 10, 11, 12, 13])) - std::vector k1; - k1.push_back("9"); - k1.push_back("10"); - k1.push_back("11"); - k1.push_back("12"); - k1.push_back("13"); - assertEqual("[[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} -void testMultipleEqualValues() { - std::cout << "testMultipleEqualValues" << std::endl; - //@ssm[Set[11, 12]] = 1 - std::vector s1; - s1.push_back("11"); - s1.push_back("12"); - ssm.put(s1, "1"); - - //@ssm[Set[12, 13]] = 2 - std::vector s2; - s2.push_back("12"); - s2.push_back("13"); - ssm.put(s2, "2"); - - //@ssm[Set[13, 14]] = 1 - std::vector s3; - s3.push_back("13"); - s3.push_back("14"); - ssm.put(s3, "1"); - - //@ssm[Set[14, 15]] = 1 - std::vector s4; - s4.push_back("14"); - s4.push_back("15"); - ssm.put(s4, "1"); - - //assert_equal([[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]], @ssm.get(Set[11, 12, 13, 14, 15])) - std::vector k1; - k1.push_back("11"); - k1.push_back("12"); - k1.push_back("13"); - k1.push_back("14"); - k1.push_back("15"); - assertEqual("[[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} - -int main() -{ - std::vector s1; - s1.push_back("1"); - s1.push_back("2"); - - std::vector s2; - s2.push_back("2"); - s2.push_back("3"); - - std::vector s3; - s3.push_back("3"); - s3.push_back("4"); - - ssm.put(s1, "value1"); - ssm.put(s2, "value2"); - ssm.put(s3, "value3"); - - std::vector s4; - s4.push_back("1"); - s4.push_back("2"); - s4.push_back("3"); - - std::vector > > fetched(ssm.get_kv(s4)); - - std::cout << "PRINTING RESULTS:" << std::endl; - for (size_t i = 0, S = fetched.size(); i < S; ++i) { - std::cout << fetched[i].first << std::endl; - } - - Subset_Map ssm2; - ssm2.put(s1, "foo"); - ssm2.put(s2, "bar"); - ssm2.put(s4, "hux"); - - std::vector > > fetched2(ssm2.get_kv(s4)); - - std::cout << std::endl << "PRINTING RESULTS:" << std::endl; - for (size_t i = 0, S = fetched2.size(); i < S; ++i) { - std::cout << fetched2[i].first << std::endl; - } - - std::cout << "TRYING ON A SELECTOR-LIKE OBJECT" << std::endl; - - Subset_Map sel_ssm; - std::vector target; - target.push_back("desk"); - target.push_back(".wood"); - - std::vector actual; - actual.push_back("desk"); - actual.push_back(".wood"); - actual.push_back(".mine"); - - sel_ssm.put(target, "has-aquarium"); - std::vector > > fetched3(sel_ssm.get_kv(actual)); - std::cout << "RESULTS:" << std::endl; - for (size_t i = 0, S = fetched3.size(); i < S; ++i) { - std::cout << fetched3[i].first << std::endl; - } - - std::cout << std::endl; - - // BEGIN PORTED RUBY TESTS FROM /test/sass/util/subset_map_test.rb - - setup(); - testEqualKeys(); - testSubsetKeys(); - testSupersetKeys(); - testDisjointKeys(); - testSemiDisjointKeys(); - testEmptyKeySet(); - testEmptyKeyGet(); - testMultipleSubsets(); - testBracketBracket(); - testKeyOrder(); - - setup(); - testOrderPreserved(); - - setup(); - testMultipleEqualValues(); - - return 0; -} - -string toString(std::vector>> v) -{ - std::stringstream buffer; - buffer << "["; - for (size_t i = 0, S = v.size(); i < S; ++i) { - buffer << "[" << v[i].first; - buffer << ", Set["; - for (size_t j = 0, S = v[i].second.size(); j < S; ++j) { - buffer << v[i].second[j]; - if (j < S-1) { - buffer << ", "; - } - } - buffer << "]]"; - if (i < S-1) { - buffer << ", "; - } - } - buffer << "]"; - return buffer.str(); -} - -string toString(std::vector v) -{ - std::stringstream buffer; - buffer << "["; - for (size_t i = 0, S = v.size(); i < S; ++i) { - buffer << v[i]; - if (i < S-1) { - buffer << ", "; - } - } - buffer << "]"; - return buffer.str(); -} - -void assertEqual(string sExpected, string sResult) { - std::cout << "Expected: " << sExpected << std::endl; - std::cout << "Result: " << sResult << std::endl; - assert(sExpected == sResult); -} diff --git a/src/libsass/test/test_superselector.cpp b/src/libsass/test/test_superselector.cpp deleted file mode 100644 index bf21c7c4d..000000000 --- a/src/libsass/test/test_superselector.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include - -using namespace Sass; - -Context ctx = Context(Context::Data()); - -Compound_Selector* compound_selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } - -Complex_Selector* complex_selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_complex_selector(false); } - -void check_compound(std::string s1, std::string s2) -{ - std::cout << "Is " - << s1 - << " a superselector of " - << s2 - << "?\t" - << compound_selector(s1 + ";")->is_superselector_of(compound_selector(s2 + ";")) - << std::endl; -} - -void check_complex(std::string s1, std::string s2) -{ - std::cout << "Is " - << s1 - << " a superselector of " - << s2 - << "?\t" - << complex_selector(s1 + ";")->is_superselector_of(complex_selector(s2 + ";")) - << std::endl; -} - -int main() -{ - check_compound(".foo", ".foo.bar"); - check_compound(".foo.bar", ".foo"); - check_compound(".foo.bar", "div.foo"); - check_compound(".foo", "div.foo"); - check_compound("div.foo", ".foo"); - check_compound("div.foo", "div.bar.foo"); - check_compound("p.foo", "div.bar.foo"); - check_compound(".hux", ".mumble"); - - std::cout << std::endl; - - check_complex(".foo ~ .bar", ".foo + .bar"); - check_complex(".foo .bar", ".foo + .bar"); - check_complex(".foo .bar", ".foo > .bar"); - check_complex(".foo .bar > .hux", ".foo.a .bar.b > .hux"); - check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .hux"); - check_complex(".foo", ".bar .foo"); - check_complex(".foo", ".foo.a"); - check_complex(".foo.bar", ".foo"); - check_complex(".foo .bar .hux", ".bar .hux"); - check_complex(".foo ~ .bar .hux.x", ".foo.a + .bar.b > .hux.y"); - check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .mumble"); - check_complex(".foo + .bar", ".foo ~ .bar"); - check_complex("a c e", "a b c d e"); - check_complex("c a e", "a b c d e"); - - return 0; -} - - diff --git a/src/libsass/test/test_unification.cpp b/src/libsass/test/test_unification.cpp deleted file mode 100644 index 5c663ee90..000000000 --- a/src/libsass/test/test_unification.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include - -using namespace Sass; - -Context ctx = Context(Context::Data()); - -Compound_Selector* selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } - -void unify(std::string lhs, std::string rhs) -{ - Compound_Selector* unified = selector(lhs + ";")->unify_with(selector(rhs + ";"), ctx); - std::cout << lhs << " UNIFIED WITH " << rhs << " =\t" << (unified ? unified->to_string() : "NOTHING") << std::endl; -} - -int main() -{ - unify(".foo", ".foo.bar"); - unify("div:nth-of-type(odd)", "div:first-child"); - unify("div", "span:whatever"); - unify("div", "span"); - unify("foo:bar::after", "foo:bar::first-letter"); - unify(".foo#bar.hux", ".hux.foo#bar"); - unify(".foo#bar.hux", ".hux.foo#baz"); - unify("*:blah:fudge", "p:fudge:blah"); - - return 0; -} diff --git a/src/libsass/version.sh b/src/libsass/version.sh deleted file mode 100755 index 281de74d7..000000000 --- a/src/libsass/version.sh +++ /dev/null @@ -1,10 +0,0 @@ -if test "x$LIBSASS_VERSION" = "x"; then - LIBSASS_VERSION=`git describe --abbrev=4 --dirty --always --tags 2>/dev/null` -fi -if test "x$LIBSASS_VERSION" = "x"; then - LIBSASS_VERSION=`cat VERSION 2>/dev/null` -fi -if test "x$LIBSASS_VERSION" = "x"; then - LIBSASS_VERSION="[na]" -fi -echo $LIBSASS_VERSION diff --git a/src/libsass/win/libsass.sln b/src/libsass/win/libsass.sln deleted file mode 100644 index 2a55ad87e..000000000 --- a/src/libsass/win/libsass.sln +++ /dev/null @@ -1,39 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsass", "libsass.vcxproj", "{E4030474-AFC9-4CC6-BEB6-D846F631502B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".SolutionItems", ".SolutionItems", "{33318C77-2391-4399-8118-C109155A4A75}" - ProjectSection(SolutionItems) = preProject - ..\.editorconfig = ..\.editorconfig - ..\.gitattributes = ..\.gitattributes - ..\.gitignore = ..\.gitignore - ..\.travis.yml = ..\.travis.yml - ..\appveyor.yml = ..\appveyor.yml - ..\Readme.md = ..\Readme.md - ..\res\resource.rc = ..\res\resource.rc - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|Win64 = Debug|Win64 - Release|Win32 = Release|Win32 - Release|Win64 = Release|Win64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.ActiveCfg = Debug|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.Build.0 = Debug|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.ActiveCfg = Debug|x64 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.Build.0 = Debug|x64 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.ActiveCfg = Release|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.Build.0 = Release|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.ActiveCfg = Release|x64 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/libsass/win/libsass.sln.DotSettings b/src/libsass/win/libsass.sln.DotSettings deleted file mode 100644 index 405024e15..000000000 --- a/src/libsass/win/libsass.sln.DotSettings +++ /dev/null @@ -1,9 +0,0 @@ - - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded \ No newline at end of file diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets deleted file mode 100644 index c1c7d45f3..000000000 --- a/src/libsass/win/libsass.targets +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libsass/win/libsass.vcxproj b/src/libsass/win/libsass.vcxproj deleted file mode 100644 index 8cfd61f2a..000000000 --- a/src/libsass/win/libsass.vcxproj +++ /dev/null @@ -1,188 +0,0 @@ - - - - [NA] - ..\src - ..\src - ..\include - - - - - - - - - - %(PreprocessorDefinitions);LIBSASS_VERSION="$(LIBSASS_VERSION)"; - - - - - - - - - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - {E4030474-AFC9-4CC6-BEB6-D846F631502B} - Win32Proj - libsass - - - libsass - Unicode - - - DynamicLibrary - ADD_EXPORTS;$(PreprocessorDefinitions); - - - StaticLibrary - - - v120 - - - v140 - - - true - - - true - - - false - true - - - false - true - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)bin\Debug\ - $(SolutionDir)bin\Debug\obj\ - - - true - $(SolutionDir)bin\Debug\ - $(SolutionDir)bin\Debug\obj\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)bin\obj\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)bin\obj\ - - - - ..\include;%(AdditionalIncludeDirectories) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); - - - Console - true - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); - - - Console - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); - - - Console - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); - - - Console - true - true - true - - - - - - - diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters deleted file mode 100644 index 980f00f3f..000000000 --- a/src/libsass/win/libsass.vcxproj.filters +++ /dev/null @@ -1,357 +0,0 @@ - - - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {bb9c270d-e9f5-49bf-afda-771a1a4bb5b7} - h;hh;hpp;hxx;hm;in;inl;inc;xsd - - - - - Include Headers - - - Include Headers - - - Include Headers - - - Include Headers - - - Include Headers - - - Include Headers - - - Include Headers - - - Include Headers - - - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - Headers - - - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Source Files - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - Sources - - - From 912301673420cdd8fbc1efcd09037206539915ee Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Nov 2018 16:24:04 +1100 Subject: [PATCH 173/286] Squashed 'src/libsass/' content from commit 39e30874 git-subtree-dir: src/libsass git-subtree-split: 39e30874b9a5dd6a802c20e8b0470ba44eeba929 --- .editorconfig | 15 + .gitattributes | 2 + .github/CONTRIBUTING.md | 65 + .github/ISSUE_TEMPLATE.md | 54 + .gitignore | 85 + .travis.yml | 64 + COPYING | 25 + GNUmakefile.am | 74 + INSTALL | 1 + LICENSE | 25 + Makefile | 351 ++++ Makefile.conf | 55 + Readme.md | 104 + SECURITY.md | 10 + appveyor.yml | 91 + configure.ac | 134 ++ contrib/libsass.spec | 66 + contrib/plugin.cpp | 60 + docs/README.md | 20 + docs/api-context-example.md | 45 + docs/api-context-internal.md | 163 ++ docs/api-context.md | 295 +++ docs/api-doc.md | 215 ++ docs/api-function-example.md | 67 + docs/api-function-internal.md | 8 + docs/api-function.md | 74 + docs/api-importer-example.md | 112 + docs/api-importer-internal.md | 20 + docs/api-importer.md | 86 + docs/api-value-example.md | 55 + docs/api-value-internal.md | 76 + docs/api-value.md | 154 ++ docs/build-on-darwin.md | 27 + docs/build-on-gentoo.md | 55 + docs/build-on-windows.md | 139 ++ docs/build-shared-library.md | 35 + docs/build-with-autotools.md | 78 + docs/build-with-makefiles.md | 68 + docs/build-with-mingw.md | 107 + docs/build-with-visual-studio.md | 90 + docs/build.md | 97 + docs/compatibility-plan.md | 48 + docs/contributing.md | 17 + docs/custom-functions-internal.md | 122 ++ docs/dev-ast-memory.md | 223 ++ docs/implementations.md | 65 + docs/plugins.md | 47 + docs/setup-environment.md | 68 + docs/source-map-internals.md | 51 + docs/trace.md | 26 + docs/triage.md | 17 + docs/unicode.md | 45 + extconf.rb | 6 + include/sass.h | 15 + include/sass/base.h | 89 + include/sass/context.h | 170 ++ include/sass/functions.h | 139 ++ include/sass/values.h | 145 ++ include/sass/version.h | 12 + include/sass/version.h.in | 12 + include/sass2scss.h | 120 ++ m4/.gitkeep | 0 m4/m4-ax_cxx_compile_stdcxx_11.m4 | 167 ++ res/resource.rc | 35 + script/bootstrap | 13 + script/branding | 10 + script/ci-build-libsass | 134 ++ script/ci-build-plugin | 62 + script/ci-install-compiler | 6 + script/ci-install-deps | 20 + script/ci-report-coverage | 42 + script/spec | 5 + script/tap-driver | 652 ++++++ script/tap-runner | 1 + script/test-leaks.pl | 103 + src/GNUmakefile.am | 54 + src/ast.cpp | 2226 ++++++++++++++++++++ src/ast.hpp | 3049 ++++++++++++++++++++++++++++ src/ast_def_macros.hpp | 80 + src/ast_fwd_decl.cpp | 29 + src/ast_fwd_decl.hpp | 463 +++++ src/b64/cencode.h | 32 + src/b64/encode.h | 79 + src/backtrace.cpp | 46 + src/backtrace.hpp | 29 + src/base64vlq.cpp | 44 + src/base64vlq.hpp | 30 + src/bind.cpp | 311 +++ src/bind.hpp | 13 + src/c99func.c | 54 + src/cencode.c | 108 + src/check_nesting.cpp | 398 ++++ src/check_nesting.hpp | 65 + src/color_maps.cpp | 648 ++++++ src/color_maps.hpp | 331 +++ src/constants.cpp | 179 ++ src/constants.hpp | 181 ++ src/context.cpp | 880 ++++++++ src/context.hpp | 152 ++ src/cssize.cpp | 606 ++++++ src/cssize.hpp | 77 + src/debug.hpp | 43 + src/debugger.hpp | 801 ++++++++ src/emitter.cpp | 297 +++ src/emitter.hpp | 99 + src/environment.cpp | 246 +++ src/environment.hpp | 113 ++ src/error_handling.cpp | 235 +++ src/error_handling.hpp | 216 ++ src/eval.cpp | 1663 +++++++++++++++ src/eval.hpp | 103 + src/expand.cpp | 817 ++++++++ src/expand.hpp | 82 + src/extend.cpp | 2130 ++++++++++++++++++++ src/extend.hpp | 86 + src/file.cpp | 485 +++++ src/file.hpp | 133 ++ src/functions.cpp | 2234 ++++++++++++++++++++ src/functions.hpp | 198 ++ src/inspect.cpp | 1138 +++++++++++ src/inspect.hpp | 103 + src/json.cpp | 1436 +++++++++++++ src/json.hpp | 117 ++ src/kwd_arg_macros.hpp | 28 + src/lexer.cpp | 181 ++ src/lexer.hpp | 315 +++ src/listize.cpp | 86 + src/listize.hpp | 34 + src/mapping.hpp | 18 + src/memory/SharedPtr.cpp | 114 ++ src/memory/SharedPtr.hpp | 206 ++ src/node.cpp | 319 +++ src/node.hpp | 118 ++ src/operation.hpp | 173 ++ src/operators.cpp | 267 +++ src/operators.hpp | 30 + src/output.cpp | 336 +++ src/output.hpp | 54 + src/parser.cpp | 3137 +++++++++++++++++++++++++++++ src/parser.hpp | 400 ++++ src/paths.hpp | 71 + src/plugins.cpp | 184 ++ src/plugins.hpp | 57 + src/position.cpp | 181 ++ src/position.hpp | 124 ++ src/prelexer.cpp | 1774 ++++++++++++++++ src/prelexer.hpp | 484 +++++ src/remove_placeholders.cpp | 84 + src/remove_placeholders.hpp | 35 + src/sass.cpp | 151 ++ src/sass.hpp | 139 ++ src/sass2scss.cpp | 864 ++++++++ src/sass_context.cpp | 769 +++++++ src/sass_context.hpp | 129 ++ src/sass_functions.cpp | 207 ++ src/sass_functions.hpp | 50 + src/sass_util.cpp | 149 ++ src/sass_util.hpp | 256 +++ src/sass_values.cpp | 357 ++++ src/sass_values.hpp | 82 + src/source_map.cpp | 195 ++ src/source_map.hpp | 62 + src/subset_map.cpp | 55 + src/subset_map.hpp | 76 + src/support/libsass.pc.in | 11 + src/to_c.cpp | 74 + src/to_c.hpp | 39 + src/to_value.cpp | 112 + src/to_value.hpp | 50 + src/units.cpp | 501 +++++ src/units.hpp | 109 + src/utf8.h | 34 + src/utf8/checked.h | 334 +++ src/utf8/core.h | 329 +++ src/utf8/unchecked.h | 235 +++ src/utf8_string.cpp | 102 + src/utf8_string.hpp | 37 + src/util.cpp | 733 +++++++ src/util.hpp | 56 + src/values.cpp | 131 ++ src/values.hpp | 12 + test/test_node.cpp | 94 + test/test_paths.cpp | 28 + test/test_selector_difference.cpp | 25 + test/test_specificity.cpp | 25 + test/test_subset_map.cpp | 472 +++++ test/test_superselector.cpp | 69 + test/test_unification.cpp | 31 + version.sh | 10 + win/libsass.sln | 39 + win/libsass.sln.DotSettings | 9 + win/libsass.targets | 118 ++ win/libsass.vcxproj | 188 ++ win/libsass.vcxproj.filters | 357 ++++ 194 files changed, 45708 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 COPYING create mode 100644 GNUmakefile.am create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile.conf create mode 100644 Readme.md create mode 100644 SECURITY.md create mode 100644 appveyor.yml create mode 100644 configure.ac create mode 100644 contrib/libsass.spec create mode 100644 contrib/plugin.cpp create mode 100644 docs/README.md create mode 100644 docs/api-context-example.md create mode 100644 docs/api-context-internal.md create mode 100644 docs/api-context.md create mode 100644 docs/api-doc.md create mode 100644 docs/api-function-example.md create mode 100644 docs/api-function-internal.md create mode 100644 docs/api-function.md create mode 100644 docs/api-importer-example.md create mode 100644 docs/api-importer-internal.md create mode 100644 docs/api-importer.md create mode 100644 docs/api-value-example.md create mode 100644 docs/api-value-internal.md create mode 100644 docs/api-value.md create mode 100644 docs/build-on-darwin.md create mode 100644 docs/build-on-gentoo.md create mode 100644 docs/build-on-windows.md create mode 100644 docs/build-shared-library.md create mode 100644 docs/build-with-autotools.md create mode 100644 docs/build-with-makefiles.md create mode 100644 docs/build-with-mingw.md create mode 100644 docs/build-with-visual-studio.md create mode 100644 docs/build.md create mode 100644 docs/compatibility-plan.md create mode 100644 docs/contributing.md create mode 100644 docs/custom-functions-internal.md create mode 100644 docs/dev-ast-memory.md create mode 100644 docs/implementations.md create mode 100644 docs/plugins.md create mode 100644 docs/setup-environment.md create mode 100644 docs/source-map-internals.md create mode 100644 docs/trace.md create mode 100644 docs/triage.md create mode 100644 docs/unicode.md create mode 100644 extconf.rb create mode 100644 include/sass.h create mode 100644 include/sass/base.h create mode 100644 include/sass/context.h create mode 100644 include/sass/functions.h create mode 100644 include/sass/values.h create mode 100644 include/sass/version.h create mode 100644 include/sass/version.h.in create mode 100644 include/sass2scss.h create mode 100644 m4/.gitkeep create mode 100644 m4/m4-ax_cxx_compile_stdcxx_11.m4 create mode 100644 res/resource.rc create mode 100755 script/bootstrap create mode 100755 script/branding create mode 100755 script/ci-build-libsass create mode 100755 script/ci-build-plugin create mode 100755 script/ci-install-compiler create mode 100755 script/ci-install-deps create mode 100755 script/ci-report-coverage create mode 100755 script/spec create mode 100755 script/tap-driver create mode 100755 script/tap-runner create mode 100755 script/test-leaks.pl create mode 100644 src/GNUmakefile.am create mode 100644 src/ast.cpp create mode 100644 src/ast.hpp create mode 100644 src/ast_def_macros.hpp create mode 100644 src/ast_fwd_decl.cpp create mode 100644 src/ast_fwd_decl.hpp create mode 100644 src/b64/cencode.h create mode 100644 src/b64/encode.h create mode 100644 src/backtrace.cpp create mode 100644 src/backtrace.hpp create mode 100644 src/base64vlq.cpp create mode 100644 src/base64vlq.hpp create mode 100644 src/bind.cpp create mode 100644 src/bind.hpp create mode 100644 src/c99func.c create mode 100644 src/cencode.c create mode 100644 src/check_nesting.cpp create mode 100644 src/check_nesting.hpp create mode 100644 src/color_maps.cpp create mode 100644 src/color_maps.hpp create mode 100644 src/constants.cpp create mode 100644 src/constants.hpp create mode 100644 src/context.cpp create mode 100644 src/context.hpp create mode 100644 src/cssize.cpp create mode 100644 src/cssize.hpp create mode 100644 src/debug.hpp create mode 100644 src/debugger.hpp create mode 100644 src/emitter.cpp create mode 100644 src/emitter.hpp create mode 100644 src/environment.cpp create mode 100644 src/environment.hpp create mode 100644 src/error_handling.cpp create mode 100644 src/error_handling.hpp create mode 100644 src/eval.cpp create mode 100644 src/eval.hpp create mode 100644 src/expand.cpp create mode 100644 src/expand.hpp create mode 100644 src/extend.cpp create mode 100644 src/extend.hpp create mode 100644 src/file.cpp create mode 100644 src/file.hpp create mode 100644 src/functions.cpp create mode 100644 src/functions.hpp create mode 100644 src/inspect.cpp create mode 100644 src/inspect.hpp create mode 100644 src/json.cpp create mode 100644 src/json.hpp create mode 100644 src/kwd_arg_macros.hpp create mode 100644 src/lexer.cpp create mode 100644 src/lexer.hpp create mode 100644 src/listize.cpp create mode 100644 src/listize.hpp create mode 100644 src/mapping.hpp create mode 100644 src/memory/SharedPtr.cpp create mode 100644 src/memory/SharedPtr.hpp create mode 100644 src/node.cpp create mode 100644 src/node.hpp create mode 100644 src/operation.hpp create mode 100644 src/operators.cpp create mode 100644 src/operators.hpp create mode 100644 src/output.cpp create mode 100644 src/output.hpp create mode 100644 src/parser.cpp create mode 100644 src/parser.hpp create mode 100644 src/paths.hpp create mode 100644 src/plugins.cpp create mode 100644 src/plugins.hpp create mode 100644 src/position.cpp create mode 100644 src/position.hpp create mode 100644 src/prelexer.cpp create mode 100644 src/prelexer.hpp create mode 100644 src/remove_placeholders.cpp create mode 100644 src/remove_placeholders.hpp create mode 100644 src/sass.cpp create mode 100644 src/sass.hpp create mode 100644 src/sass2scss.cpp create mode 100644 src/sass_context.cpp create mode 100644 src/sass_context.hpp create mode 100644 src/sass_functions.cpp create mode 100644 src/sass_functions.hpp create mode 100644 src/sass_util.cpp create mode 100644 src/sass_util.hpp create mode 100644 src/sass_values.cpp create mode 100644 src/sass_values.hpp create mode 100644 src/source_map.cpp create mode 100644 src/source_map.hpp create mode 100644 src/subset_map.cpp create mode 100644 src/subset_map.hpp create mode 100644 src/support/libsass.pc.in create mode 100644 src/to_c.cpp create mode 100644 src/to_c.hpp create mode 100644 src/to_value.cpp create mode 100644 src/to_value.hpp create mode 100644 src/units.cpp create mode 100644 src/units.hpp create mode 100644 src/utf8.h create mode 100644 src/utf8/checked.h create mode 100644 src/utf8/core.h create mode 100644 src/utf8/unchecked.h create mode 100644 src/utf8_string.cpp create mode 100644 src/utf8_string.hpp create mode 100644 src/util.cpp create mode 100644 src/util.hpp create mode 100644 src/values.cpp create mode 100644 src/values.hpp create mode 100644 test/test_node.cpp create mode 100644 test/test_paths.cpp create mode 100644 test/test_selector_difference.cpp create mode 100644 test/test_specificity.cpp create mode 100644 test/test_subset_map.cpp create mode 100644 test/test_superselector.cpp create mode 100644 test/test_unification.cpp create mode 100755 version.sh create mode 100644 win/libsass.sln create mode 100644 win/libsass.sln.DotSettings create mode 100644 win/libsass.targets create mode 100644 win/libsass.vcxproj create mode 100644 win/libsass.vcxproj.filters diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..c3d859e7a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[{Makefile, GNUmakefile.am}] +indent_style = tab +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..2ff99d8bd --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,65 @@ +# Contributing to LibSass + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +The following is a set of guidelines for contributing to LibSass, which is hosted in the [Sass Organization](https://github.com/sass) on GitHub. +These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. + +LibSass is a library that implements a [sass language][8] compiler. As such it does not directly interface with end users (frontend developers). +For direct contributions to the LibSass code base you will need to have at least a rough idea of C++, we will not lie about that. +But there are other ways to contribute to the progress of LibSass. All contributions are done via github pull requests. + +You can also contribute to the LibSass [documentation][9] or provide additional [spec tests][10] (and we will gladly point you in the +direction for corners that lack test coverage). Foremost we rely on good and concise bug reports for issues the spec tests do not yet catch. + +## Precheck: My Sass isn't compiling +- [ ] Check if you can reproduce the issue via [SourceMap Inspector][5] (updated regularly). +- [ ] Validate official ruby sass compiler via [SassMeister][6] produces your expected result. +- [ ] Search for similar issue in [LibSass][1] and [node-sass][2] (include closed tickets) +- [ ] Optionally test your code directly with [sass][7] or [sassc][3] ([installer][4]) + +## Precheck: My build/install fails +- [ ] Problems with building or installing libsass should be directed to implementors first! +- [ ] Except for issues directly verified via sassc or LibSass own build (make/autotools9 + +## Craft a meaningfull error report +- [ ] Include the version of libsass and the implementor (i.e. node-sass or sassc) +- [ ] Include information about your operating system and environment (i.e. io.js) +- [ ] Either create a self contained sample that shows your issue ... +- [ ] ... or provide it as a fetchable (github preferred) archive/repo +- [ ] ... and include a step by step list of command to get all dependencies +- [ ] Make it clear if you use indented or/and scss syntax + +## My error is hiding in a big code base +1. we do not have time to support your code base! +2. to fix occuring issues we need precise bug reports +3. the more precise you are, the faster we can help you +4. lazy reports get overlooked even when exposing serious bugs +5. it's not hard to do, it only takes time +- [ ] Make sure you saved the current state (i.e. commit to git) +- [ ] Start by uncommenting blocks in the initial source file +- [ ] Check if the problem is still there after each edit +- [ ] Repeat until the problem goes away +- [ ] Inline imported files as you go along +- [ ] Finished once you cannot remove more +- [ ] The emphasis is on the word "repeat" ... + +## What makes a code test case + +Important is that someone else can get the test case up and running to reproduce it locally. For this +we urge you to verify that your sample yields the expected result by testing it via [SassMeister][6] +or directly via ruby sass or node-sass (or any other libsass implementor) before submitting your bug +report. Once you verified all of the above, you may use the template below to file your bug report. + + +[1]: https://github.com/sass/libsass/issues?utf8=%E2%9C%93&q=is%3Aissue +[2]: https://github.com/sass/node-sass/issues?utf8=%E2%9C%93&q=is%3Aissue +[3]: https://github.com/sass/sassc +[4]: http://libsass.ocbnet.ch/installer/ +[5]: http://libsass.ocbnet.ch/srcmap/ +[6]: http://www.sassmeister.com/ +[7]: https://rubygems.org/gems/sass + +[8]: http://sass-lang.com/ +[9]: https://github.com/sass/libsass/tree/master/docs +[10]: https://github.com/sass/sass-spec diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..43ffaaae1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,54 @@ +[todo]: # (Title: Be as meaningful as possible) +[todo]: # (Title: Try to use 60 or less chars) + +[todo]: # (This is only a template!) +[todo]: # (remove unneeded bits) +[todo]: # (use github preview!) + +## input.scss + +[todo]: # (always test and report with scss syntax) +[todo]: # (use sass only when results differ from scss) + +```scss +test { + content: bar +} +``` + +## Actual results + +[todo]: # (update version info!) + +[libsass 3.X.y][1] +```css +test { + content: bar; } +``` + +## Expected result + +[todo]: # (update version info!) + +ruby sass 3.X.y +```css +test { + content: bar; } +``` + +[todo]: # (update version info!) +[todo]: # (example for node-sass!) + +version info: +```cmd +$ node-sass --version +node-sass 3.X.y (Wrapper) [JavaScript] +libsass 3.X.y (Sass Compiler) [C/C++] +``` + +[todo]: # (Go to http://libsass.ocbnet.ch/srcmap) +[todo]: # (Enter your SCSS code and hit compile) +[todo]: # (Click `bookmark` and replace the url) +[todo]: # (link is used in actual results above) + +[1]: http://libsass.ocbnet.ch/srcmap/#dGVzdCB7CiAgY29udGVudDogYmFyOyB9Cg== diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f2ee6beac --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Miscellaneous stuff + +/sassc +/sass-spec + +VERSION +.DS_Store +.sass-cache +*.gem +*.gcno +.svn/* +.cproject +.project +.settings/ +*.db +*.aps + +# Configuration stuff + +GNUmakefile.in +GNUmakefile +/aclocal.m4 +/autom4te.cache/ +/src/config.h +/config.h.in +/config.log +/config.status +/configure +/libtool +/m4/libtool.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 +/script/ar-lib +/script/compile +/script/config.guess +/script/config.sub +/script/depcomp +/script/install-sh +/script/ltmain.sh +/script/missing +/script/test-driver +/src/stamp-h1 +/src/Makefile.in +/src/Makefile +libsass/* + +# Build stuff + +*.o +*.lo +*.so +*.dll +*.a +*.suo +*.sdf +*.opendb +*.opensdf +a.out +libsass.js +tester +tester.exe +build/ +config.h.in* +lib/pkgconfig/ + +bin/* +.deps/ +.libs/ +win/bin +*.user +win/*.db + +# Final results + +sassc++ +libsass.la +src/support/libsass.pc + +# Cloned testing dirs +sassc/ +sass-spec/ + +installer/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..09ca55066 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,64 @@ +language: cpp +sudo: false + + +# don't create redundant code coverage reports +# - AUTOTOOLS=yes COVERAGE=yes BUILD=static +# - AUTOTOOLS=no COVERAGE=yes BUILD=shared +# - AUTOTOOLS=no COVERAGE=no BUILD=static + +# further speed up day by day travis-ci builds +# re-enable this if you change the makefiles +# this will still catch all coding errors! +# - AUTOTOOLS=yes COVERAGE=no BUILD=static + +# currenty there are various issues when +# built with coverage, clang and autotools +# - AUTOTOOLS=yes COVERAGE=yes BUILD=shared + +matrix: + include : + - os: linux + compiler: gcc + env: AUTOTOOLS=no COVERAGE=yes BUILD=static + - os: linux + compiler: g++-5 + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-5 + - os: linux + compiler: clang++-3.7 + env: AUTOTOOLS=no COVERAGE=yes BUILD=static + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - clang-3.7 + - os: linux + compiler: clang + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared + - os: osx + compiler: clang + env: AUTOTOOLS=no COVERAGE=no BUILD=shared + - os: osx + compiler: clang + env: AUTOTOOLS=no COVERAGE=yes BUILD=static + - os: osx + compiler: clang + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared + +script: + - ./script/ci-build-libsass + - ./script/ci-build-plugin math + - ./script/ci-build-plugin glob + - ./script/ci-build-plugin digest + - ./script/ci-build-plugin tests +before_install: ./script/ci-install-deps +install: ./script/ci-install-compiler +after_success: ./script/ci-report-coverage diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..8639c1117 --- /dev/null +++ b/COPYING @@ -0,0 +1,25 @@ + +Copyright (C) 2012 by Hampton Catlin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +The following files in the spec were taken from the original Ruby Sass project which +is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under +the same license. diff --git a/GNUmakefile.am b/GNUmakefile.am new file mode 100644 index 000000000..d197261e7 --- /dev/null +++ b/GNUmakefile.am @@ -0,0 +1,74 @@ +ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4 -I script + +AM_COPT = -Wall -O2 +AM_COVLDFLAGS = + +if ENABLE_COVERAGE + AM_COPT = -Wall -O1 -fno-omit-frame-pointer --coverage + AM_COVLDFLAGS += -lgcov +endif + +AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CFLAGS = $(AM_COPT) +AM_CXXFLAGS = $(AM_COPT) +AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) + +# only needed to support old source tree +# we have moved the files to src folder +AM_CPPFLAGS += -I$(top_srcdir) + +RESOURCES = +if COMPILER_IS_MINGW32 + RESOURCES += res/libsass.rc + AM_CXXFLAGS += -std=gnu++0x +else + AM_CXXFLAGS += -std=c++0x +endif + +TEST_EXTENSIONS = .rb + +if ENABLE_TESTS + +SASS_SASSC_PATH ?= $(top_srcdir)/sassc +SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec + +noinst_PROGRAMS = tester +tester_LDADD = src/libsass.la +tester_LDFLAGS = $(AM_LDFLAGS) +nodist_tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c +SASS_SASSC_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` +tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" +tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" + +if ENABLE_COVERAGE +nodist_EXTRA_tester_SOURCES = non-existent-file-to-force-CXX-linking.cxx +endif + +TESTS = $(SASS_SPEC_PATH)/sass-spec.rb +RB_LOG_COMPILER = ./script/tap-runner +AM_RB_LOG_FLAGS = $(RUBY) + +SASS_TEST_FLAGS = -V 3.5 --impl libsass +SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH) +SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) +AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' + +SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb + +test: + $(SASS_TESTER) $(SASS_TEST_FLAGS) + +test_build: + $(SASS_TESTER) $(SASS_TEST_FLAGS) + +test_full: + $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) + +test_probe: + $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) + +.PHONY: test test_build test_full test_probe + +endif + +SUBDIRS = src diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..92e0156bf --- /dev/null +++ b/INSTALL @@ -0,0 +1 @@ +// Autotools requires us to have this file. Boo. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..6bc408500 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ + +Copyright (C) 2012-2016 by the Sass Open Source Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +The following files in the spec were taken from the original Ruby Sass project which +is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under +the same license. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..f3a294533 --- /dev/null +++ b/Makefile @@ -0,0 +1,351 @@ +OS ?= $(shell uname -s) +CC ?= gcc +CXX ?= g++ +RM ?= rm -f +CP ?= cp -a +MKDIR ?= mkdir +RMDIR ?= rmdir +WINDRES ?= windres +# Solaris/Illumos flavors +# ginstall from coreutils +ifeq ($(OS),SunOS) +INSTALL ?= ginstall +endif +INSTALL ?= install +CFLAGS ?= -Wall +CXXFLAGS ?= -Wall +LDFLAGS ?= -Wall +ifeq "x$(COVERAGE)" "x" + CFLAGS += -O2 + CXXFLAGS += -O2 + LDFLAGS += -O2 +else + CFLAGS += -O1 -fno-omit-frame-pointer + CXXFLAGS += -O1 -fno-omit-frame-pointer + LDFLAGS += -O1 -fno-omit-frame-pointer +endif +LDFLAGS += -Wl,-undefined,error +CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) + +ifneq (,$(findstring /cygdrive/,$(PATH))) + UNAME := Cygwin +else + ifneq (,$(findstring Windows_NT,$(OS))) + UNAME := Windows + else + ifneq (,$(findstring mingw32,$(MAKE))) + UNAME := Windows + else + ifneq (,$(findstring MINGW32,$(shell uname -s))) + UNAME = Windows + else + UNAME := $(shell uname -s) + endif + endif + endif +endif + +ifeq ($(SASS_LIBSASS_PATH),) + SASS_LIBSASS_PATH = $(abspath $(CURDIR)) +endif + +ifeq ($(LIBSASS_VERSION),) + ifneq ($(wildcard ./.git/ ),) + LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) + endif +endif + +ifeq ($(LIBSASS_VERSION),) + ifneq ($(wildcard VERSION),) + LIBSASS_VERSION ?= $(shell $(CAT) VERSION) + endif +endif + +ifneq ($(LIBSASS_VERSION),) + CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" + CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" +endif + +# enable mandatory flag +ifeq (Windows,$(UNAME)) + ifneq ($(BUILD),shared) + STATIC_ALL ?= 1 + endif + STATIC_LIBGCC ?= 1 + STATIC_LIBSTDCPP ?= 1 + CXXFLAGS += -std=gnu++0x + LDFLAGS += -std=gnu++0x +else + STATIC_ALL ?= 0 + STATIC_LIBGCC ?= 0 + STATIC_LIBSTDCPP ?= 0 + CXXFLAGS += -std=c++0x + LDFLAGS += -std=c++0x +endif + +ifneq ($(SASS_LIBSASS_PATH),) + CFLAGS += -I $(SASS_LIBSASS_PATH)/include + CXXFLAGS += -I $(SASS_LIBSASS_PATH)/include +else + # this is needed for mingw + CFLAGS += -I include + CXXFLAGS += -I include +endif + +ifneq ($(EXTRA_CFLAGS),) + CFLAGS += $(EXTRA_CFLAGS) +endif +ifneq ($(EXTRA_CXXFLAGS),) + CXXFLAGS += $(EXTRA_CXXFLAGS) +endif +ifneq ($(EXTRA_LDFLAGS),) + LDFLAGS += $(EXTRA_LDFLAGS) +endif + +LDLIBS = -lm + +ifneq ($(BUILD),shared) + LDLIBS += -lstdc++ +endif + +# link statically into lib +# makes it a lot more portable +# increases size by about 50KB +ifeq ($(STATIC_ALL),1) + LDFLAGS += -static +endif +ifeq ($(STATIC_LIBGCC),1) + LDFLAGS += -static-libgcc +endif +ifeq ($(STATIC_LIBSTDCPP),1) + LDFLAGS += -static-libstdc++ +endif + +ifeq ($(UNAME),Darwin) + CFLAGS += -stdlib=libc++ + CXXFLAGS += -stdlib=libc++ + LDFLAGS += -stdlib=libc++ +endif + +ifneq (Windows,$(UNAME)) + ifneq (FreeBSD,$(UNAME)) + ifneq (OpenBSD,$(UNAME)) + LDFLAGS += -ldl + LDLIBS += -ldl + endif + endif +endif + +ifneq ($(BUILD),shared) + BUILD := static +endif +ifeq ($(DEBUG),1) + BUILD := debug-$(BUILD) +endif + +ifeq (,$(TRAVIS_BUILD_DIR)) + ifeq ($(OS),SunOS) + PREFIX ?= /opt/local + else + PREFIX ?= /usr/local + endif +else + PREFIX ?= $(TRAVIS_BUILD_DIR) +endif + + +SASS_SASSC_PATH ?= sassc +SASS_SPEC_PATH ?= sass-spec +SASS_SPEC_SPEC_DIR ?= spec +SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc +RUBY_BIN = ruby + +LIB_STATIC = $(SASS_LIBSASS_PATH)/lib/libsass.a +LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.so + +ifeq (Windows,$(UNAME)) + ifeq (shared,$(BUILD)) + CFLAGS += -D ADD_EXPORTS + CXXFLAGS += -D ADD_EXPORTS + LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.dll + endif +else + ifneq (Cygwin,$(UNAME)) + CFLAGS += -fPIC + CXXFLAGS += -fPIC + LDFLAGS += -fPIC + endif +endif + +ifeq (Windows,$(UNAME)) + SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc.exe +endif + +include Makefile.conf + +RESOURCES = +STATICLIB = lib/libsass.a +SHAREDLIB = lib/libsass.so +ifeq (Windows,$(UNAME)) + RESOURCES += res/resource.rc + SHAREDLIB = lib/libsass.dll + ifeq (shared,$(BUILD)) + CFLAGS += -D ADD_EXPORTS + CXXFLAGS += -D ADD_EXPORTS + endif +else + ifneq (Cygwin,$(UNAME)) + CFLAGS += -fPIC + CXXFLAGS += -fPIC + LDFLAGS += -fPIC + endif +endif + +OBJECTS = $(addprefix src/,$(SOURCES:.cpp=.o)) +COBJECTS = $(addprefix src/,$(CSOURCES:.c=.o)) +RCOBJECTS = $(RESOURCES:.rc=.o) + +DEBUG_LVL ?= NONE + +CLEANUPS ?= +CLEANUPS += $(RCOBJECTS) +CLEANUPS += $(COBJECTS) +CLEANUPS += $(OBJECTS) +CLEANUPS += $(LIBSASS_LIB) + +all: $(BUILD) + +debug: $(BUILD) + +debug-static: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS)) +debug-static: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS)) +debug-static: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS)) +debug-static: static + +debug-shared: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS)) +debug-shared: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS)) +debug-shared: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS)) +debug-shared: shared + +lib: + $(MKDIR) lib + +lib/libsass.a: lib $(COBJECTS) $(OBJECTS) + $(AR) rcvs $@ $(COBJECTS) $(OBJECTS) + +lib/libsass.so: lib $(COBJECTS) $(OBJECTS) + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(LDLIBS) + +lib/libsass.dll: lib $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -Wl,--subsystem,windows,--out-implib,lib/libsass.a + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.rc + $(WINDRES) -i $< -o $@ + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%: %.o static + $(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) $(LDLIBS) + +install: install-$(BUILD) + +static: $(STATICLIB) +shared: $(SHAREDLIB) + +$(DESTDIR)$(PREFIX): + $(MKDIR) $(DESTDIR)$(PREFIX) + +$(DESTDIR)$(PREFIX)/lib: $(DESTDIR)$(PREFIX) + $(MKDIR) $(DESTDIR)$(PREFIX)/lib + +$(DESTDIR)$(PREFIX)/include: $(DESTDIR)$(PREFIX) + $(MKDIR) $(DESTDIR)$(PREFIX)/include + +$(DESTDIR)$(PREFIX)/include/sass: $(DESTDIR)$(PREFIX)/include + $(MKDIR) $(DESTDIR)$(PREFIX)/include/sass + +$(DESTDIR)$(PREFIX)/include/%.h: include/%.h \ + $(DESTDIR)$(PREFIX)/include \ + $(DESTDIR)$(PREFIX)/include/sass + $(INSTALL) -v -m0644 "$<" "$@" + +install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ + $(DESTDIR)$(PREFIX)/include/sass2scss.h \ + $(DESTDIR)$(PREFIX)/include/sass/base.h \ + $(DESTDIR)$(PREFIX)/include/sass/version.h \ + $(DESTDIR)$(PREFIX)/include/sass/values.h \ + $(DESTDIR)$(PREFIX)/include/sass/context.h \ + $(DESTDIR)$(PREFIX)/include/sass/functions.h + +$(DESTDIR)$(PREFIX)/lib/%.a: lib/%.a \ + $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +$(DESTDIR)$(PREFIX)/lib/%.so: lib/%.so \ + $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +$(DESTDIR)$(PREFIX)/lib/%.dll: lib/%.dll \ + $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +install-static: $(DESTDIR)$(PREFIX)/lib/libsass.a + +install-shared: $(DESTDIR)$(PREFIX)/lib/libsass.so \ + install-headers + +$(SASSC_BIN): $(BUILD) + $(MAKE) -C $(SASS_SASSC_PATH) build-$(BUILD)-dev + +sassc: $(SASSC_BIN) + $(SASSC_BIN) -v + +version: $(SASSC_BIN) + $(SASSC_BIN) -h + $(SASSC_BIN) -v + +test: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_build: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_full: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_probe: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +clean-objects: lib + -$(RM) lib/*.a lib/*.so lib/*.dll lib/*.la + -$(RMDIR) lib +clean: clean-objects + $(RM) $(CLEANUPS) + +clean-all: + $(MAKE) -C $(SASS_SASSC_PATH) clean + +lib-file: lib-file-$(BUILD) +lib-opts: lib-opts-$(BUILD) + +lib-file-static: + @echo $(LIB_STATIC) +lib-file-shared: + @echo $(LIB_SHARED) +lib-opts-static: + @echo -L"$(SASS_LIBSASS_PATH)/lib" +lib-opts-shared: + @echo -L"$(SASS_LIBSASS_PATH)/lib -lsass" + +.PHONY: all static shared sassc \ + version install-headers \ + clean clean-all clean-objects \ + debug debug-static debug-shared \ + install install-static install-shared \ + lib-opts lib-opts-shared lib-opts-static \ + lib-file lib-file-shared lib-file-static +.DELETE_ON_ERROR: diff --git a/Makefile.conf b/Makefile.conf new file mode 100644 index 000000000..5ba968b68 --- /dev/null +++ b/Makefile.conf @@ -0,0 +1,55 @@ +# this is merely a common Makefile multiple implementers can use +# bigger files (in terms of compile time) tend to go to the top, +# so they don't end up as the last compile unit when compiling +# in parallel. But we also want to mix them a little too avoid +# heavy RAM usage peaks. Other than that the order is arbitrary. + + +SOURCES = \ + ast.cpp \ + node.cpp \ + context.cpp \ + constants.cpp \ + functions.cpp \ + color_maps.cpp \ + environment.cpp \ + ast_fwd_decl.cpp \ + bind.cpp \ + file.cpp \ + util.cpp \ + json.cpp \ + units.cpp \ + values.cpp \ + plugins.cpp \ + position.cpp \ + lexer.cpp \ + parser.cpp \ + prelexer.cpp \ + eval.cpp \ + expand.cpp \ + listize.cpp \ + cssize.cpp \ + extend.cpp \ + output.cpp \ + inspect.cpp \ + emitter.cpp \ + check_nesting.cpp \ + remove_placeholders.cpp \ + sass.cpp \ + sass_util.cpp \ + sass_values.cpp \ + sass_context.cpp \ + sass_functions.cpp \ + sass2scss.cpp \ + backtrace.cpp \ + operators.cpp \ + to_c.cpp \ + to_value.cpp \ + source_map.cpp \ + subset_map.cpp \ + error_handling.cpp \ + memory/SharedPtr.cpp \ + utf8_string.cpp \ + base64vlq.cpp + +CSOURCES = cencode.c diff --git a/Readme.md b/Readme.md new file mode 100644 index 000000000..908de2dc4 --- /dev/null +++ b/Readme.md @@ -0,0 +1,104 @@ +LibSass - Sass compiler written in C++ +====================================== + +Currently maintained by Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) +Originally created by Aaron Leung ([@akhleung]) and Hampton Catlin ([@hcatlin]) + +[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass "Travis CI") +[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master "Appveyor CI") +[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3 "Code coverage of spec tests") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Percentage of issues still open") +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Average time to resolve an issue") +[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE "Bountysource") +[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/ "Slack communication channels") + + +[LibSass](https://github.com/sass/libsass "LibSass GitHub Project") is just a library! +If you want to use LibSass to compile Sass, you need an implementer. Some +implementations are only bindings into other programming languages. But most also +ship with a command line interface (CLI) you can use directly. There is also +[SassC](https://github.com/sass/sassc), which is the official lightweight +CLI tool built by the same people as LibSass. + +### Excerpt of "sanctioned" implementations: + +- https://github.com/sass/node-sass (Node.js) +- https://github.com/sass/perl-libsass (Perl) +- https://github.com/sass/libsass-python (Python) +- https://github.com/wellington/go-libsass (Go) +- https://github.com/sass/sassc-ruby (Ruby) +- https://github.com/sass/libsass-net (C#) +- https://github.com/medialize/sass.js (JS) +- https://github.com/bit3/jsass (Java) + +This list does not say anything about the quality of either the listed or not listed [implementations](docs/implementations.md)! +The authors of the listed projects above are just known to work regularly together with LibSass developers. + +About +----- + +LibSass is a C++ port of the original Ruby Sass CSS compiler with a [C API](docs/api-doc.md). +We coded LibSass with portability and efficiency in mind. You can expect LibSass to be a lot +faster than Ruby Sass and on par or faster than the best alternative CSS compilers around. + +Developing +---------- + +As noted above, the LibSass repository does not contain any binaries or other way to execute +LibSass. Therefore, you need an implementer to develop LibSass. Easiest is to start with +the official [SassC](http://github.com/sass/sassc) CLI wrapper. It is *guaranteed* to compile +with the latest code in LibSass master, since it is also used in the CI process. There is no +limitation here, as you may use any other LibSass implementer to test your LibSass branch! + +Testing +------- + +Since LibSass is a pure library, tests are run through the [Sass-Spec](https://github.com/sass/sass-spec) +project using the [SassC](http://github.com/sass/sassc) CLI wrapper. To run the tests against LibSass while +developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and +then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. +Note that the scripts in the `./script` folder are mainly intended for our CI needs. + +Building +-------- + +To build LibSass you need GCC 4.6+ or Clang/LLVM. If your OS is older, you may need to upgrade +them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.6+ or VS 2013 +Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows with various build chains +and/or command line interpreters. + +See the [build docs for further instructions](docs/build.md)! + +Compatibility +------------- + +Current LibSass 3.4 should be compatible with Sass 3.4. Please refer to the [sass compatibility +page](http://sass-compatibility.github.io/) for a more detailed comparison. But note that there +are still a few incomplete edges which we are aware of. Otherwise LibSass has reached a good level +of stability, thanks to our ever growing [Sass-Spec test suite](https://github.com/sass/sass-spec). + +About Sass +---------- + +Sass is a CSS pre-processor language to add on exciting, new, awesome features to CSS. Sass was +the first language of its kind and by far the most mature and up to date codebase. + +Sass was originally conceived of by the co-creator of this library, Hampton Catlin ([@hcatlin]). +Most of the language has been the result of years of work by Natalie Weizenbaum ([@nex3]) and +Chris Eppstein ([@chriseppstein]). + +For more information about Sass itself, please visit http://sass-lang.com + +Initial development of LibSass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). + +Licensing +--------- + +Our [MIT license](LICENSE) is designed to be as simple and liberal as possible. + +[@hcatlin]: https://github.com/hcatlin +[@akhleung]: https://github.com/akhleung +[@chriseppstein]: https://github.com/chriseppstein +[@nex3]: https://github.com/nex3 +[@mgreter]: https://github.com/mgreter +[@xzyfer]: https://github.com/xzyfer diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..90c837c60 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +Serious about security +====================== + +The LibSass team recognizes the important contributions the security research +community can make. We therefore encourage reporting security issues with the +code contained in this repository. + +If you believe you have discovered a security vulnerability, please report it at +https://hackerone.com/libsass instead of GitHub. + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..d964fade4 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,91 @@ +os: Visual Studio 2013 + +environment: + CTEST_OUTPUT_ON_FAILURE: 1 + ruby_version: 22-x64 + TargetPath: sassc/bin/sassc.exe + matrix: + - Compiler: msvc + Config: Release + Platform: Win32 + - Compiler: msvc + Config: Debug + Platform: Win32 + - Compiler: msvc + Config: Release + Platform: Win64 + - Compiler: mingw + Build: static + - Compiler: mingw + Build: shared + +cache: + - C:\Ruby%ruby_version%\lib\ruby\gems + - C:\mingw64 + +install: + - git clone https://github.com/sass/sassc.git + - git clone https://github.com/sass/sass-spec.git + - set PATH=C:\Ruby%ruby_version%\bin;%PATH% + - ps: | + if(!(gem which minitest 2>$nul)) { gem install minitest --no-ri --no-rdoc } + if ($env:Compiler -eq "mingw" -AND -Not (Test-Path "C:\mingw64")) { + # Install MinGW. + $file = "x86_64-4.9.2-release-win32-seh-rt_v4-rev3.7z" + wget https://bintray.com/artifact/download/drewwells/generic/$file -OutFile $file + &7z x -oC:\ $file > $null + } + - set PATH=C:\mingw64\bin;%PATH% + - set CC=gcc + +build_script: + - ps: | + if ($env:Compiler -eq "mingw") { + mingw32-make -j4 sassc + } else { + msbuild /m:4 /p:"Configuration=$env:Config;Platform=$env:Platform" sassc\win\sassc.sln + } + + # print the branding art + mv script/branding script/branding.ps1 + script/branding.ps1 + + # print the version info + &$env:TargetPath -v + ruby -v + +test_script: + - ps: | + $PRNR = $env:APPVEYOR_PULL_REQUEST_NUMBER + if ($PRNR) { + echo "Fetching info for PR $PRNR" + wget https://api.github.com/repos/sass/libsass/pulls/$PRNR -OutFile pr.json + $json = cat pr.json -Raw + $SPEC_PR = [regex]::match($json,'sass\/sass-spec(#|\/pull\/)([0-9]+)').Groups[2].Value + if ($SPEC_PR) { + echo "Checkout sass spec PR $SPEC_PR" + git -C sass-spec fetch -q -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR + git -C sass-spec checkout -q --force ci-spec-pr-$SPEC_PR + } + } + $env:TargetPath = Join-Path $pwd.Path $env:TargetPath + If (Test-Path "$env:TargetPath") { + ruby sass-spec/sass-spec.rb -V 3.5 --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec + if(-not($?)) { + echo "sass-spec tests failed" + exit 1 + } + } else { + echo "spec runner not found (compile error?)" + exit 1 + } + Write-Host "Explicitly testing the case when cwd has Cyrillic characters: " -nonewline + # See comments in gh-1774 for details. + cd sass-spec/spec/libsass/Sáss-UŢF8/ + &$env:TargetPath ./input.scss 2>&1>$null + if(-not($?)) { + echo "Failed!" + exit 1 + } else { + echo "Success!" + } diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000..b5a943217 --- /dev/null +++ b/configure.ac @@ -0,0 +1,134 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.61]) + +AC_INIT([libsass], m4_esyscmd_s([./version.sh]), [support@moovweb.com]) +AC_CONFIG_SRCDIR([src/ast.hpp]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([src/config.h]) +AC_CONFIG_FILES([include/sass/version.h]) +AC_CONFIG_AUX_DIR([script]) + +# These are flags passed to automake +# Though they look like gcc flags! +AM_INIT_AUTOMAKE([foreign parallel-tests -Wall]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([no])]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX +AC_LANG_PUSH([C]) +AC_LANG_PUSH([C++]) +AC_GNU_SOURCE +# Check fails on Travis, but it works fine +# AX_CXX_COMPILE_STDCXX_11([ext],[optional]) +AC_CHECK_TOOL([AR], [ar], [false]) +AC_CHECK_TOOL([DLLTOOL], [dlltool], [false]) +AC_CHECK_TOOL([DLLWRAP], [dllwrap], [false]) +AC_CHECK_TOOL([WINDRES], [windres], [false]) +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +LT_INIT([dlopen]) + +# Checks for header files. +AC_CHECK_HEADERS([unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_CHECK_FUNCS([floor getcwd strtol]) + +# Checks for testing. +AC_ARG_ENABLE(tests, AS_HELP_STRING([--enable-tests], [enable testing the build]), + [enable_tests="$enableval"], [enable_tests=no]) + +AS_CASE([$host], [*-*-mingw*], [is_mingw32=yes], [is_mingw32=no]) +AM_CONDITIONAL(COMPILER_IS_MINGW32, test "x$is_mingw32" = "xyes") + +dnl The dlopen() function is in the C library for *BSD and in +dnl libdl on GLIBC-based systems +if test "x$is_mingw32" != "xyes"; then + AC_SEARCH_LIBS([dlopen], [dl dld], [], [ + AC_MSG_ERROR([unable to find the dlopen() function]) + ]) +fi + +if test "x$enable_tests" = "xyes"; then + AC_PROG_CC + AC_PROG_AWK + # test need minitest gem + AC_PATH_PROG(RUBY, [ruby]) + AC_PATH_PROG(TAPOUT, [tapout]) + AC_REQUIRE_AUX_FILE([tap-driver]) + AC_REQUIRE_AUX_FILE([tap-runner]) + AC_ARG_WITH(sassc-dir, + AS_HELP_STRING([--with-sassc-dir=], [specify directory of sassc sources for testing (default: sassc)]), + [sassc_dir="$withval"], [sassc_dir="sassc"]) + AC_CHECK_FILE([$sassc_dir/sassc.c], [], [ + AC_MSG_ERROR([Unable to find sassc directory. +You must clone the sassc repository in this directory or specify +the --with-sassc-dir= argument. +]) + ]) + SASS_SASSC_PATH=$sassc_dir + AC_SUBST(SASS_SASSC_PATH) + + AC_ARG_WITH(sass-spec-dir, + AS_HELP_STRING([--with-sass-spec-dir=], [specify directory of sass-spec for testing (default: sass-spec)]), + [sass_spec_dir="$withval"], [sass_spec_dir="sass-spec"]) + AC_CHECK_FILE([$sass_spec_dir/sass-spec.rb], [], [ + AC_MSG_ERROR([Unable to find sass-spec directory. +You must clone the sass-spec repository in this directory or specify +the --with-sass-spec-dir= argument. +]) + ]) + # Automake doesn't like its tests in an absolute path, so we make it relative. + case $sass_spec_dir in + /*) + SASS_SPEC_PATH=`$RUBY -e "require 'pathname'; puts Pathname.new('$sass_spec_dir').relative_path_from(Pathname.new('$PWD')).to_s"` + ;; + *) + SASS_SPEC_PATH="$sass_spec_dir" + ;; + esac + AC_SUBST(SASS_SPEC_PATH) +else + # we do not really need these paths for non test build + # but automake may error if we do not define them here + SASS_SPEC_PATH=sass-spec + SASS_SASSC_PATH=sassc + AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SASSC_PATH) +fi + +AM_CONDITIONAL(ENABLE_TESTS, test "x$enable_tests" = "xyes") + +AC_ARG_ENABLE([coverage], + [AS_HELP_STRING([--enable-coverage], + [enable coverage report for test suite])], + [enable_cov=$enableval], + [enable_cov=no]) + +if test "x$enable_cov" = "xyes"; then + + AC_CHECK_PROG(GCOV, gcov, gcov) + + # Remove all optimization flags from C[XX]FLAGS + changequote({,}) + CFLAGS=`echo "$CFLAGS -O1 -fno-omit-frame-pointer" | $SED -e 's/-O[0-9]*//g'` + CXXFLAGS=`echo "$CXXFLAGS -O1 -fno-omit-frame-pointer" | $SED -e 's/-O[0-9]*//g'` + changequote([,]) + + AC_SUBST(GCOV) +fi + +AM_CONDITIONAL(ENABLE_COVERAGE, test "x$enable_cov" = "xyes") + +AC_SUBST(PACKAGE_VERSION) + +AC_MSG_NOTICE([Building libsass ($VERSION)]) + +AC_CONFIG_FILES([GNUmakefile src/GNUmakefile src/support/libsass.pc]) +AC_OUTPUT diff --git a/contrib/libsass.spec b/contrib/libsass.spec new file mode 100644 index 000000000..a83d5f0cb --- /dev/null +++ b/contrib/libsass.spec @@ -0,0 +1,66 @@ +Name: libsass +Version: %{version} +Release: 1%{?dist} +Summary: A C/C++ implementation of a Sass compiler + +License: MIT +URL: http://libsass.org +Source0: %{name}-%{version}.tar.gz + +BuildRequires: gcc-c++ >= 4.7 +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool + + +%description +LibSass is a C/C++ port of the Sass engine. The point is to be simple, fast, and easy to integrate. + +%package devel +Summary: Development files for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} + + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + + +%prep +%setup -q +autoreconf --force --install + + +%build +%configure --disable-static \ + --disable-tests \ + --enable-shared + +make %{?_smp_mflags} + + +%install +%make_install +find $RPM_BUILD_ROOT -name '*.la' -exec rm -f {} ';' + + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + + +%files +%doc Readme.md LICENSE +%{_libdir}/*.so.* + +%files devel +%doc +%{_includedir}/* +%{_libdir}/*.so +%{_libdir}/pkgconfig/*.pc + + +%changelog +* Tue Feb 10 2015 Gawain Lynch - 3.1.0-1 +- Initial SPEC file + diff --git a/contrib/plugin.cpp b/contrib/plugin.cpp new file mode 100644 index 000000000..2f67bb371 --- /dev/null +++ b/contrib/plugin.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +// gcc: g++ -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +// mingw: g++ -shared plugin.cpp -o plugin.dll -Llib -lsass + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +union Sass_Value* custom_function(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) +{ + // get context/option struct associated with this compiler + struct Sass_Context* ctx = sass_compiler_get_context(comp); + struct Sass_Options* opts = sass_compiler_get_options(comp); + // get the cookie from function descriptor + void* cookie = sass_function_get_cookie(cb); + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" Sass_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_Function_Entry c_func = + sass_make_function("foo()", custom_function, (void*)42); + // create list of all custom functions + Sass_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, c_func); + // return the list + return fn_list; +} + +Sass_Import_List custom_importer(const char* cur_path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) +{ + // get the cookie from importer descriptor + void* cookie = sass_importer_get_cookie(cb); + // create a list to hold our import entries + Sass_Import_List incs = sass_make_import_list(1); + // create our only import entry (route path back) + incs[0] = sass_make_import_entry(cur_path, 0, 0); + // return imports + return incs; +} + +extern "C" Sass_Importer_List ADDCALL libsass_load_importers() +{ + // allocate a custom function caller + Sass_Importer_Entry c_imp = + sass_make_importer(custom_importer, - 99, (void*)42); + // create list of all custom functions + Sass_Importer_List imp_list = sass_make_importer_list(1); + // put the only function in this plugin to the list + sass_importer_set_list_entry(imp_list, 0, c_imp); + // return the list + return imp_list; +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..a233fae48 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,20 @@ +Welcome to the LibSass documentation! + +## First Off +LibSass is just a library. To run the code locally (i.e. to compile your stylesheets), you need an implementer. SassC (get it?) is an implementer written in C. There are a number of other implementations of LibSass - for example Node. We encourage you to write your own port - the whole point of LibSass is that we want to bring Sass to many other languages, not just Ruby! + +We're working hard on moving to full parity with Ruby Sass... learn more at the [The-LibSass-Compatibility-Plan](compatibility-plan.md)! + +### Implementing LibSass + +If you're interested in implementing LibSass in your own project see the [API Documentation](api-doc.md) which now includes implementing +your own [Sass functions](api-function.md). You may wish to [look at other implementations](implementations.md) for your language of choice. +Or make your own! + +### Contributing to LibSass + +| Issue Tracker | Issue Triage | Community Guidelines | +|-------------------|----------------------------------|-----------------------------| +| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [[Sass Community Guidelines|http://sass-lang.com/community-guidelines]]. Be nice and everyone else will be nice too! | + +Please refer to the steps on [Building LibSass](build.md) diff --git a/docs/api-context-example.md b/docs/api-context-example.md new file mode 100644 index 000000000..4f2a2a0ce --- /dev/null +++ b/docs/api-context-example.md @@ -0,0 +1,45 @@ +## Example main.c + +```C +#include +#include "sass/context.h" + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // configure some options ... + sass_option_set_precision(ctx_opt, 10); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +### Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: 21px * 2; }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + diff --git a/docs/api-context-internal.md b/docs/api-context-internal.md new file mode 100644 index 000000000..1a2818b34 --- /dev/null +++ b/docs/api-context-internal.md @@ -0,0 +1,163 @@ +```C +// Input behaviours +enum Sass_Input_Style { + SASS_CONTEXT_NULL, + SASS_CONTEXT_FILE, + SASS_CONTEXT_DATA, + SASS_CONTEXT_FOLDER +}; + +// sass config options structure +struct Sass_Inspect_Options { + + // Output style for the generated css code + // A value from above SASS_STYLE_* constants + enum Sass_Output_Style output_style; + + // Precision for fractional numbers + int precision; + +}; + +// sass config options structure +struct Sass_Output_Options : Sass_Inspect_Options { + + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + + // Emit comments in the generated CSS indicating + // the corresponding source line. + bool source_comments; + +}; + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. LibSass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Maybe use array interface instead? + char* include_path; + char* plugin_path; + + // Include paths (linked string list) + struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; + + // Path to source map file + // Enables source map generation + // Used to create sourceMappingUrl + char* source_map_file; + + // Directly inserted in source maps + char* source_map_root; + + // Custom functions that can be called from sccs code + Sass_Function_List c_functions; + + // Callback to overload imports + Sass_Importer_List c_importers; + + // List of custom headers + Sass_Importer_List c_headers; + +}; + +// base for all contexts +struct Sass_Context : Sass_Options +{ + + // store context type info + enum Sass_Input_Style type; + + // generated output data + char* output_string; + + // generated source map json + char* source_map_string; + + // error status + int error_status; + char* error_json; + char* error_text; + char* error_message; + // error position + char* error_file; + size_t error_line; + size_t error_column; + const char* error_src; + + // report imported files + char** included_files; + +}; + +// struct for file compilation +struct Sass_File_Context : Sass_Context { + + // no additional fields required + // input_path is already on options + +}; + +// struct for data compilation +struct Sass_Data_Context : Sass_Context { + + // provided source string + char* source_string; + char* srcmap_string; + +}; + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + Sass::Context* cpp_ctx; + // Sass::Block + Sass::Block_Obj root; +}; +``` + diff --git a/docs/api-context.md b/docs/api-context.md new file mode 100644 index 000000000..dfd10c181 --- /dev/null +++ b/docs/api-context.md @@ -0,0 +1,295 @@ +Sass Contexts come in two flavors: + +- `Sass_File_Context` +- `Sass_Data_Context` + +### Basic Usage + +```C +#include "sass/context.h" +``` + +***Sass_Options*** + +```C +// Precision for fractional numbers +int precision; +``` +```C +// Output style for the generated css code +// A value from above SASS_STYLE_* constants +int output_style; +``` +```C +// Emit comments in the generated CSS indicating +// the corresponding source line. +bool source_comments; +``` +```C +// embed sourceMappingUrl as data uri +bool source_map_embed; +``` +```C +// embed include contents in maps +bool source_map_contents; +``` +```C +// create file urls for sources +bool source_map_file_urls; +``` +```C +// Disable sourceMappingUrl in css output +bool omit_source_map_url; +``` +```C +// Treat source_string as sass (as opposed to scss) +bool is_indented_syntax_src; +``` +```C +// The input path is used for source map +// generating. It can be used to define +// something with string compilation or to +// overload the input file path. It is +// set to "stdin" for data contexts and +// to the input file on file contexts. +char* input_path; +``` +```C +// The output path is used for source map +// generating. LibSass will not write to +// this file, it is just used to create +// information in source-maps etc. +char* output_path; +``` +```C +// String to be used for indentation +const char* indent; +``` +```C +// String to be used to for line feeds +const char* linefeed; +``` +```C +// Colon-separated list of paths +// Semicolon-separated on Windows +char* include_path; +char* plugin_path; +``` +```C +// Additional include paths +// Must be null delimited +char** include_paths; +char** plugin_paths; +``` +```C +// Path to source map file +// Enables the source map generating +// Used to create sourceMappingUrl +char* source_map_file; +``` +```C +// Directly inserted in source maps +char* source_map_root; +``` +```C +// Custom functions that can be called from Sass code +Sass_C_Function_List c_functions; +``` +```C +// Callback to overload imports +Sass_C_Import_Callback importer; +``` + +***Sass_Context*** + +```C +// store context type info +enum Sass_Input_Style type; +```` +```C +// generated output data +char* output_string; +``` +```C +// generated source map json +char* source_map_string; +``` +```C +// error status +int error_status; +char* error_json; +char* error_text; +char* error_message; +// error position +char* error_file; +size_t error_line; +size_t error_column; +``` +```C +// report imported files +char** included_files; +``` + +***Sass_File_Context*** + +```C +// no additional fields required +// input_path is already on options +``` + +***Sass_Data_Context*** + +```C +// provided source string +char* source_string; +``` + +### Sass Context API + +```C +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Create and initialize an option struct +struct Sass_Options* sass_make_options (void); +// Create and initialize a specific context +struct Sass_File_Context* sass_make_file_context (const char* input_path); +struct Sass_Data_Context* sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +int sass_compile_file_context (struct Sass_File_Context* ctx); +int sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ctx); +struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// Usefull if you only want to query the included files +int sass_compiler_parse (struct Sass_Compiler* compiler); +int sass_compiler_execute (struct Sass_Compiler* compiler); + +// Release all memory allocated with the compiler +// This does _not_ include any contexts or options +void sass_delete_compiler (struct Sass_Compiler* compiler); +void sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +void sass_delete_file_context (struct Sass_File_Context* ctx); +void sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for Context from specific implementation +struct Sass_Context* sass_file_context_get_context (struct Sass_File_Context* file_ctx); +struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +struct Sass_Options* sass_context_get_options (struct Sass_Context* ctx); +struct Sass_Options* sass_file_context_get_options (struct Sass_File_Context* file_ctx); +struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +void sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +void sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + +// Getters for Sass_Context values +const char* sass_context_get_output_string (struct Sass_Context* ctx); +int sass_context_get_error_status (struct Sass_Context* ctx); +const char* sass_context_get_error_json (struct Sass_Context* ctx); +const char* sass_context_get_error_text (struct Sass_Context* ctx); +const char* sass_context_get_error_message (struct Sass_Context* ctx); +const char* sass_context_get_error_file (struct Sass_Context* ctx); +size_t sass_context_get_error_line (struct Sass_Context* ctx); +size_t sass_context_get_error_column (struct Sass_Context* ctx); +const char* sass_context_get_source_map_string (struct Sass_Context* ctx); +char** sass_context_get_included_files (struct Sass_Context* ctx); + +// Getters for Sass_Compiler options (query import stack) +size_t sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +Sass_Import_Entry sass_compiler_get_last_import(struct Sass_Compiler* compiler); +Sass_Import_Entry sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +// Getters for Sass_Compiler options (query function stack) +size_t sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +Sass_Callee_Entry sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +Sass_Callee_Entry sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); + +// Take ownership of memory (value on context is set to 0) +char* sass_context_take_error_json (struct Sass_Context* ctx); +char* sass_context_take_error_text (struct Sass_Context* ctx); +char* sass_context_take_error_message (struct Sass_Context* ctx); +char* sass_context_take_error_file (struct Sass_Context* ctx); +char* sass_context_take_output_string (struct Sass_Context* ctx); +char* sass_context_take_source_map_string (struct Sass_Context* ctx); +``` + +### Sass Options API + +```C +// Getters for Context_Option values +int sass_option_get_precision (struct Sass_Options* options); +enum Sass_Output_Style sass_option_get_output_style (struct Sass_Options* options); +bool sass_option_get_source_comments (struct Sass_Options* options); +bool sass_option_get_source_map_embed (struct Sass_Options* options); +bool sass_option_get_source_map_contents (struct Sass_Options* options); +bool sass_option_get_source_map_file_urls (struct Sass_Options* options); +bool sass_option_get_omit_source_map_url (struct Sass_Options* options); +bool sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +const char* sass_option_get_indent (struct Sass_Options* options); +const char* sass_option_get_linefeed (struct Sass_Options* options); +const char* sass_option_get_input_path (struct Sass_Options* options); +const char* sass_option_get_output_path (struct Sass_Options* options); +const char* sass_option_get_source_map_file (struct Sass_Options* options); +const char* sass_option_get_source_map_root (struct Sass_Options* options); +Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); +Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); + +// Getters for Context_Option include path array +size_t sass_option_get_include_path_size(struct Sass_Options* options); +const char* sass_option_get_include_path(struct Sass_Options* options, size_t i); +// Plugin paths to load dynamic libraries work the same +size_t sass_option_get_plugin_path_size(struct Sass_Options* options); +const char* sass_option_get_plugin_path(struct Sass_Options* options, size_t i); + +// Setters for Context_Option values +void sass_option_set_precision (struct Sass_Options* options, int precision); +void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +void sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +void sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +void sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +void sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +void sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +void sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +void sass_option_set_indent (struct Sass_Options* options, const char* indent); +void sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +void sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +void sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +void sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +void sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +void sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +void sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +void sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); +void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); + +// Push function for paths (no manipulation support for now) +void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +void sass_option_push_include_path (struct Sass_Options* options, const char* path); + +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +char* sass_find_file (const char* path, struct Sass_Options* opt); +char* sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +char* sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +char* sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); +``` + +### More links + +- [Sass Context Example](api-context-example.md) +- [Sass Context Internal](api-context-internal.md) + diff --git a/docs/api-doc.md b/docs/api-doc.md new file mode 100644 index 000000000..376561612 --- /dev/null +++ b/docs/api-doc.md @@ -0,0 +1,215 @@ +## Introduction + +LibSass wouldn't be much good without a way to interface with it. These +interface documentations describe the various functions and data structures +available to implementers. They are split up over three major components, which +have all their own source files (plus some common functionality). + +- [Sass Context](api-context.md) - Trigger and handle the main Sass compilation +- [Sass Value](api-value.md) - Exchange values and its format with LibSass +- [Sass Function](api-function.md) - Get invoked by LibSass for function statments +- [Sass Importer](api-importer.md) - Get invoked by LibSass for @import statments + +### Basic usage + +First you will need to include the header file! +This will automatically load all other headers too! + +```C +#include "sass/context.h" +``` + +## Basic C Example + +```C +#include +#include "sass/context.h" + +int main() { + puts(libsass_version()); + return 0; +} +``` + +```bash +gcc -Wall version.c -lsass -o version && ./version +``` + +## More C Examples + +- [Sample code for Sass Context](api-context-example.md) +- [Sample code for Sass Value](api-value-example.md) +- [Sample code for Sass Function](api-function-example.md) +- [Sample code for Sass Importer](api-importer-example.md) + +## Compiling your code + +The most important is your sass file (or string of sass code). With this, you +will want to start a LibSass compiler. Here is some pseudocode describing the +process. The compiler has two different modes: direct input as a string with +`Sass_Data_Context` or LibSass will do file reading for you by using +`Sass_File_Context`. See the code for a list of options available +[Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) + +**Building a file compiler** + + context = sass_make_file_context("file.scss") + options = sass_file_context_get_options(context) + sass_option_set_precision(options, 1) + sass_option_set_source_comments(options, true) + + sass_file_context_set_options(context, options) + + compiler = sass_make_file_compiler(sass_context) + sass_compiler_parse(compiler) + sass_compiler_execute(compiler) + + output = sass_context_get_output_string(context) + // Retrieve errors during compilation + error_status = sass_context_get_error_status(context) + json_error = sass_context_get_error_json(context) + // Release memory dedicated to the C compiler + sass_delete_compiler(compiler) + +**Building a data compiler** + + context = sass_make_data_context("div { a { color: blue; } }") + options = sass_data_context_get_options(context) + sass_option_set_precision(options, 1) + sass_option_set_source_comments(options, true) + + sass_data_context_set_options(context, options) + + compiler = sass_make_data_compiler(context) + sass_compiler_parse(compiler) + sass_compiler_execute(compiler) + + output = sass_context_get_output_string(context) + // div a { color: blue; } + // Retrieve errors during compilation + error_status = sass_context_get_error_status(context) + json_error = sass_context_get_error_json(context) + // Release memory dedicated to the C compiler + sass_delete_compiler(compiler) + +## Sass Context Internals + +Everything is stored in structs: + +```C +struct Sass_Options; +struct Sass_Context : Sass_Options; +struct Sass_File_context : Sass_Context; +struct Sass_Data_context : Sass_Context; +``` + +This mirrors very well how `libsass` uses these structures. + +- `Sass_Options` holds everything you feed in before the compilation. It also hosts +`input_path` and `output_path` options, because they are used to generate/calculate +relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. +- `Sass_Context` holds all the data returned by the compilation step. +- `Sass_File_Context` is a specific implementation that requires no additional fields +- `Sass_Data_Context` is a specific implementation that adds the `input_source` field + +Structs can be down-casted to access `context` or `options`! + +## Memory handling and life-cycles + +We keep memory around for as long as the main [context](api-context.md) object +is not destroyed (`sass_delete_context`). LibSass will create copies of most +inputs/options beside the main sass code. You need to allocate and fill that +buffer before passing it to LibSass. You may also overtake memory management +from libsass for certain return values (i.e. `sass_context_take_output_string`). + +```C +// to allocate buffer to be filled +void* sass_alloc_memory(size_t size); +// to allocate a buffer from existing string +char* sass_copy_c_string(const char* str); +// to free overtaken memory when done +void sass_free_memory(void* ptr); +``` + +## Miscellaneous API functions + +```C +// Some convenient string helper function +char* sass_string_unquote (const char* str); +char* sass_string_quote (const char* str, const char quote_mark); + +// Get compiled libsass version +const char* libsass_version(void); + +// Implemented sass language version +// Hardcoded version 3.4 for time being +const char* libsass_language_version(void); +``` + +## Common Pitfalls + +**input_path** + +The `input_path` is part of `Sass_Options`, but it also is the main option for +`Sass_File_Context`. It is also used to generate relative file links in source- +maps. Therefore it is pretty usefull to pass this information if you have a +`Sass_Data_Context` and know the original path. + +**output_path** + +Be aware that `libsass` does not write the output file itself. This option +merely exists to give `libsass` the proper information to generate links in +source-maps. The file has to be written to the disk by the +binding/implementation. If the `output_path` is omitted, `libsass` tries to +extrapolate one from the `input_path` by replacing (or adding) the file ending +with `.css`. + +## Error Codes + +The `error_code` is integer value which indicates the type of error that +occurred inside the LibSass process. Following is the list of error codes along +with the short description: + +* 1: normal errors like parsing or `eval` errors +* 2: bad allocation error (memory error) +* 3: "untranslated" C++ exception (`throw std::exception`) +* 4: legacy string exceptions ( `throw const char*` or `std::string` ) +* 5: Some other unknown exception + +Although for the API consumer, error codes do not offer much value except +indicating whether *any* error occurred during the compilation, it helps +debugging the LibSass internal code paths. + +## Real-World Implementations + +The proof is in the pudding, so we have highlighted a few implementations that +should be on par with the latest LibSass interface version. Some of them may not +have all features implemented! + +1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) +2. [Go Example](https://godoc.org/github.com/wellington/go-libsass#example-Compiler--Stdin) +3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) + +## ABI forward compatibility + +We use a functional API to make dynamic linking more robust and future +compatible. The API is not yet 100% stable, so we do not yet guarantee +[ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward +compatibility. + +## Plugins (experimental) + +LibSass can load plugins from directories. Just define `plugin_path` on context +options to load all plugins from the directories. To implement plugins, please +consult the following example implementations. + +- https://github.com/mgreter/libsass-glob +- https://github.com/mgreter/libsass-math +- https://github.com/mgreter/libsass-digest + +## Internal Structs + +- [Sass Context Internals](api-context-internal.md) +- [Sass Value Internals](api-value-internal.md) +- [Sass Function Internals](api-function-internal.md) +- [Sass Importer Internals](api-importer-internal.md) diff --git a/docs/api-function-example.md b/docs/api-function-example.md new file mode 100644 index 000000000..38608e1a2 --- /dev/null +++ b/docs/api-function-example.md @@ -0,0 +1,67 @@ +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) +{ + // get context/option struct associated with this compiler + struct Sass_Context* ctx = sass_compiler_get_context(comp); + struct Sass_Options* opts = sass_compiler_get_options(comp); + // get information about previous importer entry from the stack + Sass_Import_Entry import = sass_compiler_get_last_import(comp); + const char* prev_abs_path = sass_import_get_abs_path(import); + const char* prev_imp_path = sass_import_get_imp_path(import); + // get the cookie from function descriptor + void* cookie = sass_function_get_cookie(cb); + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_Function_Entry fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +### Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + diff --git a/docs/api-function-internal.md b/docs/api-function-internal.md new file mode 100644 index 000000000..69d81d04d --- /dev/null +++ b/docs/api-function-internal.md @@ -0,0 +1,8 @@ +```C +// Struct to hold custom function callback +struct Sass_Function { + const char* signature; + Sass_Function_Fn function; + void* cookie; +}; +``` diff --git a/docs/api-function.md b/docs/api-function.md new file mode 100644 index 000000000..8d9d97ca4 --- /dev/null +++ b/docs/api-function.md @@ -0,0 +1,74 @@ +Sass functions are used to define new custom functions callable by Sass code. They are also used to overload debug or error statements. You can also define a fallback function, which is called for every unknown function found in the Sass code. Functions get passed zero or more `Sass_Values` (a `Sass_List` value) and they must also return a `Sass_Value`. Return a `Sass_Error` if you want to signal an error. + +## Special signatures + +- `*` - Fallback implementation +- `@warn` - Overload warn statements +- `@error` - Overload error statements +- `@debug` - Overload debug statements + +Note: The fallback implementation will be given the name of the called function as the first argument, before all the original function arguments. These features are pretty new and should be considered experimental. + +### Basic Usage + +```C +#include "sass/functions.h" +``` + +## Sass Function API + +```C +// Forward declaration +struct Sass_Compiler; +struct Sass_Function; + +// Typedef helpers for custom functions lists +typedef struct Sass_Function (*Sass_Function_Entry); +typedef struct Sass_Function* (*Sass_Function_List); +// Typedef defining function signature and return type +typedef union Sass_Value* (*Sass_Function_Fn) + (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); + +// Creators for sass function list and function descriptors +Sass_Function_List sass_make_function_list (size_t length); +Sass_Function_Entry sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); +// In case you need to free them yourself +void sass_delete_function (Sass_Function_Entry entry); +void sass_delete_function_list (Sass_Function_List list); + +// Setters and getters for callbacks on function lists +Sass_Function_Entry sass_function_get_list_entry(Sass_Function_List list, size_t pos); +void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +void sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); +Sass_Import_Entry sass_import_get_list_entry (Sass_Import_List list, size_t idx); + +// Getters for custom function descriptors +const char* sass_function_get_signature (Sass_Function_Entry cb); +Sass_Function_Fn sass_function_get_function (Sass_Function_Entry cb); +void* sass_function_get_cookie (Sass_Function_Entry cb); + +// Getters for callee entry +const char* sass_callee_get_name (Sass_Callee_Entry); +const char* sass_callee_get_path (Sass_Callee_Entry); +size_t sass_callee_get_line (Sass_Callee_Entry); +size_t sass_callee_get_column (Sass_Callee_Entry); +enum Sass_Callee_Type sass_callee_get_type (Sass_Callee_Entry); +Sass_Env_Frame sass_callee_get_env (Sass_Callee_Entry); + +// Getters and Setters for environments (lexical, local and global) +union Sass_Value* sass_env_get_lexical (Sass_Env_Frame, const char*); +void sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); +union Sass_Value* sass_env_get_local (Sass_Env_Frame, const char*); +void sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); +union Sass_Value* sass_env_get_global (Sass_Env_Frame, const char*); +void sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); +``` + +### More links + +- [Sass Function Example](api-function-example.md) +- [Sass Function Internal](api-function-internal.md) + diff --git a/docs/api-importer-example.md b/docs/api-importer-example.md new file mode 100644 index 000000000..d83bf2609 --- /dev/null +++ b/docs/api-importer-example.md @@ -0,0 +1,112 @@ +## Example importer.c + +```C +#include +#include +#include "sass/context.h" + +Sass_Import_List sass_importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) +{ + // get the cookie from importer descriptor + void* cookie = sass_importer_get_cookie(cb); + Sass_Import_List list = sass_make_import_list(2); + char* local = sass_copy_c_string("local { color: green; }"); + char* remote = sass_copy_c_string("remote { color: red; }"); + list[0] = sass_make_import_entry("/tmp/styles.scss", local, 0); + list[1] = sass_make_import_entry("http://www.example.com", remote, 0); + return list; +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate custom importer + Sass_Importer_Entry c_imp = + sass_make_importer(sass_importer, 0, 0); + // create list for all custom importers + Sass_Importer_List imp_list = sass_make_importer_list(1); + // put only the importer on to the list + sass_importer_set_list_entry(imp_list, 0, c_imp); + // register list on to the context options + sass_option_set_c_importers(ctx_opt, imp_list); + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +Compile importer.c + +```bash +gcc -c importer.c -o importer.o +gcc -o importer importer.o -lsass +echo "@import 'foobar';" > importer.scss +./importer importer.scss +``` + +## Importer Behavior Examples + +```C +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass handle the import request + return NULL; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass handle the request + // swallows »@import "http://…"« pass-through + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry(path, 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // return an error to halt execution + Sass_Import_List list = sass_make_import_list(1); + const char* message = "some error message"; + list[0] = sass_make_import_entry(path, 0, 0); + sass_import_set_error(list[0], sass_copy_c_string(message), 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass load the file identifed by the importer + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry("/tmp/file.scss", 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // completely hide the import + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // completely hide the import + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry(0, 0, 0); + return list; +} +``` diff --git a/docs/api-importer-internal.md b/docs/api-importer-internal.md new file mode 100644 index 000000000..63d70fe75 --- /dev/null +++ b/docs/api-importer-internal.md @@ -0,0 +1,20 @@ +```C +// External import entry +struct Sass_Import { + char* imp_path; // path as found in the import statement + char *abs_path; // path after importer has resolved it + char* source; + char* srcmap; + // error handling + char* error; + size_t line; + size_t column; +}; + +// Struct to hold importer callback +struct Sass_Importer { + Sass_Importer_Fn importer; + double priority; + void* cookie; +}; +``` diff --git a/docs/api-importer.md b/docs/api-importer.md new file mode 100644 index 000000000..b6265002e --- /dev/null +++ b/docs/api-importer.md @@ -0,0 +1,86 @@ +By using custom importers, Sass stylesheets can be implemented in any possible way, such as by being loaded via a remote server. Please note: this feature is experimental and is implemented differently than importers in Ruby Sass. Imports must be relative to the parent import context and therefore we need to pass this information to the importer callback. This is currently done by passing the complete import string/path of the previous import context. + +## Return Imports + +You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). + +```C +Sass_Import_Entry* rv = sass_make_import_list(1); +rv[0] = sass_make_import(rel, abs, source, srcmap); +``` + +Every import will then be included in LibSass. You are allowed to only return a file path without any loaded source. This way you can ie. implement rewrite rules for import paths and leave the loading part for LibSass. + +Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not deprecate the C-API once support has been implemented. It will be used to re-map the actual sourcemap with the provided ones. + +### Basic Usage + +```C +#include "sass/functions.h" +``` + +## Sass Importer API + +```C +// Forward declaration +struct Sass_Import; + +// Forward declaration +struct Sass_C_Import_Descriptor; + +// Typedef defining the custom importer callback +typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); +// Typedef defining the importer c function prototype +typedef Sass_Import_Entry* (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); + +// Creators for custom importer callback (with some additional pointer) +// The pointer is mostly used to store the callback into the actual function +Sass_C_Import_Callback sass_make_importer (Sass_C_Import_Fn, void* cookie); + +// Getters for import function descriptors +Sass_C_Import_Fn sass_import_get_function (Sass_C_Import_Callback fn); +void* sass_import_get_cookie (Sass_C_Import_Callback fn); + +// Deallocator for associated memory +void sass_delete_importer (Sass_C_Import_Callback fn); + +// Creator for sass custom importer return argument list +Sass_Import_Entry* sass_make_import_list (size_t length); +// Creator for a single import entry returned by the custom importer inside the list +Sass_Import_Entry sass_make_import_entry (const char* path, char* source, char* srcmap); +Sass_Import_Entry sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); + +// set error message to abort import and to print out a message (path from existing object is used in output) +Sass_Import_Entry sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +void sass_import_set_list_entry (Sass_Import_Entry* list, size_t idx, Sass_Import_Entry entry); +Sass_Import_Entry sass_import_get_list_entry (Sass_Import_Entry* list, size_t idx); + +// Getters for import entry +const char* sass_import_get_imp_path (Sass_Import_Entry); +const char* sass_import_get_abs_path (Sass_Import_Entry); +const char* sass_import_get_source (Sass_Import_Entry); +const char* sass_import_get_srcmap (Sass_Import_Entry); +// Explicit functions to take ownership of these items +// The property on our struct will be reset to NULL +char* sass_import_take_source (Sass_Import_Entry); +char* sass_import_take_srcmap (Sass_Import_Entry); + +// Getters for import error entries +size_t sass_import_get_error_line (Sass_Import_Entry); +size_t sass_import_get_error_column (Sass_Import_Entry); +const char* sass_import_get_error_message (Sass_Import_Entry); + +// Deallocator for associated memory (incl. entries) +void sass_delete_import_list (Sass_Import_Entry*); +// Just in case we have some stray import structs +void sass_delete_import (Sass_Import_Entry); +``` + +### More links + +- [Sass Importer Example](api-importer-example.md) +- [Sass Importer Internal](api-importer-internal.md) + diff --git a/docs/api-value-example.md b/docs/api-value-example.md new file mode 100644 index 000000000..690654eaf --- /dev/null +++ b/docs/api-value-example.md @@ -0,0 +1,55 @@ +## Example operation.c + +```C +#include +#include +#include "sass/values.h" + +int main( int argc, const char* argv[] ) +{ + + // create two new sass values to be added + union Sass_Value* string = sass_make_string("String"); + union Sass_Value* number = sass_make_number(42, "nits"); + + // invoke the add operation which returns a new sass value + union Sass_Value* total = sass_value_op(ADD, string, number); + + // no further use for the two operands + sass_delete_value(string); + sass_delete_value(number); + + // this works since libsass will always return a + // string for add operations with a string as the + // left hand side. But you should never rely on it! + puts(sass_string_get_value(total)); + + // invoke stringification (uncompressed with precision of 5) + union Sass_Value* result = sass_value_stringify(total, false, 5); + + // no further use for the sum + sass_delete_value(total); + + // print the result - you may want to make + // sure result is indeed a string, altough + // stringify guarantees to return a string + // if (sass_value_is_string(result)) {} + // really depends on your level of paranoia + puts(sass_string_get_value(result)); + + // finally free result + sass_delete_value(result); + + // exit status + return 0; + +} +``` + +## Compile operation.c + +```bash +gcc -c operation.c -o operation.o +gcc -o operation operation.o -lsass +./operation # => String42nits +``` diff --git a/docs/api-value-internal.md b/docs/api-value-internal.md new file mode 100644 index 000000000..fed402256 --- /dev/null +++ b/docs/api-value-internal.md @@ -0,0 +1,76 @@ +```C +struct Sass_Unknown { + enum Sass_Tag tag; +}; + +struct Sass_Boolean { + enum Sass_Tag tag; + bool value; +}; + +struct Sass_Number { + enum Sass_Tag tag; + double value; + char* unit; +}; + +struct Sass_Color { + enum Sass_Tag tag; + double r; + double g; + double b; + double a; +}; + +struct Sass_String { + enum Sass_Tag tag; + char* value; +}; + +struct Sass_List { + enum Sass_Tag tag; + enum Sass_Separator separator; + size_t length; + // null terminated "array" + union Sass_Value** values; +}; + +struct Sass_Map { + enum Sass_Tag tag; + size_t length; + struct Sass_MapPair* pairs; +}; + +struct Sass_Null { + enum Sass_Tag tag; +}; + +struct Sass_Error { + enum Sass_Tag tag; + char* message; +}; + +struct Sass_Warning { + enum Sass_Tag tag; + char* message; +}; + +union Sass_Value { + struct Sass_Unknown unknown; + struct Sass_Boolean boolean; + struct Sass_Number number; + struct Sass_Color color; + struct Sass_String string; + struct Sass_List list; + struct Sass_Map map; + struct Sass_Null null; + struct Sass_Error error; + struct Sass_Warning warning; +}; + +struct Sass_MapPair { + union Sass_Value* key; + union Sass_Value* value; +}; +``` + diff --git a/docs/api-value.md b/docs/api-value.md new file mode 100644 index 000000000..d78625875 --- /dev/null +++ b/docs/api-value.md @@ -0,0 +1,154 @@ +`Sass_Values` are used to pass values and their types between the implementer +and LibSass. Sass knows various different value types (including nested arrays +and hash-maps). If you implement a binding to another programming language, you +have to find a way to [marshal][1] (convert) `Sass_Values` between the target +language and C. `Sass_Values` are currently only used by custom functions, but +it should also be possible to use them without a compiler context. + +[1]: https://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 + +### Basic Usage + +```C +#include "sass/values.h" +``` + +```C +// Type for Sass values +enum Sass_Tag { + SASS_BOOLEAN, + SASS_NUMBER, + SASS_COLOR, + SASS_STRING, + SASS_LIST, + SASS_MAP, + SASS_NULL, + SASS_ERROR, + SASS_WARNING +}; + +// Tags for denoting Sass list separators +enum Sass_Separator { + SASS_COMMA, + SASS_SPACE, + // only used internally to represent a hash map before evaluation + // otherwise we would be too early to check for duplicate keys + SASS_HASH +}; + +// Value Operators +enum Sass_OP { + AND, OR, // logical connectives + EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations + ADD, SUB, MUL, DIV, MOD, // arithmetic functions + NUM_OPS // so we know how big to make the op table +}; +``` + +### Sass Value API + +```C +// Forward declaration +union Sass_Value; + +// Creator functions for all value types +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); + +// Generic destructor function for all types +// Will release memory of all associated Sass_Values +// Means we will delete recursively for lists and maps +void sass_delete_value (union Sass_Value* val); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) +union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); + +// Execute an operation for two Sass_Values and return the result as a Sass_Value too +union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); + +// Return the sass tag for a generic sass value +// Check is needed before accessing specific values! +enum Sass_Tag sass_value_get_tag (const union Sass_Value* v); + +// Check value to be of a specific type +// Can also be used before accessing properties! +bool sass_value_is_null (const union Sass_Value* v); +bool sass_value_is_number (const union Sass_Value* v); +bool sass_value_is_string (const union Sass_Value* v); +bool sass_value_is_boolean (const union Sass_Value* v); +bool sass_value_is_color (const union Sass_Value* v); +bool sass_value_is_list (const union Sass_Value* v); +bool sass_value_is_map (const union Sass_Value* v); +bool sass_value_is_error (const union Sass_Value* v); +bool sass_value_is_warning (const union Sass_Value* v); + +// Getters and setters for Sass_Number +double sass_number_get_value (const union Sass_Value* v); +void sass_number_set_value (union Sass_Value* v, double value); +const char* sass_number_get_unit (const union Sass_Value* v); +void sass_number_set_unit (union Sass_Value* v, char* unit); + +// Getters and setters for Sass_String +const char* sass_string_get_value (const union Sass_Value* v); +void sass_string_set_value (union Sass_Value* v, char* value); +bool sass_string_is_quoted(const union Sass_Value* v); +void sass_string_set_quoted(union Sass_Value* v, bool quoted); + +// Getters and setters for Sass_Boolean +bool sass_boolean_get_value (const union Sass_Value* v); +void sass_boolean_set_value (union Sass_Value* v, bool value); + +// Getters and setters for Sass_Color +double sass_color_get_r (const union Sass_Value* v); +void sass_color_set_r (union Sass_Value* v, double r); +double sass_color_get_g (const union Sass_Value* v); +void sass_color_set_g (union Sass_Value* v, double g); +double sass_color_get_b (const union Sass_Value* v); +void sass_color_set_b (union Sass_Value* v, double b); +double sass_color_get_a (const union Sass_Value* v); +void sass_color_set_a (union Sass_Value* v, double a); + +// Getter for the number of items in list +size_t sass_list_get_length (const union Sass_Value* v); +// Getters and setters for Sass_List +enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); +void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +bool sass_list_get_is_bracketed (const union Sass_Value* v); +void sass_list_set_is_bracketed (union Sass_Value* v, bool value); +// Getters and setters for Sass_List values +union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); +void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); + +// Getter for the number of items in map +size_t sass_map_get_length (const union Sass_Value* v); +// Getters and setters for Sass_Map keys and values +union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); +void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); +union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); +void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); + +// Getters and setters for Sass_Error +char* sass_error_get_message (const union Sass_Value* v); +void sass_error_set_message (union Sass_Value* v, char* msg); + +// Getters and setters for Sass_Warning +char* sass_warning_get_message (const union Sass_Value* v); +void sass_warning_set_message (union Sass_Value* v, char* msg); +``` + +### More links + +- [Sass Value Example](api-value-example.md) +- [Sass Value Internal](api-value-internal.md) + diff --git a/docs/build-on-darwin.md b/docs/build-on-darwin.md new file mode 100644 index 000000000..119a5350e --- /dev/null +++ b/docs/build-on-darwin.md @@ -0,0 +1,27 @@ +To install LibSass, make sure the OS X build tools are installed: + + xcode-select --install + +## Homebrew + +To install homebrew, see [http://brew.sh](http://brew.sh) + + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +You can install the latest version of LibSass quite easily with brew. + + brew install --HEAD libsass + +To update this, do: + + brew reinstall --HEAD libsass + +Brew will build static and shared libraries, and a `libsass.pc` file in `/usr/local/lib/pkgconfig`. + +To use `libsass.pc`, make sure this path is in your `PKG_CONFIG_PATH` + + export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig + +## Manually + +See the linux instructions [Building-with-autotools](build-with-autotools.md) or [Building-with-makefiles](build-with-makefiles.md) diff --git a/docs/build-on-gentoo.md b/docs/build-on-gentoo.md new file mode 100644 index 000000000..601b1fe5e --- /dev/null +++ b/docs/build-on-gentoo.md @@ -0,0 +1,55 @@ +Here are two ebuilds to compile LibSass and sassc on gentoo linux. If you do not know how to use these ebuilds, you should probably read the gentoo wiki page about [portage overlays](http://wiki.gentoo.org/wiki/Overlay). + +## www-misc/libsass/libsass-9999.ebuild +```ebuild +EAPI=4 + +inherit eutils git-2 autotools + +DESCRIPTION="A C/C++ implementation of a Sass compiler." +HOMEPAGE="http://libsass.org/" +EGIT_PROJECT='libsass' +EGIT_REPO_URI="https://github.com/sass/libsass.git" +LICENSE="MIT" +SLOT="0" +KEYWORDS="" +IUSE="" +DEPEND="" +RDEPEND="${DEPEND}" +DEPEND="${DEPEND}" + +pkg_pretend() { + # older gcc is not supported + local major=$(gcc-major-version) + local minor=$(gcc-minor-version) + [[ "${MERGE_TYPE}" != "binary" && ( $major > 4 || ( $major == 4 && $minor < 5 ) ) ]] && \ + die "Sorry, but gcc earlier than 4.5 will not work for LibSass." +} + +src_prepare() { + eautoreconf +} +``` + +## www-misc/sassc/sassc-9999.ebuild +```ebuild +EAPI=4 + +inherit eutils git-2 autotools + +DESCRIPTION="Command Line Tool for LibSass." +HOMEPAGE="http://libsass.org/" +EGIT_PROJECT='sassc' +EGIT_REPO_URI="https://github.com/sass/sassc.git" +LICENSE="MIT" +SLOT="0" +KEYWORDS="" +IUSE="" +DEPEND="www-misc/libsass" +RDEPEND="${DEPEND}" +DEPEND="${DEPEND}" + +src_prepare() { + eautoreconf +} +``` diff --git a/docs/build-on-windows.md b/docs/build-on-windows.md new file mode 100644 index 000000000..0afaa2e4c --- /dev/null +++ b/docs/build-on-windows.md @@ -0,0 +1,139 @@ +We support builds via MingGW and via Visual Studio Community 2013. +Both should be considered experimental (MinGW was better tested)! + +## Building via MingGW (makefiles) + +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. + +You need to have the following components installed: +![Visualization of components installed in the interface](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) + +Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. + +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: + +```bash +gem install minitest +``` + +### Mount the mingw root directory + +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: + +``` +C:\MinGW /mingw +``` + +### Starting a "MingGW" console + +Create a batch file with this content: +```bat +@echo off +set PATH=C:\MinGW\bin;%PATH% +REM only needed if not already available +set PATH=%PROGRAMFILES%\git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! + +### Get the sources + +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bat +set BUILD="shared" +``` + +### Compile the library +```bash +mingw32-make -C libsass +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.dll libsass.so +``` + +### Run the spec test-suite +```bash +mingw32-make -C libsass test_build +``` + +## Building via MingGW 64bit (makefiles) +Building libass to dll on window 64bit. + ++ downloads [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) , and unzip to "C:\mingw64". + ++ Create a batch file with this content: + +```bat +@echo off +set PATH=C:\mingw64\bin;%PATH% +set CC=gcc +REM only needed if not already available +set PATH=%PROGRAMFILES%\Git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + ++ By default , mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​" , we can modify Makefile to fix this:(add "-static") + +``` bash +lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(MKDIR) lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a +``` + ++ Compile the library + +```bash +mingw32-make -C libsass +``` + +By the way , if you are using java jna , [JNAerator](http://jnaerator.googlecode.com/) is a good tool. + +## Building via Visual Studio Community 2013 + +Open a Visual Studio 2013 command prompt: +- `VS2013 x86 Native Tools Command Prompt` + +Note: When I installed the community edition, I only got the 2012 command prompts. I copied them from the Startmenu to the Desktop and adjusted the paths from `Visual Studio 11.0` to `Visual Studio 12.0`. Since `libsass` uses some `C++11` features, you need at least a MSVC 2013 compiler (v120). + +### Get the source +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +git clone https://github.com/sass/sassc.git libsass/sassc +# only needed if you want to run the testsuite +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Compile sassc + +Sometimes `msbuild` seems not available from the command prompt. Just search for it and add it to the global path. It seems to be included in the .net folders too. + +```bat +cd libsass +REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin +msbuild /m:4 /p:Configuration=Release win\libsass.sln +REM running the spec test-suite manually (needs ruby and minitest gem) +ruby sass-spec\sass-spec.rb -V 3.5 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec +cd .. +``` + +[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files +[2]: https://msysgit.github.io/ +[3]: http://rubyinstaller.org/ diff --git a/docs/build-shared-library.md b/docs/build-shared-library.md new file mode 100644 index 000000000..3c143b46a --- /dev/null +++ b/docs/build-shared-library.md @@ -0,0 +1,35 @@ +This page is mostly intended for people that want to build a system library that gets distributed via RPMs or other means. This is currently in a experimental phase, as we currently do not really guarantee any ABI forward compatibility. The C API was rewritten to make this possible in the future, but we want to wait some more time till we can call this final and stable. + +Building via autotools +-- + +You want to build a system library only via autotools, since it will create the proper `libtool` files to make it loadable on multiple systems. We hope this works correctly, but nobody of the `libsass` core team has much knowledge in this area. Therefore we are open for comments or improvements by people that have more experience in that matter (like package maintainers from various linux distributions). + +```bash +apt-get install autoconf libtool +git clone https://github.com/sass/libsass.git +cd libsass +autoreconf --force --install +./configure \ + --disable-tests \ + --disable-static \ + --enable-shared \ + --prefix=/usr +make -j5 install +cd .. +``` + +This should install these files +```bash +# $ ls -la /usr/lib/libsass.* +/usr/lib/libsass.la +/usr/lib/libsass.so -> libsass.so.0.0.9 +/usr/lib/libsass.so.0 -> libsass.so.0.0.9 +/usr/lib/libsass.so.0.0.9 +# $ ls -la /usr/include/sass* +/usr/include/sass.h +/usr/include/sass2scss.h +/usr/include/sass/context.h +/usr/include/sass/functions.h +/usr/include/sass/values.h +``` diff --git a/docs/build-with-autotools.md b/docs/build-with-autotools.md new file mode 100644 index 000000000..a48ed18aa --- /dev/null +++ b/docs/build-with-autotools.md @@ -0,0 +1,78 @@ +### Get the sources +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Prerequisites + +In order to run autotools you need a few tools installed on your system. +```bash +yum install automake libtool # RedHat Linux +emerge -a automake libtool # Gentoo Linux +pkgin install automake libtool # SmartOS +``` + + +### Create configure script +```bash +cd libsass +autoreconf --force --install +cd .. +``` + +### Create custom makefiles +```bash +cd libsass +./configure \ + --disable-tests \ + --disable-shared \ + --prefix=/usr +cd .. +``` + +### Build the library +```bash +make -C libsass -j5 +``` + +### Install the library +The library will be installed to the location given as `prefix` to `configure`. This is standard behavior for autotools and not `libsass` specific. +```bash +make -C libsass -j5 install +``` + +### Configure options +The `configure` script is created by autotools. To get an overview of available options you can call `./configure --help`. When you execute this script, it will create specific makefiles, which you then use via the regular make command. + +There are some `libsass` specific options: + +``` +Optional Features: + --enable-tests enable testing the build + --enable-coverage enable coverage report for test suite + --enable-shared build shared libraries [default=yes] + --enable-static build static libraries [default=yes] + +Optional Packages: + --with-sassc-dir= specify directory of sassc sources for + testing (default: sassc) + --with-sass-spec-dir= specify directory of sass-spec for testing + (default: sass-spec) +``` + +### Build sassc and run spec test-suite + +```bash +cd libsass +autoreconf --force --install +./configure \ + --enable-tests \ + --enable-shared \ + --prefix=/usr +make -j5 test_build +cd .. +``` diff --git a/docs/build-with-makefiles.md b/docs/build-with-makefiles.md new file mode 100644 index 000000000..7ae2e33d6 --- /dev/null +++ b/docs/build-with-makefiles.md @@ -0,0 +1,68 @@ +### Get the sources +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bash +export BUILD="shared" +``` + +Alternatively you can also define it directly when calling make: + +```bash +BUILD="shared" make ... +``` + +### Compile the library +```bash +make -C libsass -j5 +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.so +``` + +### Install onto the system + +We recommend to use [autotools to install](build-with-autotools.md) libsass onto the +system, since that brings all the benefits of using libtools as the main install method. +If you still want to install libsass via the makefile, you need to make sure that gnu +`install` utility (or compatible) is installed on your system. +```bash +yum install coreutils # RedHat Linux +emerge -a coreutils # Gentoo Linux +pkgin install coreutils # SmartOS +``` + +You can set the install location by setting `PREFIX` +```bash +PREFIX="/opt/local" make install +``` + + +### Compling sassc + +```bash +# Let build know library location +export SASS_LIBSASS_PATH="`pwd`/libsass" +# Invokes the sassc makefile +make -C libsass -j5 sassc +``` + +### Run the spec test-suite + +```bash +# needs ruby available +# also gem install minitest +make -C libsass -j5 test_build +``` diff --git a/docs/build-with-mingw.md b/docs/build-with-mingw.md new file mode 100644 index 000000000..416507f3c --- /dev/null +++ b/docs/build-with-mingw.md @@ -0,0 +1,107 @@ +## Building LibSass with MingGW (makefiles) + +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. + +You need to have the following components installed: +![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) + +Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. + +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: + +```bash +gem install minitest +``` + +### Mount the mingw root directory + +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: + +``` +C:\MinGW /mingw +``` + +### Starting a "MingGW" console + +Create a batch file with this content: +```bat +@echo off +set PATH=C:\MinGW\bin;%PATH% +REM only needed if not already available +set PATH=%PROGRAMFILES%\git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! + +### Get the sources + +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bat +set BUILD="shared" +``` + +### Compile the library +```bash +mingw32-make -C libsass +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.dll libsass.so +``` + +### Run the spec test-suite +```bash +mingw32-make -C libsass test_build +``` + +## Building via MingGW 64bit (makefiles) +Building libass to dll on window 64bit. + +Download [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) and unzip to "C:\mingw64". + +Create a batch file with this content: + +```bat +@echo off +set PATH=C:\mingw64\bin;%PATH% +set CC=gcc +REM only needed if not already available +set PATH=%PROGRAMFILES%\Git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +By default, mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​", we can modify Makefile to fix this:(add "-static") + +``` bash +lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(MKDIR) lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a +``` + +Compile the library + +```bash +mingw32-make -C libsass +``` + +By the way, if you are using java jna, [JNAerator](http://jnaerator.googlecode.com/) is a good tool. + +[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files +[2]: https://msysgit.github.io/ +[3]: http://rubyinstaller.org/ diff --git a/docs/build-with-visual-studio.md b/docs/build-with-visual-studio.md new file mode 100644 index 000000000..275b917b8 --- /dev/null +++ b/docs/build-with-visual-studio.md @@ -0,0 +1,90 @@ +## Building LibSass with Visual Studio + +### Requirements: + +The minimum requirement to build LibSass with Visual Studio is "Visual Studio 2013 Express for Desktop". + +Additionally, it is recommended to have `git` installed and available in `PATH`, so to deduce the `libsass` version information. For instance, if GitHub for Windows (https://windows.github.com/) is installed, the `PATH` will have an entry resembling: `X:\Users\\AppData\Local\GitHub\PortableGit_\cmd\` (where `X` is the drive letter of system drive). If `git` is not available, inquiring the LibSass version will result in `[NA]`. + +### Build Steps: + +#### From Visual Studio: + +On opening the `win\libsass.sln` solution and build (Ctrl+Shift+B) to build `libsass.dll`. + +To Build LibSass as a static Library, it is recommended to set an environment variable `LIBSASS_STATIC_LIB` before launching the project: + +```cmd +cd path\to\libsass +SET LIBSASS_STATIC_LIB=1 +:: +:: or in PowerShell: +:: $env:LIBSASS_STATIC_LIB=1 +:: +win\libsass.sln +``` + +Visual Studio will form the filtered source tree as shown below: + +![image](https://cloud.githubusercontent.com/assets/3840695/9298985/aae9e072-44bf-11e5-89eb-e7995c098085.png) + +`Header Files` contains the .h and .hpp files, while `Source Files` covers `.c` and `.cpp`. The other used headers/sources will appear under `External Dependencies`. + +If there is a LibSass code file appearing under External Dependencies, it can be changed by altering the `win\libsass.vcxproj.filters` file or dragging in Solution Explorer. + +#### From Command Prompt: + +Notice that in the following commands: + +* If the platform is 32-bit Windows, replace `ProgramFiles(x86)` with `ProgramFiles`. +* To build with Visual Studio 2015, replace `12.0` with `14.0` in the aforementioned command. + +Open a command prompt: + +To build dynamic/shared library (`libsass.dll`): + +```cmd +:: debug build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln + +:: release build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:Configuration=Release +``` + +To build static library (`libsass.lib`): + +```cmd +:: debug build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:LIBSASS_STATIC_LIB=1 + +:: release build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +``` + +#### From PowerShell: + +To build dynamic/shared library (`libsass.dll`): + +```powershell +# debug build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln + +# release build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:Configuration=Release +``` + +To build static library (`libsass.lib`): + +```powershell +# build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:LIBSASS_STATIC_LIB=1 + +# release build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +``` diff --git a/docs/build.md b/docs/build.md new file mode 100644 index 000000000..c656d8839 --- /dev/null +++ b/docs/build.md @@ -0,0 +1,97 @@ +`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line][6]. Or some [bindings|Implementations][9] to use it within your favorite programming language. You should be able to get [`sassc`][6] running by following the instructions in this guide. + +Before starting, see [setup dev environment](setup-environment.md). + +Building on different Operating Systems +-- + +We try to keep the code as OS independent and standard compliant as possible. Reading files from the file-system has some OS depending code, but will ultimately fall back to a posix compatible implementation. We do use some `C++11` features, but are so far only committed to use `unordered_map`. This means you will need a pretty recent compiler on most systems (gcc 4.5 seems to be the minimum). + +### Building on Linux (and other *nix flavors) + +Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). + +- [Building with makefiles][1] +- [Building with autotools][2] + +### Building on Windows (experimental) + +Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! + +- [Building with MinGW][3] +- [Building with Visual Studio][11] + +### Building on Max OS X (untested) + +Works the same as on linux, but you can also install LibSass via `homebrew`. + +- [Building on Mac OS X][10] + +### Building a system library (experimental) + +Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. + +- [Building shared system library][4] + +Compiling with clang instead of gcc +-- + +To use clang you just need to set the appropriate environment variables: + +```bash +export CC=/usr/bin/clang +export CXX=/usr/bin/clang++ +``` + +Running the spec test-suite +-- + +We constantly and automatically test `libsass` against the official [spec test-suite][5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: + +```bash +ruby -v +gem install minitest +# should be optional +gem install minitap +``` + +Including the LibSass version +-- + +There is a function in `libsass` to query the current version. This has to be defined at compile time. We use a C macro for this, which can be defined by calling `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not need to do anything if you use the makefiles or autotools. They will try to fetch the version via git directly. If you only have the sources without the git repo, you can pass the version as an environment variable to `make` or `configure`: + +``` +export LIBSASS_VERSION="x.y.z." +``` + +Continuous Integration +-- + +We use two CI services to automatically test all commits against the latest [spec test-suite][5]. + +- [LibSass on Travis-CI (linux)][7] +[![Build Status](https://travis-ci.org/sass/libsass.png?branch=master)](https://travis-ci.org/sass/libsass) +- [LibSass on AppVeyor (windows)][8] +[![Build status](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/mgreter/libsass-513/branch/master) + +Why not using CMake? +-- + +There were some efforts to get `libsass` to compile with CMake, which should make it easier to create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! + +Miscellaneous +-- + +- [Ebuilds for Gentoo Linux](build-on-gentoo.md) + +[1]: build-with-makefiles.md +[2]: build-with-autotools.md +[3]: build-with-mingw.md +[4]: build-shared-library.md +[5]: https://github.com/sass/sass-spec +[6]: https://github.com/sass/sassc +[7]: https://github.com/sass/libsass/blob/master/.travis.yml +[8]: https://github.com/sass/libsass/blob/master/appveyor.yml +[9]: implementations.md +[10]: build-on-darwin.md +[11]: build-with-visual-studio.md diff --git a/docs/compatibility-plan.md b/docs/compatibility-plan.md new file mode 100644 index 000000000..d8e538fa4 --- /dev/null +++ b/docs/compatibility-plan.md @@ -0,0 +1,48 @@ +This document is to serve as a living, changing plan for getting LibSass caught up with Ruby Sass. + +_Note: an "s" preceeding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ + +# Goal +**Our goal is to reach full s3.4 compatibility as soon as possible. LibSass version 3.4 will behave just like Ruby Sass 3.4** + +I highlight the goal, because there are some things that are *not* currently priorities. To be clear, they WILL be priorities, but they are not at the moment: + +* Performance Improvements +* Extensibility + +The overriding goal is correctness. + +## Verifying Correctness +LibSass uses the spec for its testing. The spec was originally based off s3.2 tests. Many things have changed in Ruby Sass since then and some of the tests need to be updated and changed in order to get them to match both LibSass and Ruby Sass. + +Until this project is complete, the spec will be primarily a place to test LibSass. By the time LibSass reaches 3.4, it is our goal that sass-spec will be fully usable as an official testing source for ALL implementations of Sass. + +## Version Naming +Until LibSass reaches parity with Ruby Sass, we will be aggressively bumping versions, and LibSass 3.4 will be the peer to Ruby Sass 3.4 in every way. + +# Release Plan + +## 3.0 +The goal of 3.0 is to introduce some of the most demanded features for LibSass. That is, we are focusing on issues and features that have kept adoption down. This is a mongrel release wrt which version of Sass it's targeting. It's often a mixture of 3.2 / 3.3 / 3.4 behaviours. This is not ideal, but it's favourable to not existing. Targeting 3.4 strictly during this release would mean we never actually release. + +# 3.1 +The goal of 3.1 is to update all the passing specs to agree with 3.4. This will not be a complete representation of s3.4 (aka, there will me missing features), but the goal is to change existing features and implemented features to match 3.4 behaviour. + +By the end of this, the sass-spec must pass against 3.4. + +Major issues: +* Variable Scoping +* Color Handling +* Precision + +# 3.2 +This version will focus on edge case fixes. There are a LOT of edge cases in the _todo_ tests and this is the release where we hunt those down like dogs (not that we want to hurt dogs, it's just a figure of speech in English). + +# 3.3 +Dress rehearsal. When we are 99% sure that we've fixed the main issues keeping us from saying we are compliant in s3.4 behaviour. + +# 3.4 +Compass Compatibility. We need to be able to work with Compass and all the other libraries out there. At this point, we are calling LibSass "mature" + +# Beyond 3.4 +Obviously, there is matching Sass 3.5 behaviour. But, beyond that, we'll want to focus on performance, stability, and error handling. These can always be improved upon and are the life's work of an open source project. We'll have to work closely with Sass in the future. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 000000000..4a2d470ef --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,17 @@ +First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! + +## I'm a programmer + +Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! + +To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). + +## I'm not a backend programmer + +COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. + +Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. + +## I don't know what a computer is? + +Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/docs/custom-functions-internal.md b/docs/custom-functions-internal.md new file mode 100644 index 000000000..57fec82b8 --- /dev/null +++ b/docs/custom-functions-internal.md @@ -0,0 +1,122 @@ +# Developer Documentation + +Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. + +## Sass_C_Function_Descriptor + +```C +struct Sass_C_Function_Descriptor { + const char* signature; + Sass_C_Function function; + void* cookie; +}; +``` + +- `signature`: The function declaration, like `foo($bar, $baz:1)` +- `function`: Reference to the C function callback +- `cookie`: any pointer you want to attach + +### signature + +The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. + + foo($bar, $baz: 2) + +In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. + +### function + +The callback function needs to be of the following form: + +```C +union Sass_Value* call_sass_function( + const union Sass_Value* s_args, + void* cookie +) { + return sass_clone_value(s_args); +} +``` + +### cookie + +The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! + +## Sass_Values + +```C +// allocate memory (copies passed strings) +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// deallocate memory (incl. all copied memory) +void sass_delete_value (const union Sass_Value* val); +``` + +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((size_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +## Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` diff --git a/docs/dev-ast-memory.md b/docs/dev-ast-memory.md new file mode 100644 index 000000000..31004bcf2 --- /dev/null +++ b/docs/dev-ast-memory.md @@ -0,0 +1,223 @@ +# LibSass smart pointer implementation + +LibSass uses smart pointers very similar to `shared_ptr` known +by Boost or C++11. Implementation is a bit less modular since +it was not needed. Various compile time debug options are +available if you need to debug memory life-cycles. + + +## Memory Classes + +### SharedObj + +Base class for the actual node implementations. This ensures +that every object has a reference counter and other values. + +```c++ +class AST_Node : public SharedObj { ... }; +``` + +### SharedPtr (base class for SharedImpl) + +Base class that holds on to the pointer. The reference counter +is stored inside the pointer object directly (`SharedObj`). + +### SharedImpl (inherits from SharedPtr) + +This is the main base class for objects you use in your code. It +will make sure that the memory it points at will be deleted once +all copies to the same object/memory go out of scope. + +```c++ +Class* pointer = new Class(...); +SharedImpl obj(pointer); +``` + +To spare the developer of typing the templated class every time, +we created typedefs for each available AST Node specialization. + +```c++ +typedef SharedImpl Number_Obj; +Number_Obj number = SASS_MEMORY_NEW(...); +``` + + +## Memory life-cycles + +### Pointer pickups + +I often use the terminology of "pickup". This means the moment when +a raw pointer not under any control is assigned to a reference counted +object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be +automatically released once the object goes out of scope (but only +if the reference counter reaches zero). Main point beeing, you don't +have to worry about memory management yourself. + +### Object detach + +Sometimes we can't return reference counted objects directly (see +invalid covariant return types problems below). But we often still +need to use reference objects inside a function to avoid leaks when +something throws. For this you can use `detach`, which basically +detaches the pointer memory from the reference counted object. So +when the reference counted object goes out of scope, it will not +free the attached memory. You are now again in charge of freeing +the memory (just assign it to a reference counted object again). + + +## Circular references + +Reference counted memory implementations are prone to circular references. +This can be addressed by using a multi generation garbage collector. But +for our use-case that seems overkill. There is no way so far for users +(sass code) to create circular references. Therefore we can code around +this possible issue. But developers should be aware of this limitation. + +There are AFAIR two places where circular references could happen. One is +the `sources` member on every `Selector`. The other one can happen in the +extend code (Node handling). The easy way to avoid this is to only assign +complete object clones to these members. If you know the objects lifetime +is longer than the reference you create, you can also just store the raw +pointer. Once needed this could be solved with weak pointers. + + +## Addressing the invalid covariant return types problems + +If you are not familiar with the mentioned problem, you may want +to read up on covariant return types and virtual functions, i.e. + +- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers +- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers +- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr + +We hit this issue at least with the CRTP visitor pattern (eval, expand, +listize and so forth). This means we cannot return reference counted +objects directly. We are forced to return raw pointers or we would need +to have a lot of explicit and expensive upcasts by callers/consumers. + +### Simple functions that allocate new AST Nodes + +In the parser step we often create new objects and can just return a +unique pointer (meaning ownership clearly shifts back to the caller). +The caller/consumer is responsible that the memory is freed. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + return 42; +} +Number_Ptr parse_number() { + Number_Ptr p_nr = SASS_MEMORY_NEW(...); + p_nr->value(parse_integer()); + return p_nr; +} +Number_Obj nr = parse_number(); +``` + +The above would be the encouraged pattern for such simple cases. + +### Allocate new AST Nodes in functions that can throw + +There is a major caveat with the previous example, considering this +more real-life implementation that throws an error. The throw may +happen deep down in another function. Holding raw pointers that +we need to free would leak in this case. + +```c++ +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +``` + +With this `parse_integer` function the previous example would leak memory. +I guess it is pretty obvious, as the allocated memory will not be freed, +as it was never assigned to a SharedObj value. Therefore the above code +would better be written as: + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +// this leaks due to pointer return +// should return Number_Obj instead +// though not possible for virtuals! +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return &nr; // Ptr from Obj +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + +The example above unfortunately will not work as is, since we return a +`Number_Ptr` from that function. Therefore the object allocated inside +the function is already gone when it is picked up again by the caller. +The easy fix for the given simplified use case would be to change the +return type of `parse_number` to `Number_Obj`. Indeed we do it exactly +this way in the parser. But as stated above, this will not work for +virtual functions due to invalid covariant return types! + +### Return managed objects from virtual functions + +The easy fix would be to just create a new copy on the heap and return +that. But this seems like a very inelegant solution to this problem. I +mean why can't we just tell the object to treat it like a newly allocated +object? And indeed we can. I've added a `detach` method that will tell +the object to survive deallocation until the next pickup. This means +that it will leak if it is not picked up by consumer. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return nr.detach(); +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + + +## Compile time debug options + +To enable memory debugging you need to define `DEBUG_SHARED_PTR`. +This can i.e. be done in `include/sass/base.h` + +```c++ +define DEBUG_SHARED_PTR +``` + +This will print lost memory on exit to stderr. You can also use +`setDbg(true)` on sepecific variables to emit reference counter +increase, decrease and other events. + + +## Why reinvent the wheel when there is `shared_ptr` from C++11 + +First, implementing a smart pointer class is not really that hard. It +was indeed also a learning experience for myself. But there are more +profound advantages: + +- Better GCC 4.4 compatibility (which most code still has OOTB) +- Not thread safe (give us some free performance on some compiler) +- Beeing able to track memory allocations for debugging purposes +- Adding additional features if needed (as seen in `detach`) +- Optional: optimized weak pointer implementation possible + +### Thread Safety + +As said above, this is not thread safe currently. But we don't need +this ATM anyway. And I guess we probably never will share AST Nodes +across different threads. \ No newline at end of file diff --git a/docs/implementations.md b/docs/implementations.md new file mode 100644 index 000000000..5239adcde --- /dev/null +++ b/docs/implementations.md @@ -0,0 +1,65 @@ +There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. + +### C +* [sassc](https://github.com/hcatlin/sassc) + +### Crystal +* [sass.cr](https://github.com/straight-shoota/sass.cr) + +### Elixir +* [sass.ex](https://github.com/scottdavis/sass.ex) + +### Go +* [go-libsass](https://github.com/wellington/go-libsass) +* [go_sass](https://github.com/suapapa/go_sass) +* [go-sass](https://github.com/SamWhited/go-sass) + +### Haskell +* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) +* [hSass](https://github.com/jakubfijalkowski/hsass) + +### Java +* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) +* [jsass](https://github.com/bit3/jsass) + +### JavaScript +* [sass.js](https://github.com/medialize/sass.js) + +### Lua +* [lua-sass](https://github.com/craigbarnes/lua-sass) + +### .NET +* [libsass-net](https://github.com/darrenkopp/libsass-net) +* [NSass](https://github.com/TBAPI-0KA/NSass) +* [Sass.Net](https://github.com/andyalm/Sass.Net) +* [SharpScss](https://github.com/xoofx/SharpScss) +* [LibSassHost](https://github.com/Taritsyn/LibSassHost) + +### Nim +* [nim-sass](https://github.com/zacharycarter/nim-sass) + +### node.js +* [node-sass](https://github.com/sass/node-sass) + +### Perl +* [CSS::Sass](https://github.com/caldwell/CSS-Sass) +* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) + +### PHP +* [sassphp](https://github.com/sensational/sassphp) +* [php-sass](https://github.com/lesstif/php-sass) + +### Python +* [libsass-python](https://github.com/dahlia/libsass-python) +* [SassPython](https://github.com/marianoguerra/SassPython) +* [pylibsass](https://github.com/rsenk330/pylibsass) +* [python-scss](https://github.com/pistolero/python-scss) + +### Ruby +* [sassruby](https://github.com/hcatlin/sassruby) + +### Scala +* [Sass-Scala](https://github.com/kkung/Sass-Scala) + +### Tcl +* [tclsass](https://github.com/flightaware/tclsass) diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 000000000..a9711e3e1 --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,47 @@ +Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). + +## plugin.cpp + +```C++ +#include +#include +#include +#include "sass_values.h" + +union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, fn_foo); + // return the list + return fn_list; +} +``` + +To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). + +## Compile with gcc on linux + +```bash +g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +``` + +## Compile with mingw on windows + +```bash +g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass +``` diff --git a/docs/setup-environment.md b/docs/setup-environment.md new file mode 100644 index 000000000..805613656 --- /dev/null +++ b/docs/setup-environment.md @@ -0,0 +1,68 @@ +## Requirements +In order to install and setup your local development environment, there are some prerequisites: + +* git +* gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) +* ruby w/ bundler + +OS X: +First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: + +```` +xcode-select --install +```` + +## Cloning the Projects + +First, clone the project and then add a line to your `~/.bash_profile` that will let other programs know where the LibSass dev files are. + +```` +git clone git@github.com:sass/libsass.git +cd libsass +echo "export SASS_LIBSASS_PATH=$(pwd)" >> ~/.bash_profile + +```` + +Then, if you run the "bootstrap" script, it should clone all the other required projects. + +```` +./script/bootstrap +```` + +You should now have a `sass-spec` and `sassc` folder within the libsass folder. Both of these are clones of their respective git projects. If you want to do a pull request, remember to work in those folders. For instance, if you want to add a test (see other documentation for how to do that), make sure to commit it to your *fork* of the sass-spec github project. Also, whenever you are running tests, make sure to `pull` from the origin! We want to make sure we are testing against the newest libsass, sassc, and sass-spec! + +Now, try and see if you can build the project. We do that with the `make` command. + +```` +make +```` + +At this point, if you get an error, something is most likely wrong with your compiler installation. Yikes. It's hard to cover how to fix this in an article. Feel free to open an issue and we'll try and help! But, remember, before you do that, googling the error message is your friend! Many problems are solved quickly that way. + +## Running The Spec Against LibSass + +Then, to run the spec against LibSass, just run: + +```` +./script/spec +```` + +If you get an error about `SASS_LIBSASS_PATH`, you may still need to set a variable pointing to the libsass folder, like this: + +```` +export SASS_LIBSASS_PATH=/Users/you/path/libsass +```` + +...where the latter part is to the `libsass` directory you've cloned. You can get this path by typing `pwd` in the Terminal + +## Running the Spec Against Ruby Sass + +Go into the sass-spec folder that should have been cloned earlier with the "bootstrap" command. Run the following. + +```` +bundle install +./sass-spec.rb +```` + +Voila! Now you are testing against Sass too! + diff --git a/docs/source-map-internals.md b/docs/source-map-internals.md new file mode 100644 index 000000000..50f83b54f --- /dev/null +++ b/docs/source-map-internals.md @@ -0,0 +1,51 @@ +This document is mainly intended for developers! + +# Documenting some of the source map internals + +Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. + +The main storage for SourceMap mappings is the `mappings` vector: + +``` +# in source_map.hpp +vector mappings +# in mappings.hpp +struct Mapping ... + Position original_position; + Position generated_position; +``` + +## Every parsed token has its source associated + +LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: + +``` +if (lex< custom_property_name >()) { + Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); + return new (ctx.mem) Declaration(path, prop->position(), prop, ...); +} +``` + +## How is the `source_position` calculated + +This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! + +``` +lex< exactly < empty_str > >(); +end = new (ctx.mem) String_Constant(path, source_position, lexed); +``` + +## How are mappings for the output created + +So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: + +``` +# in source_map.hpp +void add_mapping(AST_Node* node); +``` + +This method is called in two places: +- `Inspect::append_to_buffer` +- `Output_[Nested|Compressed]::append_to_buffer` + +Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/docs/trace.md b/docs/trace.md new file mode 100644 index 000000000..4a57c901f --- /dev/null +++ b/docs/trace.md @@ -0,0 +1,26 @@ +## This is proposed interface in https://github.com/sass/libsass/pull/1288 + +Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. + +Both macros simulate a string stream, so they can be used like this: + + TRACE() << "Reached."; + +produces: + + [LibSass] parse_value parser.cpp:1384 Reached. + +`TRACE()` + logs function name, source filename, source file name to the standard error and the attached + stream to the standard error. + +`TRACEINST(obj)` + logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: + + TRACEINST(this) << "String_Constant created " << this; + +produces: + + [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") + +The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/docs/triage.md b/docs/triage.md new file mode 100644 index 000000000..0fc11784c --- /dev/null +++ b/docs/triage.md @@ -0,0 +1,17 @@ +This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: + +1. Issue is reported by a user. +2. If the issue seems like a bug, then the "bug" tag is added. +3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. +4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. +5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. +6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! +7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. +8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". +9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". +10. A C++ developer will then work on the issue and issue a pull request to fix the issue. +11. A core member verifies that the fix does actually fix the spec tests. +12. The fix is merged into the project. +13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues +14. The issue is closed +15. Have a soda pop or enjoyable beverage of your choice diff --git a/docs/unicode.md b/docs/unicode.md new file mode 100644 index 000000000..a1eb5b1cf --- /dev/null +++ b/docs/unicode.md @@ -0,0 +1,45 @@ +LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your +input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected +behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! + +### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) + +This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. + +Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). + +### Current status on LibSass unicode support + +LibSass should/is fully UTF (and therefore plain ASCII) compatible. + +~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ + +LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. + +### Current encoding auto detection + +LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! + +### What is currently not supported + +- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) +- Using non ASCII characters in different encodings in different includes + +### What is missing to support the above cases + +- A way to convert between encodings (like libiconv/ICU) +- Sniffing the charset inside the file (source is available) +- Handling the conversion on import (and export) +- Optional: Make output encoding configurable +- Optional: Add optional/mandatory BOM (configurable) + +### Low priority feature + +I guess the current implementation should handle more than 99% of all real world use cases. +A) Unicode characters are still seldomly seen (as they can be written escaped) +~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. +Although I'm not sure how this applies to asian and other "exotic" codepages!~~ + +I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). + +I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/extconf.rb b/extconf.rb new file mode 100644 index 000000000..3e6d00bc9 --- /dev/null +++ b/extconf.rb @@ -0,0 +1,6 @@ +require 'mkmf' +# .. more stuff +#$LIBPATH.push(Config::CONFIG['libdir']) +$CFLAGS << " #{ENV["CFLAGS"]}" +$LIBS << " #{ENV["LIBS"]}" +create_makefile("libsass") diff --git a/include/sass.h b/include/sass.h new file mode 100644 index 000000000..1dd8b06dc --- /dev/null +++ b/include/sass.h @@ -0,0 +1,15 @@ +#ifndef SASS_H +#define SASS_H + +// #define DEBUG 1 + +// include API headers +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/include/sass/base.h b/include/sass/base.h new file mode 100644 index 000000000..88dd8d303 --- /dev/null +++ b/include/sass/base.h @@ -0,0 +1,89 @@ +#ifndef SASS_BASE_H +#define SASS_BASE_H + +// #define DEBUG_SHARED_PTR + +#ifdef _MSC_VER + #pragma warning(disable : 4503) + #ifndef _SCL_SECURE_NO_WARNINGS + #define _SCL_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#include +#include + +#ifdef __GNUC__ + #define DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) + #define DEPRECATED(func) __declspec(deprecated) func +#else + #pragma message("WARNING: You need to implement DEPRECATED for this compiler") + #define DEPRECATED(func) func +#endif + +#ifdef _WIN32 + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #define ADDCALL __cdecl + #else + #define ADDAPI + #define ADDCALL + #endif + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +/* Make sure functions are exported with C linkage under C++ compilers. */ +#ifdef __cplusplus +extern "C" { +#endif + + +// Different render styles +enum Sass_Output_Style { + SASS_STYLE_NESTED, + SASS_STYLE_EXPANDED, + SASS_STYLE_COMPACT, + SASS_STYLE_COMPRESSED, + // only used internaly + SASS_STYLE_INSPECT, + SASS_STYLE_TO_SASS +}; + +// to allocate buffer to be filled +ADDAPI void* ADDCALL sass_alloc_memory(size_t size); +// to allocate a buffer from existing string +ADDAPI char* ADDCALL sass_copy_c_string(const char* str); +// to free overtaken memory when done +ADDAPI void ADDCALL sass_free_memory(void* ptr); + +// Some convenient string helper function +ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); +ADDAPI char* ADDCALL sass_string_unquote (const char* str); + +// Implemented sass language version +// Hardcoded version 3.4 for time being +ADDAPI const char* ADDCALL libsass_version(void); + +// Get compiled libsass language +ADDAPI const char* ADDCALL libsass_language_version(void); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/context.h b/include/sass/context.h new file mode 100644 index 000000000..2f88d6888 --- /dev/null +++ b/include/sass/context.h @@ -0,0 +1,170 @@ +#ifndef SASS_C_CONTEXT_H +#define SASS_C_CONTEXT_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; // base struct +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// Create and initialize an option struct +ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); +// Create and initialize a specific context +ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); +ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); +ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// Usefull if you only want to query the included files +ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); +ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); + +// Release all memory allocated with the compiler +// This does _not_ include any contexts or options +ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); +ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); +ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for context from specific implementation +ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); +ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + + +// Getters for Context_Option values +ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); +ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); +ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); + +// Setters for Context_Option values +ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); +ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); +ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); +ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); +ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); + + +// Getters for Sass_Context values +ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); +ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); + +// Getters for options include path array +ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); + +// Calculate the size of the stored null terminated array +ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); + +// Take ownership of memory (value on context is set to 0) +ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); + +// Getters for Sass_Compiler options +ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); +ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); +ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); +ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); + +// Push function for paths (no manipulation support for now) +ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); + +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); +ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/functions.h b/include/sass/functions.h new file mode 100644 index 000000000..ac47e8ede --- /dev/null +++ b/include/sass/functions.h @@ -0,0 +1,139 @@ +#ifndef SASS_C_FUNCTIONS_H +#define SASS_C_FUNCTIONS_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +struct Sass_Env; +struct Sass_Callee; +struct Sass_Import; +struct Sass_Options; +struct Sass_Compiler; +struct Sass_Importer; +struct Sass_Function; + +// Typedef helpers for callee lists +typedef struct Sass_Env (*Sass_Env_Frame); +// Typedef helpers for callee lists +typedef struct Sass_Callee (*Sass_Callee_Entry); +// Typedef helpers for import lists +typedef struct Sass_Import (*Sass_Import_Entry); +typedef struct Sass_Import* (*Sass_Import_List); +// Typedef helpers for custom importer lists +typedef struct Sass_Importer (*Sass_Importer_Entry); +typedef struct Sass_Importer* (*Sass_Importer_List); +// Typedef defining importer signature and return type +typedef Sass_Import_List (*Sass_Importer_Fn) + (const char* url, Sass_Importer_Entry cb, struct Sass_Compiler* compiler); + +// Typedef helpers for custom functions lists +typedef struct Sass_Function (*Sass_Function_Entry); +typedef struct Sass_Function* (*Sass_Function_List); +// Typedef defining function signature and return type +typedef union Sass_Value* (*Sass_Function_Fn) + (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); + +// Type of function calls +enum Sass_Callee_Type { + SASS_CALLEE_MIXIN, + SASS_CALLEE_FUNCTION, + SASS_CALLEE_C_FUNCTION, +}; + +// Creator for sass custom importer return argument list +ADDAPI Sass_Importer_List ADDCALL sass_make_importer_list (size_t length); +ADDAPI Sass_Importer_Entry ADDCALL sass_importer_get_list_entry (Sass_Importer_List list, size_t idx); +ADDAPI void ADDCALL sass_importer_set_list_entry (Sass_Importer_List list, size_t idx, Sass_Importer_Entry entry); +ADDAPI void ADDCALL sass_delete_importer_list (Sass_Importer_List list); + + +// Creators for custom importer callback (with some additional pointer) +// The pointer is mostly used to store the callback into the actual binding +ADDAPI Sass_Importer_Entry ADDCALL sass_make_importer (Sass_Importer_Fn importer, double priority, void* cookie); + +// Getters for import function descriptors +ADDAPI Sass_Importer_Fn ADDCALL sass_importer_get_function (Sass_Importer_Entry cb); +ADDAPI double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb); +ADDAPI void* ADDCALL sass_importer_get_cookie (Sass_Importer_Entry cb); + +// Deallocator for associated memory +ADDAPI void ADDCALL sass_delete_importer (Sass_Importer_Entry cb); + +// Creator for sass custom importer return argument list +ADDAPI Sass_Import_List ADDCALL sass_make_import_list (size_t length); +// Creator for a single import entry returned by the custom importer inside the list +ADDAPI Sass_Import_Entry ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); +ADDAPI Sass_Import_Entry ADDCALL sass_make_import (const char* imp_path, const char* abs_base, char* source, char* srcmap); +// set error message to abort import and to print out a message (path from existing object is used in output) +ADDAPI Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); +ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx); + +// Getters for callee entry +ADDAPI const char* ADDCALL sass_callee_get_name (Sass_Callee_Entry); +ADDAPI const char* ADDCALL sass_callee_get_path (Sass_Callee_Entry); +ADDAPI size_t ADDCALL sass_callee_get_line (Sass_Callee_Entry); +ADDAPI size_t ADDCALL sass_callee_get_column (Sass_Callee_Entry); +ADDAPI enum Sass_Callee_Type ADDCALL sass_callee_get_type (Sass_Callee_Entry); +ADDAPI Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry); + +// Getters and Setters for environments (lexical, local and global) +ADDAPI union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame, const char*); +ADDAPI void ADDCALL sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); +ADDAPI union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame, const char*); +ADDAPI void ADDCALL sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); +ADDAPI union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame, const char*); +ADDAPI void ADDCALL sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); + +// Getters for import entry +ADDAPI const char* ADDCALL sass_import_get_imp_path (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_abs_path (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_source (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_srcmap (Sass_Import_Entry); +// Explicit functions to take ownership of these items +// The property on our struct will be reset to NULL +ADDAPI char* ADDCALL sass_import_take_source (Sass_Import_Entry); +ADDAPI char* ADDCALL sass_import_take_srcmap (Sass_Import_Entry); +// Getters from import error entry +ADDAPI size_t ADDCALL sass_import_get_error_line (Sass_Import_Entry); +ADDAPI size_t ADDCALL sass_import_get_error_column (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_error_message (Sass_Import_Entry); + +// Deallocator for associated memory (incl. entries) +ADDAPI void ADDCALL sass_delete_import_list (Sass_Import_List); +// Just in case we have some stray import structs +ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry); + + + +// Creators for sass function list and function descriptors +ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); +ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); +ADDAPI void ADDCALL sass_delete_function (Sass_Function_Entry entry); +ADDAPI void ADDCALL sass_delete_function_list (Sass_Function_List list); + +// Setters and getters for callbacks on function lists +ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); +ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); + +// Getters for custom function descriptors +ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); +ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); +ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); + + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/values.h b/include/sass/values.h new file mode 100644 index 000000000..9832038b7 --- /dev/null +++ b/include/sass/values.h @@ -0,0 +1,145 @@ +#ifndef SASS_C_VALUES_H +#define SASS_C_VALUES_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +union Sass_Value; + +// Type for Sass values +enum Sass_Tag { + SASS_BOOLEAN, + SASS_NUMBER, + SASS_COLOR, + SASS_STRING, + SASS_LIST, + SASS_MAP, + SASS_NULL, + SASS_ERROR, + SASS_WARNING +}; + +// Tags for denoting Sass list separators +enum Sass_Separator { + SASS_COMMA, + SASS_SPACE, + // only used internally to represent a hash map before evaluation + // otherwise we would be too early to check for duplicate keys + SASS_HASH +}; + +// Value Operators +enum Sass_OP { + AND, OR, // logical connectives + EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations + ADD, SUB, MUL, DIV, MOD, // arithmetic functions + NUM_OPS // so we know how big to make the op table +}; + +// Creator functions for all value types +ADDAPI union Sass_Value* ADDCALL sass_make_null (void); +ADDAPI union Sass_Value* ADDCALL sass_make_boolean (bool val); +ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); +ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); +ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); +ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); +ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); +ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); +ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg); + +// Generic destructor function for all types +// Will release memory of all associated Sass_Values +// Means we will delete recursively for lists and maps +ADDAPI void ADDCALL sass_delete_value (union Sass_Value* val); + +// Make a deep cloned copy of the given sass value +ADDAPI union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val); + +// Execute an operation for two Sass_Values and return the result as a Sass_Value too +ADDAPI union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); + +// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) +ADDAPI union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); + +// Return the sass tag for a generic sass value +// Check is needed before accessing specific values! +ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v); + +// Check value to be of a specific type +// Can also be used before accessing properties! +ADDAPI bool ADDCALL sass_value_is_null (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_number (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_string (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_boolean (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_color (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_list (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_map (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_error (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_warning (const union Sass_Value* v); + +// Getters and setters for Sass_Number +ADDAPI double ADDCALL sass_number_get_value (const union Sass_Value* v); +ADDAPI void ADDCALL sass_number_set_value (union Sass_Value* v, double value); +ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v); +ADDAPI void ADDCALL sass_number_set_unit (union Sass_Value* v, char* unit); + +// Getters and setters for Sass_String +ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v); +ADDAPI void ADDCALL sass_string_set_value (union Sass_Value* v, char* value); +ADDAPI bool ADDCALL sass_string_is_quoted(const union Sass_Value* v); +ADDAPI void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted); + +// Getters and setters for Sass_Boolean +ADDAPI bool ADDCALL sass_boolean_get_value (const union Sass_Value* v); +ADDAPI void ADDCALL sass_boolean_set_value (union Sass_Value* v, bool value); + +// Getters and setters for Sass_Color +ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r); +ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g); +ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b); +ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v); +ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a); + +// Getter for the number of items in list +ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); +// Getters and setters for Sass_List +ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v); +ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +ADDAPI bool ADDCALL sass_list_get_is_bracketed (const union Sass_Value* v); +ADDAPI void ADDCALL sass_list_set_is_bracketed (union Sass_Value* v, bool value); +// Getters and setters for Sass_List values +ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); +ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); + +// Getter for the number of items in map +ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v); +// Getters and setters for Sass_Map keys and values +ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i); +ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); +ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i); +ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); + +// Getters and setters for Sass_Error +ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v); +ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg); + +// Getters and setters for Sass_Warning +ADDAPI char* ADDCALL sass_warning_get_message (const union Sass_Value* v); +ADDAPI void ADDCALL sass_warning_set_message (union Sass_Value* v, char* msg); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/version.h b/include/sass/version.h new file mode 100644 index 000000000..56ea016a2 --- /dev/null +++ b/include/sass/version.h @@ -0,0 +1,12 @@ +#ifndef SASS_VERSION_H +#define SASS_VERSION_H + +#ifndef LIBSASS_VERSION +#define LIBSASS_VERSION "[NA]" +#endif + +#ifndef LIBSASS_LANGUAGE_VERSION +#define LIBSASS_LANGUAGE_VERSION "3.5" +#endif + +#endif diff --git a/include/sass/version.h.in b/include/sass/version.h.in new file mode 100644 index 000000000..b8d4072d4 --- /dev/null +++ b/include/sass/version.h.in @@ -0,0 +1,12 @@ +#ifndef SASS_VERSION_H +#define SASS_VERSION_H + +#ifndef LIBSASS_VERSION +#define LIBSASS_VERSION "@PACKAGE_VERSION@" +#endif + +#ifndef LIBSASS_LANGUAGE_VERSION +#define LIBSASS_LANGUAGE_VERSION "3.5" +#endif + +#endif diff --git a/include/sass2scss.h b/include/sass2scss.h new file mode 100644 index 000000000..8736b2cb9 --- /dev/null +++ b/include/sass2scss.h @@ -0,0 +1,120 @@ +/** + * sass2scss + * Licensed under the MIT License + * Copyright (c) Marcel Greter + */ + +#ifndef SASS2SCSS_H +#define SASS2SCSS_H + +#ifdef _WIN32 + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #define ADDCALL __cdecl + #else + #define ADDAPI + #define ADDCALL + #endif + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +#ifndef SASS2SCSS_VERSION +// Hardcode once the file is copied from +// https://github.com/mgreter/sass2scss +#define SASS2SCSS_VERSION "1.1.1" +#endif + +// add namespace for c++ +namespace Sass +{ + + // pretty print options + const int SASS2SCSS_PRETTIFY_0 = 0; + const int SASS2SCSS_PRETTIFY_1 = 1; + const int SASS2SCSS_PRETTIFY_2 = 2; + const int SASS2SCSS_PRETTIFY_3 = 3; + + // remove one-line comment + const int SASS2SCSS_KEEP_COMMENT = 32; + // remove multi-line comments + const int SASS2SCSS_STRIP_COMMENT = 64; + // convert one-line to multi-line + const int SASS2SCSS_CONVERT_COMMENT = 128; + + // String for finding something interesting + const std::string SASS2SCSS_FIND_WHITESPACE = " \t\n\v\f\r"; + + // converter struct + // holding all states + struct converter + { + // bit options + int options; + // is selector + bool selector; + // concat lists + bool comma; + // has property + bool property; + // has semicolon + bool semicolon; + // comment context + std::string comment; + // flag end of file + bool end_of_file; + // whitespace buffer + std::string whitespace; + // context/block stack + std::stack indents; + }; + + // function only available in c++ code + char* sass2scss (const std::string& sass, const int options); + +} +// EO namespace + +// declare for c +extern "C" { +#endif + + // prettyfy print options + #define SASS2SCSS_PRETTIFY_0 0 + #define SASS2SCSS_PRETTIFY_1 1 + #define SASS2SCSS_PRETTIFY_2 2 + #define SASS2SCSS_PRETTIFY_3 3 + + // keep one-line comments + #define SASS2SCSS_KEEP_COMMENT 32 + // remove multi-line comments + #define SASS2SCSS_STRIP_COMMENT 64 + // convert one-line to multi-line + #define SASS2SCSS_CONVERT_COMMENT 128 + + // available to c and c++ code + ADDAPI char* ADDCALL sass2scss (const char* sass, const int options); + + // Get compiled sass2scss version + ADDAPI const char* ADDCALL sass2scss_version(void); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif \ No newline at end of file diff --git a/m4/.gitkeep b/m4/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/m4/m4-ax_cxx_compile_stdcxx_11.m4 b/m4/m4-ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 000000000..395b13d2a --- /dev/null +++ b/m4/m4-ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,167 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++11 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX11 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { + func(0); + } + } +]]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], + [$2], [optional], [ax_cxx_compile_cxx11_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + for switch in -std=c++11 -std=c++0x +std=c++11; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + AC_MSG_NOTICE([No compiler with C++11 support was found]) + else + HAVE_CXX11=1 + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++11 syntax]) + fi + + AC_SUBST(HAVE_CXX11) + fi +]) diff --git a/res/resource.rc b/res/resource.rc new file mode 100644 index 000000000..fc49e6a87 --- /dev/null +++ b/res/resource.rc @@ -0,0 +1,35 @@ +#include + +// DLL version information. +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE +#else + FILEFLAGS 0 +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "CompanyName", "Sass Open Source Foundation" + VALUE "FileDescription", "A C/C++ implementation of a Sass compiler" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "libsass" + VALUE "LegalCopyright", "\251 2017 libsass.org" + VALUE "OriginalFilename", "libsass.dll" + VALUE "ProductName", "LibSass Library" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 000000000..ab82fac94 --- /dev/null +++ b/script/bootstrap @@ -0,0 +1,13 @@ +#!/bin/bash + +script/branding + +: ${SASS_SPEC_PATH:="sass-spec"} +: ${SASS_SASSC_PATH:="sassc" } + +if [ ! -d $SASS_SPEC_PATH ]; then + git clone https://github.com/sass/sass-spec.git $SASS_SPEC_PATH +fi +if [ ! -d $SASS_SASSC_PATH ]; then + git clone https://github.com/sass/sassc.git $SASS_SASSC_PATH +fi diff --git a/script/branding b/script/branding new file mode 100755 index 000000000..cd8cb2a52 --- /dev/null +++ b/script/branding @@ -0,0 +1,10 @@ +#! /bin/bash + +echo " " +echo " _ ___ ____ ____ _ ____ ____ " +echo "| | |_ _| __ ) ___| / \ / ___/ ___| " +echo "| | | || _ \___ \ / _ \ \___ \___ \ " +echo "| |___ | || |_) |__) / ___ \ ___) |__) |" +echo "|_____|___|____/____/_/ \_\____/____/ " +echo " " + diff --git a/script/ci-build-libsass b/script/ci-build-libsass new file mode 100755 index 000000000..40ea22ff7 --- /dev/null +++ b/script/ci-build-libsass @@ -0,0 +1,134 @@ +#!/bin/bash + +set -e + +script/bootstrap + +# export this path right here (was in script/spec before) +export SASS_LIBSASS_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../ && pwd )" + +# use some defaults if not running under travis ci +if [ "x$CONTINUOUS_INTEGRATION" == "x" ]; then export CONTINUOUS_INTEGRATION=true; fi +if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then export TRAVIS_BUILD_DIR=$(pwd); fi +if [ "x$SASS_SASSC_PATH" == "x" ]; then export SASS_SASSC_PATH=$(pwd)/sassc; fi +if [ "x$SASS_SPEC_PATH" == "x" ]; then export SASS_SPEC_PATH=$(pwd)/sass-spec; fi + +# try to get the os name from uname (and filter via perl - probably not the most portable way?) +if [ "x$TRAVIS_OS_NAME" == "x" ]; then export TRAVIS_OS_NAME=`uname -s | perl -ne 'print lc \$1 if\(/^([a-zA-Z]+)/'\)`; fi + +if [ "x$COVERAGE" == "xyes" ]; then + COVERAGE_OPT="--enable-coverage" + export EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" + export EXTRA_CXXFLAGS="-fprofile-arcs -ftest-coverage" + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + # osx doesn't seem to know gcov lib? + export EXTRA_LDFLAGS="--coverage" + else + export EXTRA_LDFLAGS="-lgcov --coverage" + fi +else + COVERAGE_OPT="--disable-coverage" +fi + +if [ "x$BUILD" == "xstatic" ]; then + SHARED_OPT="--disable-shared --enable-static" + MAKE_TARGET="static" +else + # Makefile of sassc wants to link to static + SHARED_OPT="--enable-shared --enable-static" + MAKE_TARGET="shared" +fi + +if [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then + MAKE_OPTS="$MAKE_OPTS -j1 V=1" +else + MAKE_OPTS="$MAKE_OPTS -j5 V=1" +fi + +if [ "x$PREFIX" == "x" ]; then + if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then + PREFIX=$SASS_LIBSASS_PATH/build + else + PREFIX=$TRAVIS_BUILD_DIR/build + fi +fi + +# enable address sanitation +# https://en.wikipedia.org/wiki/AddressSanitizer +if [ "x$CC" == "xclang" ]; then + if [ "x$COVERAGE" != "xyes" ]; then + if [ "$TRAVIS_OS_NAME" == "linux" ]; then + export EXTRA_CFLAGS="$EXTRA_CFLAGS -fsanitize=address" + export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -fsanitize=address" + export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fsanitize=address" + fi + fi +fi + +echo SASS_LIBSASS_PATH: $SASS_LIBSASS_PATH +echo TRAVIS_BUILD_DIR: $TRAVIS_BUILD_DIR +echo SASS_SASSC_PATH: $SASS_SASSC_PATH +echo SASS_SPEC_PATH: $SASS_SPEC_PATH +echo INSTALL_LOCATION: $PREFIX + +if [ "x$AUTOTOOLS" == "xyes" ]; then + + echo -en 'travis_fold:start:configure\r' + autoreconf --force --install + ./configure --enable-tests $COVERAGE_OPT \ + --disable-silent-rules \ + --with-sassc-dir=$SASS_SASSC_PATH \ + --with-sass-spec-dir=$SASS_SPEC_PATH \ + --prefix=$PREFIX \ + ${SHARED_OPT} + echo -en 'travis_fold:end:configure\r' + + make $MAKE_OPTS clean + + # install to prefix directory + PREFIX="$PREFIX" make $MAKE_OPTS install + +else + + make $MAKE_OPTS clean + +fi + +# install to prefix directory +PREFIX="$PREFIX" make $MAKE_OPTS install + +ls -la $PREFIX/* + +echo successfully compiled libsass +echo AUTOTOOLS=$AUTOTOOLS COVERAGE=$COVERAGE BUILD=$BUILD + +if [ "$CONTINUOUS_INTEGRATION" == "true" ] && [ "$TRAVIS_PULL_REQUEST" != "false" ] && [ "x$TRAVIS_PULL_REQUEST" != "x" ] && + ([ "$TRAVIS_OS_NAME" == "linux" ] || [ "$TRAVIS_OS_NAME" == "osx" ] || [ "$TRAVIS_OS_NAME" == "cygwin" ]); +then + + echo "Fetching PR $TRAVIS_PULL_REQUEST" + + JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + + if [[ $JSON =~ "API rate limit exceeded" ]]; + then + echo "Travis rate limit on github exceeded" + echo "Retrying via 'special purpose proxy'" + JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + fi + + RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + + if [[ $JSON =~ $RE_SPEC_PR ]]; + then + SPEC_PR="${BASH_REMATCH[2]}" + echo "Fetching Sass Spec PR $SPEC_PR" + git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR + git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR + LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + else + LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + fi +else + LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe +fi diff --git a/script/ci-build-plugin b/script/ci-build-plugin new file mode 100755 index 000000000..0dd67b991 --- /dev/null +++ b/script/ci-build-plugin @@ -0,0 +1,62 @@ +#!/bin/bash + +PLUGIN=$1 +RUBY_BIN=ruby +SASS_SPEC_PATH=sass-spec +SASSC_BIN=sassc/bin/sassc +SASS_SPEC_SPEC_DIR=plugins/libsass-${PLUGIN}/test + +if [ -e ./tester ] ; then + SASSC_BIN=./tester +fi + +if [ -d ./build/lib ] ; then + cp -a build/lib lib +fi + +if [ "x$1" == "x" ] ; then + echo "No plugin name given" + exit 1 +fi + +if [ "x$COVERAGE" == "0" ] ; then + unset COVERAGE +fi + +export EXTRA_CFLAGS="" +export EXTRA_CXXFLAGS="" +if [ "$TRAVIS_OS_NAME" == "osx" ]; then + # osx doesn't seem to know gcov lib? + export EXTRA_LDFLAGS="--coverage" +else + export EXTRA_LDFLAGS="-lgcov --coverage" +fi + +mkdir -p plugins +if [ ! -d plugins/libsass-${PLUGIN} ] ; then + git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} +fi +if [ ! -d plugins/libsass-${PLUGIN}/build ] ; then + mkdir plugins/libsass-${PLUGIN}/build +fi +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi + +cd plugins/libsass-${PLUGIN}/build +cmake -G "Unix Makefiles" -D LIBSASS_DIR="../../.." .. +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi +make VERBOSE=1 -j2 +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi +cd ../../.. + +# glob only works on paths relative to imports +if [ "x$PLUGIN" == "xglob" ]; then + ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss > ${SASS_SPEC_SPEC_DIR}/basic/result.css + ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss --sourcemap > /dev/null +else + cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --precision 5 --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic > ${SASS_SPEC_SPEC_DIR}/basic/result.css + cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --precision 5 --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic --sourcemap > /dev/null +fi +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi + +diff ${SASS_SPEC_SPEC_DIR}/basic/expected_output.css ${SASS_SPEC_SPEC_DIR}/basic/result.css +RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi diff --git a/script/ci-install-compiler b/script/ci-install-compiler new file mode 100755 index 000000000..3a68b3a39 --- /dev/null +++ b/script/ci-install-compiler @@ -0,0 +1,6 @@ +#!/bin/bash + +gem install minitest +gem install minitap + +pip2 install --user 'requests[security]' diff --git a/script/ci-install-deps b/script/ci-install-deps new file mode 100755 index 000000000..27b485aa7 --- /dev/null +++ b/script/ci-install-deps @@ -0,0 +1,20 @@ +#!/bin/bash +if [ "x$COVERAGE" == "xyes" ]; then + pip2 install --user gcovr + pip2 install --user cpp-coveralls +else + echo "no dependencies to install" +fi + +if [ "x$AUTOTOOLS" == "xyes" ]; then + AUTOTOOLS=yes + + if [ "$TRAVIS_OS_NAME" == "linux" ]; then + sudo add-apt-repository -y ppa:rbose-debianizer/automake &> /dev/null + sudo apt-get -qq update + sudo apt-get -qq install automake + fi + +fi + +exit 0 diff --git a/script/ci-report-coverage b/script/ci-report-coverage new file mode 100755 index 000000000..495cb05cb --- /dev/null +++ b/script/ci-report-coverage @@ -0,0 +1,42 @@ +#!/bin/bash + +if [ "x$COVERAGE" = "xyes" ]; then + + # find / -name "gcovr" + # find / -name "coveralls" + # this is only needed for mac os x builds! + PATH=$PATH:/Users/travis/Library/Python/2.7/bin/ + + + # exclude some directories from profiling (.libs is from autotools) + export EXCLUDE_COVERAGE="--exclude plugins + --exclude sassc/sassc.c + --exclude src/sass-spec + --exclude src/.libs + --exclude src/debug.hpp + --exclude src/json.cpp + --exclude src/json.hpp + --exclude src/cencode.c + --exclude src/b64 + --exclude src/utf8 + --exclude src/utf8_string.hpp + --exclude src/utf8.h + --exclude src/utf8_string.cpp + --exclude src/sass2scss.h + --exclude src/sass2scss.cpp + --exclude src/test + --exclude src/posix + --exclude src/debugger.hpp" + # debug used gcov version + # option not available on mac + if [ "$TRAVIS_OS_NAME" != "osx" ]; then + gcov -v + fi + # create summarized report + gcovr -r . + # submit report to coveralls.io + coveralls $EXCLUDE_COVERAGE --gcov-options '\-lp' + +else + echo "skip coverage reporting" +fi diff --git a/script/spec b/script/spec new file mode 100755 index 000000000..d0b864a13 --- /dev/null +++ b/script/spec @@ -0,0 +1,5 @@ +#!/bin/bash + +script/bootstrap + +make $MAKE_OPTS test_build diff --git a/script/tap-driver b/script/tap-driver new file mode 100755 index 000000000..ed8a9a9dd --- /dev/null +++ b/script/tap-driver @@ -0,0 +1,652 @@ +#!/usr/bin/env sh +# Copyright (C) 2011-2013 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +scriptversion=2011-12-27.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat < + # + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# FIXME: the usages of "cat >&3" below could be optimized when using +# FIXME: GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +# FIXME: this can certainly be improved ... +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assing the next progresive number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/script/tap-runner b/script/tap-runner new file mode 100755 index 000000000..56c13bfb4 --- /dev/null +++ b/script/tap-runner @@ -0,0 +1 @@ +$@ $TEST_FLAGS --tap --silent | tapout tap diff --git a/script/test-leaks.pl b/script/test-leaks.pl new file mode 100755 index 000000000..bfb8653f4 --- /dev/null +++ b/script/test-leaks.pl @@ -0,0 +1,103 @@ +#!/usr/bin/perl +############################################################ +# this perl script is meant for developers only! +# it will run all spec-tests (without verifying the +# results) via valgrind to detect possible leaks. +# expect that it takes 1h or more to finish! +############################################################ +# Prerequisite install: `cpan Parallel::Runner` +# You may also need to install `cpan File::Find` +# You may also need to install `cpan IPC::Run3` +############################################################ +# usage: `perl test-leaks.pl [threads]` +# example: `time perl test-leaks.pl 4` +############################################################ +# leaks will be reported in "mem-leaks.log" +############################################################ + +use strict; +use warnings; + +############################################################ +# configurations (you may adjust) +############################################################ + +# number of threads to use +my $threads = $ARGV[0] || 8; + +# the github repositories to checkout +# if you need other branch, clone manually! +my $sassc = "https://www.github.com/sass/sassc"; +my $specs = "https://www.github.com/sass/sass-spec"; + +############################################################ +# load modules +############################################################ + +use IPC::Run3; +use IO::Handle; +use Fcntl qw(:flock); +use File::Find::Rule; +use Parallel::Runner; +use List::Util qw(shuffle); + +############################################################ +# check prerequisites +############################################################ + +unless (-d "../sassc") { + warn "sassc folder not found\n"; + warn "trying to checkout via git\n"; + system("git", "clone", $sassc, "../sassc"); + die "git command did not exit gracefully" if $?; +} + +unless (-d "../sass-spec") { + warn "sass-spec folder not found\n"; + warn "trying to checkout via git\n"; + system("git", "clone", $specs, "../sass-spec"); + die "git command did not exit gracefully" if $?; +} + +unless (-f "../sassc/bin/sassc") { + warn "sassc executable not found\n"; + warn "trying to compile via make\n"; + system("make", "-C", "../sassc", "-j", $threads); + die "make command did not exit gracefully" if $?; +} + +############################################################ +# main runner code +############################################################ + +my $root = "../sass-spec/spec"; +my @files = File::Find::Rule->file() + ->name('input.scss')->in($root); + +open(my $leaks, ">", "mem-leaks.log"); +die "Cannot open log" unless $leaks; +my $runner = Parallel::Runner->new($threads); +die "Cannot start runner" unless $runner; + +print "##########################\n"; +print "Testing $#files spec files\n"; +print "##########################\n"; + +foreach my $file (shuffle @files) { + $runner->run(sub { + $| = 1; select STDOUT; + my $cmd = sprintf('../sassc/bin/sassc %s', $file); + my $check = sprintf('valgrind --leak-check=yes %s', $cmd); + run3($check, undef, \ my $out, \ my $err); + if ($err =~ m/in use at exit: 0 bytes in 0 blocks/) { + print "."; # print success indicator + } else { + print "F"; # print error indicator + flock($leaks, LOCK_EX) or die "Cannot lock log"; + $leaks->printflush("#" x 80, "\n", $err, "\n"); + flock($leaks, LOCK_UN) or die "Cannot unlock log"; + } + }); +} + +$runner->finish; diff --git a/src/GNUmakefile.am b/src/GNUmakefile.am new file mode 100644 index 000000000..fee9312f9 --- /dev/null +++ b/src/GNUmakefile.am @@ -0,0 +1,54 @@ +ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS} -I m4 -I script + +AM_COPT = -Wall -O2 +AM_COVLDFLAGS = + +if ENABLE_COVERAGE + AM_COPT = -O0 --coverage + AM_COVLDFLAGS += -lgcov +endif + +AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CFLAGS = $(AM_COPT) +AM_CXXFLAGS = $(AM_COPT) +AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) + +if COMPILER_IS_MINGW32 + AM_CXXFLAGS += -std=gnu++0x +else + AM_CXXFLAGS += -std=c++0x +endif + +EXTRA_DIST = \ + COPYING \ + INSTALL \ + LICENSE \ + Readme.md + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = support/libsass.pc + +lib_LTLIBRARIES = libsass.la + +include $(top_srcdir)/Makefile.conf + +libsass_la_SOURCES = ${CSOURCES} ${SOURCES} + +libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 1:0:0 + +if ENABLE_TESTS +if ENABLE_COVERAGE +nodist_EXTRA_libsass_la_SOURCES = non-existent-file-to-force-CXX-linking.cxx +endif +endif + +include_HEADERS = $(top_srcdir)/include/sass.h \ + $(top_srcdir)/include/sass2scss.h + +sass_includedir = $(includedir)/sass + +sass_include_HEADERS = $(top_srcdir)/include/sass/base.h \ + $(top_srcdir)/include/sass/values.h \ + $(top_srcdir)/include/sass/version.h \ + $(top_srcdir)/include/sass/context.h \ + $(top_srcdir)/include/sass/functions.h diff --git a/src/ast.cpp b/src/ast.cpp new file mode 100644 index 000000000..c3b38efb9 --- /dev/null +++ b/src/ast.cpp @@ -0,0 +1,2226 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "node.hpp" +#include "eval.hpp" +#include "extend.hpp" +#include "emitter.hpp" +#include "color_maps.hpp" +#include "ast_fwd_decl.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace Sass { + + static Null sass_null(ParserState("null")); + + bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + if (selector_) { + if (selector_->find(f)) return true; + } + // execute last + return f(this); + } + + bool Selector_List::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + for (Complex_Selector_Obj sel : elements()) { + if (sel->find(f)) return true; + } + // execute last + return f(this); + } + + bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + for (Simple_Selector_Obj sel : elements()) { + if (sel->find(f)) return true; + } + // execute last + return f(this); + } + + bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) ) + { + // check children first + if (head_ && head_->find(f)) return true; + if (tail_ && tail_->find(f)) return true; + // execute last + return f(this); + } + + bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { + if (Supports_Operator_Obj op = Cast(cond)) { + return op->operand() != operand(); + } + return Cast(cond) != NULL; + } + + bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const { + return Cast(cond) || + Cast(cond); + } + + void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v") + { + str.erase( str.find_last_not_of( delimiters ) + 1 ); + } + + void String_Constant::rtrim() + { + str_rtrim(value_); + } + + void String_Schema::rtrim() + { + if (!empty()) { + if (String_Ptr str = Cast(last())) str->rtrim(); + } + } + + void Argument::set_delayed(bool delayed) + { + if (value_) value_->set_delayed(delayed); + is_delayed(delayed); + } + + void Arguments::set_delayed(bool delayed) + { + for (Argument_Obj arg : elements()) { + if (arg) arg->set_delayed(delayed); + } + is_delayed(delayed); + } + + + bool At_Root_Query::exclude(std::string str) + { + bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; + List_Ptr l = static_cast(value().ptr()); + std::string v; + + if (with) + { + if (!l || l->length() == 0) return str.compare("rule") != 0; + for (size_t i = 0, L = l->length(); i < L; ++i) + { + v = unquote((*l)[i]->to_string()); + if (v.compare("all") == 0 || v == str) return false; + } + return true; + } + else + { + if (!l || !l->length()) return str.compare("rule") == 0; + for (size_t i = 0, L = l->length(); i < L; ++i) + { + v = unquote((*l)[i]->to_string()); + if (v.compare("all") == 0 || v == str) return true; + } + return false; + } + } + + void AST_Node::update_pstate(const ParserState& pstate) + { + pstate_.offset += pstate - pstate_ + pstate.offset; + } + + bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const + { + // https://github.com/sass/sass/issues/2229 + if ((has_ns_ == r.has_ns_) || + (has_ns_ && ns_.empty()) || + (r.has_ns_ && r.ns_.empty()) + ) { + if (ns_.empty() && r.ns() == "*") return false; + else if (r.ns().empty() && ns() == "*") return false; + else return ns() == r.ns(); + } + return false; + } + + bool Compound_Selector::operator< (const Compound_Selector& rhs) const + { + size_t L = std::min(length(), rhs.length()); + for (size_t i = 0; i < L; ++i) + { + Simple_Selector_Obj l = (*this)[i]; + Simple_Selector_Obj r = rhs[i]; + if (!l && !r) return false; + else if (!r) return false; + else if (!l) return true; + else if (*l != *r) + { return *l < *r; } + } + // just compare the length now + return length() < rhs.length(); + } + + bool Compound_Selector::has_parent_ref() const + { + for (Simple_Selector_Obj s : *this) { + if (s && s->has_parent_ref()) return true; + } + return false; + } + + bool Compound_Selector::has_real_parent_ref() const + { + for (Simple_Selector_Obj s : *this) { + if (s && s->has_real_parent_ref()) return true; + } + return false; + } + + bool Complex_Selector::has_parent_ref() const + { + return (head() && head()->has_parent_ref()) || + (tail() && tail()->has_parent_ref()); + } + + bool Complex_Selector::has_real_parent_ref() const + { + return (head() && head()->has_real_parent_ref()) || + (tail() && tail()->has_real_parent_ref()); + } + + bool Complex_Selector::operator< (const Complex_Selector& rhs) const + { + // const iterators for tails + Complex_Selector_Ptr_Const l = this; + Complex_Selector_Ptr_Const r = &rhs; + Compound_Selector_Ptr l_h = NULL; + Compound_Selector_Ptr r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); + // process all tails + while (true) + { + #ifdef DEBUG + // skip empty ancestor first + if (l && l->is_empty_ancestor()) + { + l_h = NULL; + l = l->tail(); + if(l) l_h = l->head(); + continue; + } + // skip empty ancestor first + if (r && r->is_empty_ancestor()) + { + r_h = NULL; + r = r->tail(); + if (r) r_h = r->head(); + continue; + } + #endif + // check for valid selectors + if (!l) return !!r; + if (!r) return false; + // both are null + else if (!l_h && !r_h) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() < r->combinator(); } + // advance to next tails + l = l->tail(); + r = r->tail(); + // fetch the next headers + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); + } + // one side is null + else if (!r_h) return true; + else if (!l_h) return false; + // heads ok and equal + else if (*l_h == *r_h) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() < r->combinator(); } + // advance to next tails + l = l->tail(); + r = r->tail(); + // fetch the next headers + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); + } + // heads are not equal + else return *l_h < *r_h; + } + } + + bool Complex_Selector::operator== (const Complex_Selector& rhs) const + { + // const iterators for tails + Complex_Selector_Ptr_Const l = this; + Complex_Selector_Ptr_Const r = &rhs; + Compound_Selector_Ptr l_h = NULL; + Compound_Selector_Ptr r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); + // process all tails + while (true) + { + #ifdef DEBUG + // skip empty ancestor first + if (l && l->is_empty_ancestor()) + { + l_h = NULL; + l = l->tail(); + if (l) l_h = l->head(); + continue; + } + // skip empty ancestor first + if (r && r->is_empty_ancestor()) + { + r_h = NULL; + r = r->tail(); + if (r) r_h = r->head(); + continue; + } + #endif + // check the pointers + if (!r) return !l; + if (!l) return !r; + // both are null + if (!l_h && !r_h) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() < r->combinator(); } + // advance to next tails + l = l->tail(); + r = r->tail(); + // fetch the next heads + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); + } + // equals if other head is empty + else if ((!l_h && !r_h) || + (!l_h && r_h->empty()) || + (!r_h && l_h->empty()) || + (l_h && r_h && *l_h == *r_h)) + { + // check combinator after heads + if (l->combinator() != r->combinator()) + { return l->combinator() == r->combinator(); } + // advance to next tails + l = l->tail(); + r = r->tail(); + // fetch the next heads + l_h = NULL; r_h = NULL; + if (l) l_h = l->head(); + if (r) r_h = r->head(); + } + // abort + else break; + } + // unreachable + return false; + } + + Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs) + { + if (empty()) return rhs; + Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); + for (size_t i = 0, L = length(); i < L; ++i) + { + if (unified.isNull()) break; + unified = at(i)->unify_with(unified); + } + return unified.detach(); + } + + bool Complex_Selector::operator== (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; + throw std::runtime_error("invalid selector base classes to compare"); + } + + + bool Complex_Selector::operator< (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; + throw std::runtime_error("invalid selector base classes to compare"); + } + + bool Compound_Selector::operator== (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; + throw std::runtime_error("invalid selector base classes to compare"); + } + + bool Compound_Selector::operator< (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; + throw std::runtime_error("invalid selector base classes to compare"); + } + + bool Selector_Schema::operator== (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; + throw std::runtime_error("invalid selector base classes to compare"); + } + + bool Selector_Schema::operator< (const Selector& rhs) const + { + if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; + if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; + if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; + if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; + throw std::runtime_error("invalid selector base classes to compare"); + } + + bool Simple_Selector::operator== (const Selector& rhs) const + { + if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this == *sp; + return false; + } + + bool Simple_Selector::operator< (const Selector& rhs) const + { + if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this < *sp; + return false; + } + + bool Simple_Selector::operator== (const Simple_Selector& rhs) const + { + // solve the double dispatch problem by using RTTI information via dynamic cast + if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (const Element_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs == rhs; } + else if (name_ == rhs.name_) + { return is_ns_eq(rhs); } + else return false; + } + + bool Simple_Selector::operator< (const Simple_Selector& rhs) const + { + // solve the double dispatch problem by using RTTI information via dynamic cast + if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs < rhs; } + else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs < rhs; } + else if (const Element_Selector* lhs = Cast(this)) {return *lhs < rhs; } + else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs < rhs; } + if (is_ns_eq(rhs)) + { return name_ < rhs.name_; } + return ns_ < rhs.ns_; + } + + bool Selector_List::operator== (const Selector& rhs) const + { + // solve the double dispatch problem by using RTTI information via dynamic cast + if (Selector_List_Ptr_Const sl = Cast(&rhs)) { return *this == *sl; } + else if (Complex_Selector_Ptr_Const cpx = Cast(&rhs)) { return *this == *cpx; } + else if (Compound_Selector_Ptr_Const cpd = Cast(&rhs)) { return *this == *cpd; } + // no compare method + return this == &rhs; + } + + // Selector lists can be compared to comma lists + bool Selector_List::operator== (const Expression& rhs) const + { + // solve the double dispatch problem by using RTTI information via dynamic cast + if (List_Ptr_Const ls = Cast(&rhs)) { return *ls == *this; } + if (Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } + // compare invalid (maybe we should error?) + return false; + } + + bool Selector_List::operator== (const Selector_List& rhs) const + { + // for array access + size_t i = 0, n = 0; + size_t iL = length(); + size_t nL = rhs.length(); + // create temporary vectors and sort them + std::vector l_lst = this->elements(); + std::vector r_lst = rhs.elements(); + std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); + std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); + // process loop + while (true) + { + // first check for valid index + if (i == iL) return iL == nL; + else if (n == nL) return iL == nL; + // the access the vector items + Complex_Selector_Obj l = l_lst[i]; + Complex_Selector_Obj r = r_lst[n]; + // skip nulls + if (!l) ++i; + else if (!r) ++n; + // do the check + else if (*l != *r) + { return false; } + // advance + ++i; ++n; + } + // there is no break?! + } + + bool Selector_List::operator< (const Selector& rhs) const + { + if (Selector_List_Ptr_Const sp = Cast(&rhs)) return *this < *sp; + return false; + } + + bool Selector_List::operator< (const Selector_List& rhs) const + { + size_t l = rhs.length(); + if (length() < l) l = length(); + for (size_t i = 0; i < l; i ++) { + if (*at(i) < *rhs.at(i)) return true; + } + return false; + } + + Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs) + { + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { if (to_string() == rhs->at(i)->to_string()) return rhs; } + + // check for pseudo elements because they are always last + size_t i, L; + bool found = false; + if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector) || typeid(*this) == typeid(Attribute_Selector)) + { + for (i = 0, L = rhs->length(); i < L; ++i) + { + if ((Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) + { found = true; break; } + } + } + else + { + for (i = 0, L = rhs->length(); i < L; ++i) + { + if (Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) + { found = true; break; } + } + } + if (!found) + { + rhs->append(this); + } else { + rhs->elements().insert(rhs->elements().begin() + i, this); + } + return rhs; + } + + Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs) + { + // check if ns can be extended + // true for no ns or universal + if (has_universal_ns()) + { + // but dont extend with universal + // true for valid ns and universal + if (!rhs->is_universal_ns()) + { + // overwrite the name if star is given as name + if (this->name() == "*") { this->name(rhs->name()); } + // now overwrite the namespace name and flag + this->ns(rhs->ns()); this->has_ns(rhs->has_ns()); + // return copy + return this; + } + } + // namespace may changed, check the name now + // overwrite star (but not with another star) + if (name() == "*" && rhs->name() != "*") + { + // simply set the new name + this->name(rhs->name()); + // return copy + return this; + } + // return original + return this; + } + + Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs) + { + // TODO: handle namespaces + + // if the rhs is empty, just return a copy of this + if (rhs->length() == 0) { + rhs->append(this); + return rhs; + } + + Simple_Selector_Ptr rhs_0 = rhs->at(0); + // otherwise, this is a tag name + if (name() == "*") + { + if (typeid(*rhs_0) == typeid(Element_Selector)) + { + // if rhs is universal, just return this tagname + rhs's qualifiers + Element_Selector_Ptr ts = Cast(rhs_0); + rhs->at(0) = this->unify_with(ts); + return rhs; + } + else if (Cast(rhs_0) || Cast(rhs_0)) { + // qualifier is `.class`, so we can prefix with `ns|*.class` + if (has_ns() && !rhs_0->has_ns()) { + if (ns() != "*") rhs->elements().insert(rhs->begin(), this); + } + return rhs; + } + + + return rhs; + } + + if (typeid(*rhs_0) == typeid(Element_Selector)) + { + // if rhs is universal, just return this tagname + rhs's qualifiers + if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; + // otherwise create new compound and unify first simple selector + rhs->at(0) = this->unify_with(rhs_0); + return rhs; + + } + // else it's a tag name and a bunch of qualifiers -- just append them + if (name() != "*") rhs->elements().insert(rhs->begin(), this); + return rhs; + } + + Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs) + { + rhs->has_line_break(has_line_break()); + return Simple_Selector::unify_with(rhs); + } + + Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs) + { + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { + if (Id_Selector_Ptr sel = Cast(rhs->at(i))) { + if (sel->name() != name()) return 0; + } + } + rhs->has_line_break(has_line_break()); + return Simple_Selector::unify_with(rhs); + } + + Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs) + { + if (is_pseudo_element()) + { + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { + if (Pseudo_Selector_Ptr sel = Cast(rhs->at(i))) { + if (sel->is_pseudo_element() && sel->name() != name()) return 0; + } + } + } + return Simple_Selector::unify_with(rhs); + } + + bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const + { + if (is_ns_eq(rhs)) { + if (name() == rhs.name()) { + if (matcher() == rhs.matcher()) { + bool no_lhs_val = value().isNull(); + bool no_rhs_val = rhs.value().isNull(); + if (no_lhs_val && no_rhs_val) return false; // equal + else if (no_lhs_val) return true; // lhs is null + else if (no_rhs_val) return false; // rhs is null + return *value() < *rhs.value(); // both are given + } else { return matcher() < rhs.matcher(); } + } else { return name() < rhs.name(); } + } else { return ns() < rhs.ns(); } + } + + bool Attribute_Selector::operator< (const Simple_Selector& rhs) const + { + if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const + { + // get optional value state + bool no_lhs_val = value().isNull(); + bool no_rhs_val = rhs.value().isNull(); + // both are null, therefore equal + if (no_lhs_val && no_rhs_val) { + return (name() == rhs.name()) + && (matcher() == rhs.matcher()) + && (is_ns_eq(rhs)); + } + // both are defined, evaluate + if (no_lhs_val == no_rhs_val) { + return (name() == rhs.name()) + && (matcher() == rhs.matcher()) + && (is_ns_eq(rhs)) + && (*value() == *rhs.value()); + } + // not equal + return false; + + } + + bool Attribute_Selector::operator== (const Simple_Selector& rhs) const + { + if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) + { + return is_ns_eq(rhs) && + name() == rhs.name() && + *this == *w; + } + return false; + } + + bool Element_Selector::operator< (const Element_Selector& rhs) const + { + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Element_Selector::operator< (const Simple_Selector& rhs) const + { + if (Element_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Element_Selector::operator== (const Element_Selector& rhs) const + { + return is_ns_eq(rhs) && + name() == rhs.name(); + } + + bool Element_Selector::operator== (const Simple_Selector& rhs) const + { + if (Element_Selector_Ptr_Const w = Cast(&rhs)) + { + return is_ns_eq(rhs) && + name() == rhs.name() && + *this == *w; + } + return false; + } + + bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const + { + if (is_ns_eq(rhs) && name() == rhs.name()) + { + String_Obj lhs_ex = expression(); + String_Obj rhs_ex = rhs.expression(); + if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; + else return lhs_ex.ptr() == rhs_ex.ptr(); + } + else return false; + } + + bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const + { + if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this == *w; + } + return is_ns_eq(rhs) && + name() == rhs.name(); + } + + bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const + { + if (is_ns_eq(rhs) && name() == rhs.name()) + { + String_Obj lhs_ex = expression(); + String_Obj rhs_ex = rhs.expression(); + if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; + else return lhs_ex.ptr() < rhs_ex.ptr(); + } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const + { + if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const + { + if (is_ns_eq(rhs) && name() == rhs.name()) + { return *(selector()) == *(rhs.selector()); } + else return false; + } + + bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const + { + if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this == *w; + } + return is_ns_eq(rhs) && + name() == rhs.name(); + } + + bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const + { + if (is_ns_eq(rhs) && name() == rhs.name()) + { return *(selector()) < *(rhs.selector()); } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const + { + if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) + { + return *this < *w; + } + if (is_ns_eq(rhs)) + { return name() < rhs.name(); } + return ns() < rhs.ns(); + } + + bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub) + { + if (this->name() != sub->name()) return false; + if (this->name() == ":current") return false; + if (Selector_List_Obj rhs_list = Cast(sub->selector())) { + if (Selector_List_Obj lhs_list = Cast(selector())) { + return lhs_list->is_superselector_of(rhs_list); + } + } + coreError("is_superselector expected a Selector_List", sub->pstate()); + return false; + } + + bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped) + { + for (Complex_Selector_Obj item : rhs->elements()) { + if (is_superselector_of(item, wrapped)) return true; + } + return false; + } + + bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped) + { + if (rhs->head()) return is_superselector_of(rhs->head(), wrapped); + return false; + } + + bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) + { + Compound_Selector_Ptr lhs = this; + Simple_Selector_Ptr lbase = lhs->base(); + Simple_Selector_Ptr rbase = rhs->base(); + + // Check if pseudo-elements are the same between the selectors + + std::set lpsuedoset, rpsuedoset; + for (size_t i = 0, L = length(); i < L; ++i) + { + if ((*this)[i]->is_pseudo_element()) { + std::string pseudo((*this)[i]->to_string()); + pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving + lpsuedoset.insert(pseudo); + } + } + for (size_t i = 0, L = rhs->length(); i < L; ++i) + { + if ((*rhs)[i]->is_pseudo_element()) { + std::string pseudo((*rhs)[i]->to_string()); + pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving + rpsuedoset.insert(pseudo); + } + } + if (lpsuedoset != rpsuedoset) { + return false; + } + + // would like to replace this without stringification + // https://github.com/sass/sass/issues/2229 + // SimpleSelectorSet lset, rset; + std::set lset, rset; + + if (lbase && rbase) + { + if (lbase->to_string() == rbase->to_string()) { + for (size_t i = 1, L = length(); i < L; ++i) + { lset.insert((*this)[i]->to_string()); } + for (size_t i = 1, L = rhs->length(); i < L; ++i) + { rset.insert((*rhs)[i]->to_string()); } + return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); + } + return false; + } + + for (size_t i = 0, iL = length(); i < iL; ++i) + { + Selector_Obj wlhs = (*this)[i]; + // very special case for wrapped matches selector + if (Wrapped_Selector_Obj wrapped = Cast(wlhs)) { + if (wrapped->name() == ":not") { + if (Selector_List_Obj not_list = Cast(wrapped->selector())) { + if (not_list->is_superselector_of(rhs, wrapped->name())) return false; + } else { + throw std::runtime_error("wrapped not selector is not a list"); + } + } + if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { + wlhs = wrapped->selector(); + if (Selector_List_Obj list = Cast(wrapped->selector())) { + if (Compound_Selector_Obj comp = Cast(rhs)) { + if (!wrapping.empty() && wrapping != wrapped->name()) return false; + if (wrapping.empty() || wrapping != wrapped->name()) {; + if (list->is_superselector_of(comp, wrapped->name())) return true; + } + } + } + } + Simple_Selector_Ptr rhs_sel = NULL; + if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; + if (Wrapped_Selector_Ptr wrapped_r = Cast(rhs_sel)) { + if (wrapped->name() == wrapped_r->name()) { + if (wrapped->is_superselector_of(wrapped_r)) { + continue; + }} + } + } + // match from here on as strings + lset.insert(wlhs->to_string()); + } + + for (size_t n = 0, nL = rhs->length(); n < nL; ++n) + { + Selector_Obj r = (*rhs)[n]; + if (Wrapped_Selector_Obj wrapped = Cast(r)) { + if (wrapped->name() == ":not") { + if (Selector_List_Obj ls = Cast(wrapped->selector())) { + ls->remove_parent_selectors(); + if (is_superselector_of(ls, wrapped->name())) return false; + } + } + if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { + if (!wrapping.empty()) { + if (wrapping != wrapped->name()) return false; + } + if (Selector_List_Obj ls = Cast(wrapped->selector())) { + ls->remove_parent_selectors(); + return (is_superselector_of(ls, wrapped->name())); + } + } + } + rset.insert(r->to_string()); + } + + //for (auto l : lset) { cerr << "l: " << l << endl; } + //for (auto r : rset) { cerr << "r: " << r << endl; } + + if (lset.empty()) return true; + // return true if rset contains all the elements of lset + return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); + + } + + // create complex selector (ancestor of) from compound selector + Complex_Selector_Obj Compound_Selector::to_complex() + { + // create an intermediate complex selector + return SASS_MEMORY_NEW(Complex_Selector, + pstate(), + Complex_Selector::ANCESTOR_OF, + this, + 0); + } + + Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other) + { + + // get last tails (on the right side) + Complex_Selector_Obj l_last = this->last(); + Complex_Selector_Obj r_last = other->last(); + + // check valid pointers (assertion) + SASS_ASSERT(l_last, "lhs is null"); + SASS_ASSERT(r_last, "rhs is null"); + + // Not sure about this check, but closest way I could check + // was to see if this is a ruby 'SimpleSequence' equivalent. + // It seems to do the job correctly as some specs react to this + if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0; + if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0; + + // get the headers for the last tails + Compound_Selector_Obj l_last_head = l_last->head(); + Compound_Selector_Obj r_last_head = r_last->head(); + + // check valid head pointers (assertion) + SASS_ASSERT(l_last_head, "lhs head is null"); + SASS_ASSERT(r_last_head, "rhs head is null"); + + // get the unification of the last compound selectors + Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); + + // abort if we could not unify heads + if (unified == 0) return 0; + + // check for universal (star: `*`) selector + bool is_universal = l_last_head->is_universal() || + r_last_head->is_universal(); + + if (is_universal) + { + // move the head + l_last->head(0); + r_last->head(unified); + } + + // create nodes from both selectors + Node lhsNode = complexSelectorToNode(this); + Node rhsNode = complexSelectorToNode(other); + + // overwrite universal base + if (!is_universal) + { + // create some temporaries to convert to node + Complex_Selector_Obj fake = unified->to_complex(); + Node unified_node = complexSelectorToNode(fake); + // add to permutate the list? + rhsNode.plus(unified_node); + } + + // do some magic we inherit from node and extend + Node node = subweave(lhsNode, rhsNode); + Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate()); + NodeDequePtr col = node.collection(); // move from collection to list + for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) + { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); } + + // only return if list has some entries + return result->length() ? result.detach() : 0; + + } + + bool Compound_Selector::operator== (const Compound_Selector& rhs) const + { + // for array access + size_t i = 0, n = 0; + size_t iL = length(); + size_t nL = rhs.length(); + // create temporary vectors and sort them + std::vector l_lst = this->elements(); + std::vector r_lst = rhs.elements(); + std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); + std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); + // process loop + while (true) + { + // first check for valid index + if (i == iL) return iL == nL; + else if (n == nL) return iL == nL; + // the access the vector items + Simple_Selector_Obj l = l_lst[i]; + Simple_Selector_Obj r = r_lst[n]; + // skip nulls + if (!l) ++i; + if (!r) ++n; + // do the check now + else if (*l != *r) + { return false; } + // advance now + ++i; ++n; + } + // there is no break?! + } + + bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) + { + return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); + } + + bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping) + { + Complex_Selector_Ptr lhs = this; + // check for selectors with leading or trailing combinators + if (!lhs->head() || !rhs->head()) + { return false; } + Complex_Selector_Obj l_innermost = lhs->innermost(); + if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF) + { return false; } + Complex_Selector_Obj r_innermost = rhs->innermost(); + if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF) + { return false; } + // more complex (i.e., longer) selectors are always more specific + size_t l_len = lhs->length(), r_len = rhs->length(); + if (l_len > r_len) + { return false; } + + if (l_len == 1) + { return lhs->head()->is_superselector_of(rhs->last()->head(), wrapping); } + + // we have to look one tail deeper, since we cary the + // combinator around for it (which is important here) + if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { + Complex_Selector_Obj lhs_tail = lhs->tail(); + Complex_Selector_Obj rhs_tail = rhs->tail(); + if (lhs_tail->combinator() != rhs_tail->combinator()) return false; + if (lhs_tail->head() && !rhs_tail->head()) return false; + if (!lhs_tail->head() && rhs_tail->head()) return false; + if (lhs_tail->head() && rhs_tail->head()) { + if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; + } + } + + bool found = false; + Complex_Selector_Obj marker = rhs; + for (size_t i = 0, L = rhs->length(); i < L; ++i) { + if (i == L-1) + { return false; } + if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping)) + { found = true; break; } + marker = marker->tail(); + } + if (!found) + { return false; } + + /* + Hmm, I hope I have the logic right: + + if lhs has a combinator: + if !(marker has a combinator) return false + if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false + return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost) + else if marker has a combinator: + if !(marker.combinator == ">") return false + return lhs.tail.is_superselector_of(marker.tail) + else + return lhs.tail.is_superselector_of(marker.tail) + */ + if (lhs->combinator() != Complex_Selector::ANCESTOR_OF) + { + if (marker->combinator() == Complex_Selector::ANCESTOR_OF) + { return false; } + if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) + { return false; } + return lhs->tail()->is_superselector_of(marker->tail()); + } + else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) + { + if (marker->combinator() != Complex_Selector::PARENT_OF) + { return false; } + return lhs->tail()->is_superselector_of(marker->tail()); + } + return lhs->tail()->is_superselector_of(marker->tail()); + } + + size_t Complex_Selector::length() const + { + // TODO: make this iterative + if (!tail()) return 1; + return 1 + tail()->length(); + } + + // append another complex selector at the end + // check if we need to append some headers + // then we need to check for the combinator + // only then we can safely set the new tail + void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces) + { + + Complex_Selector_Obj t = ss->tail(); + Combinator c = ss->combinator(); + String_Obj r = ss->reference(); + Compound_Selector_Obj h = ss->head(); + + if (ss->has_line_feed()) has_line_feed(true); + if (ss->has_line_break()) has_line_break(true); + + // append old headers + if (h && h->length()) { + if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { + traces.push_back(Backtrace(pstate())); + throw Exception::InvalidParent(this, traces, ss); + } else if (last()->head_ && last()->head_->length()) { + Compound_Selector_Obj rh = last()->head(); + size_t i; + size_t L = h->length(); + if (Cast(h->first())) { + if (Class_Selector_Ptr cs = Cast(rh->last())) { + Class_Selector_Ptr sqs = SASS_MEMORY_COPY(cs); + sqs->name(sqs->name() + (*h)[0]->name()); + sqs->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = sqs; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else if (Id_Selector_Ptr is = Cast(rh->last())) { + Id_Selector_Ptr sqs = SASS_MEMORY_COPY(is); + sqs->name(sqs->name() + (*h)[0]->name()); + sqs->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = sqs; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else if (Element_Selector_Ptr ts = Cast(rh->last())) { + Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts); + tss->name(tss->name() + (*h)[0]->name()); + tss->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = tss; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else if (Placeholder_Selector_Ptr ps = Cast(rh->last())) { + Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps); + pss->name(pss->name() + (*h)[0]->name()); + pss->pstate((*h)[0]->pstate()); + (*rh)[rh->length()-1] = pss; + rh->pstate(h->pstate()); + for (i = 1; i < L; ++i) rh->append((*h)[i]); + } else { + last()->head_->concat(h); + } + } else { + last()->head_->concat(h); + } + } else if (last()->head_) { + last()->head_->concat(h); + } + } else { + // std::cerr << "has no or empty head\n"; + } + + if (last()) { + if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { + Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate()); + inter->reference(r); + inter->combinator(c); + inter->tail(t); + last()->tail(inter); + } else { + if (last()->combinator() == ANCESTOR_OF) { + last()->combinator(c); + last()->reference(r); + } + last()->tail(t); + } + } + + } + + Selector_List_Obj Selector_List::eval(Eval& eval) + { + Selector_List_Obj list = schema() ? + eval(schema()) : eval(this); + list->schema(schema()); + return list; + } + + Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) + { + if (!this->has_parent_ref()) return this; + Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); + Selector_List_Ptr ps = pstack.back(); + for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { + for (size_t si = 0, sL = this->length(); si < sL; ++si) { + Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent); + ss->concat(rv); + } + } + return ss; + } + + Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) + { + Complex_Selector_Obj tail = this->tail(); + Compound_Selector_Obj head = this->head(); + Selector_List_Ptr parents = pstack.back(); + + if (!this->has_real_parent_ref() && !implicit_parent) { + Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate()); + retval->append(this); + return retval; + } + + // first resolve_parent_refs the tail (which may return an expanded list) + Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0; + + if (head && head->length() > 0) { + + Selector_List_Obj retval; + // we have a parent selector in a simple compound list + // mix parent complex selector into the compound list + if (Cast((*head)[0])) { + retval = SASS_MEMORY_NEW(Selector_List, pstate()); + + // it turns out that real parent references reach + // across @at-root rules, which comes unexpected + if (parents == NULL && head->has_real_parent_ref()) { + int i = pstack.size() - 1; + while (!parents && i > -1) { + parents = pstack.at(i--); + } + } + + if (parents && parents->length()) { + if (tails && tails->length() > 0) { + for (size_t n = 0, nL = tails->length(); n < nL; ++n) { + for (size_t i = 0, iL = parents->length(); i < iL; ++i) { + Complex_Selector_Obj t = (*tails)[n]; + Complex_Selector_Obj parent = (*parents)[i]; + Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); + Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); + ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL); + Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); + // remove parent selector from sequence + if (h->length()) { + h->erase(h->begin()); + ss->head(h); + } else { + ss->head(NULL); + } + // adjust for parent selector (1 char) + // if (h->length()) { + // ParserState state(h->at(0)->pstate()); + // state.offset.column += 1; + // state.column -= 1; + // (*h)[0]->pstate(state); + // } + // keep old parser state + s->pstate(pstate()); + // append new tail + s->append(ss, traces); + retval->append(s); + } + } + } + // have no tails but parents + // loop above is inside out + else { + for (size_t i = 0, iL = parents->length(); i < iL; ++i) { + Complex_Selector_Obj parent = (*parents)[i]; + Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); + Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); + // this is only if valid if the parent has no trailing op + // otherwise we cannot append more simple selectors to head + if (parent->last()->combinator() != ANCESTOR_OF) { + traces.push_back(Backtrace(pstate())); + throw Exception::InvalidParent(parent, traces, ss); + } + ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); + Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); + // remove parent selector from sequence + if (h->length()) { + h->erase(h->begin()); + ss->head(h); + } else { + ss->head(NULL); + } + // \/ IMO ruby sass bug \/ + ss->has_line_feed(false); + // adjust for parent selector (1 char) + // if (h->length()) { + // ParserState state(h->at(0)->pstate()); + // state.offset.column += 1; + // state.column -= 1; + // (*h)[0]->pstate(state); + // } + // keep old parser state + s->pstate(pstate()); + // append new tail + s->append(ss, traces); + retval->append(s); + } + } + } + // have no parent but some tails + else { + if (tails && tails->length() > 0) { + for (size_t n = 0, nL = tails->length(); n < nL; ++n) { + Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); + cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); + cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); + for (size_t i = 1, L = this->head()->length(); i < L; ++i) + cpy->head()->append((*this->head())[i]); + if (!cpy->head()->length()) cpy->head(0); + retval->append(cpy->skip_empty_reference()); + } + } + // have no parent nor tails + else { + Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); + cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); + for (size_t i = 1, L = this->head()->length(); i < L; ++i) + cpy->head()->append((*this->head())[i]); + if (!cpy->head()->length()) cpy->head(0); + retval->append(cpy->skip_empty_reference()); + } + } + } + // no parent selector in head + else { + retval = this->tails(tails); + } + + for (Simple_Selector_Obj ss : head->elements()) { + if (Wrapped_Selector_Ptr ws = Cast(ss)) { + if (Selector_List_Ptr sl = Cast(ws->selector())) { + if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent)); + } + } + } + + return retval.detach(); + + } + // has no head + return this->tails(tails); + } + + Selector_List_Ptr Complex_Selector::tails(Selector_List_Ptr tails) + { + Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_); + if (tails && tails->length()) { + for (size_t i = 0, iL = tails->length(); i < iL; ++i) { + Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this); + pr->tail(tails->at(i)); + rv->append(pr); + } + } + else { + rv->append(this); + } + return rv; + } + + // return the last tail that is defined + Complex_Selector_Obj Complex_Selector::first() + { + // declare variables used in loop + Complex_Selector_Obj cur = this; + Compound_Selector_Obj head; + // processing loop + while (cur) + { + // get the head + head = cur->head_; + // abort (and return) if it is not a parent selector + if (!head || head->length() != 1 || !Cast((*head)[0])) { + break; + } + // advance to next + cur = cur->tail_; + } + // result + return cur; + } + + // return the last tail that is defined + Complex_Selector_Obj Complex_Selector::last() + { + Complex_Selector_Ptr cur = this; + Complex_Selector_Ptr nxt = cur; + // loop until last + while (nxt) { + cur = nxt; + nxt = cur->tail(); + } + return cur; + } + + Complex_Selector::Combinator Complex_Selector::clear_innermost() + { + Combinator c; + if (!tail() || tail()->tail() == 0) + { c = combinator(); combinator(ANCESTOR_OF); tail(0); } + else + { c = tail()->clear_innermost(); } + return c; + } + + void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c) + { + if (!tail()) + { tail(val); combinator(c); } + else + { tail()->set_innermost(val, c); } + } + + void Complex_Selector::cloneChildren() + { + if (head()) head(SASS_MEMORY_CLONE(head())); + if (tail()) tail(SASS_MEMORY_CLONE(tail())); + } + + void Compound_Selector::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + void Selector_List::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + void Wrapped_Selector::cloneChildren() + { + selector(SASS_MEMORY_CLONE(selector())); + } + + // remove parent selector references + // basically unwraps parsed selectors + void Selector_List::remove_parent_selectors() + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = length(); i < L; ++i) { + if (!(*this)[i]->head()) continue; + if ((*this)[i]->head()->is_empty_reference()) { + // simply move to the next tail if we have "no" combinator + if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) { + if ((*this)[i]->tail()) { + if ((*this)[i]->has_line_feed()) { + (*this)[i]->tail()->has_line_feed(true); + } + (*this)[i] = (*this)[i]->tail(); + } + } + // otherwise remove the first item from head + else { + (*this)[i]->head()->erase((*this)[i]->head()->begin()); + } + } + } + } + + size_t Wrapped_Selector::hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + if (selector_) hash_combine(hash_, selector_->hash()); + } + return hash_; + } + bool Wrapped_Selector::has_parent_ref() const { + // if (has_reference()) return true; + if (!selector()) return false; + return selector()->has_parent_ref(); + } + bool Wrapped_Selector::has_real_parent_ref() const { + // if (has_reference()) return true; + if (!selector()) return false; + return selector()->has_real_parent_ref(); + } + unsigned long Wrapped_Selector::specificity() const + { + return selector_ ? selector_->specificity() : 0; + } + + + bool Selector_List::has_parent_ref() const + { + for (Complex_Selector_Obj s : elements()) { + if (s && s->has_parent_ref()) return true; + } + return false; + } + + bool Selector_List::has_real_parent_ref() const + { + for (Complex_Selector_Obj s : elements()) { + if (s && s->has_real_parent_ref()) return true; + } + return false; + } + + bool Selector_Schema::has_parent_ref() const + { + if (String_Schema_Obj schema = Cast(contents())) { + return schema->length() > 0 && Cast(schema->at(0)) != NULL; + } + return false; + } + + bool Selector_Schema::has_real_parent_ref() const + { + if (String_Schema_Obj schema = Cast(contents())) { + Parent_Selector_Obj p = Cast(schema->at(0)); + return schema->length() > 0 && p && p->is_real_parent_ref(); + } + return false; + } + + void Selector_List::adjust_after_pushing(Complex_Selector_Obj c) + { + // if (c->has_reference()) has_reference(true); + } + + // it's a superselector if every selector of the right side + // list is a superselector of the given left side selector + bool Complex_Selector::is_superselector_of(Selector_List_Obj sub, std::string wrapping) + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = sub->length(); i < L; ++i) { + if (!is_superselector_of((*sub)[i], wrapping)) return false; + } + return true; + } + + // it's a superselector if every selector of the right side + // list is a superselector of the given left side selector + bool Selector_List::is_superselector_of(Selector_List_Obj sub, std::string wrapping) + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = sub->length(); i < L; ++i) { + if (!is_superselector_of((*sub)[i], wrapping)) return false; + } + return true; + } + + // it's a superselector if every selector on the right side + // is a superselector of any one of the left side selectors + bool Selector_List::is_superselector_of(Compound_Selector_Obj sub, std::string wrapping) + { + // Check every lhs selector against right hand + for(size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->is_superselector_of(sub, wrapping)) return true; + } + return false; + } + + // it's a superselector if every selector on the right side + // is a superselector of any one of the left side selectors + bool Selector_List::is_superselector_of(Complex_Selector_Obj sub, std::string wrapping) + { + // Check every lhs selector against right hand + for(size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->is_superselector_of(sub)) return true; + } + return false; + } + + Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) { + std::vector unified_complex_selectors; + // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` + for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { + Complex_Selector_Obj seq1 = (*this)[lhs_i]; + for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { + Complex_Selector_Ptr seq2 = rhs->at(rhs_i); + + Selector_List_Obj result = seq1->unify_with(seq2); + if( result ) { + for(size_t i = 0, L = result->length(); i < L; ++i) { + unified_complex_selectors.push_back( (*result)[i] ); + } + } + } + } + + // Creates the final Selector_List by combining all the complex selectors + Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate()); + for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) { + final_result->append(*itr); + } + return final_result; + } + + void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends) + { + + Selector_List_Ptr extender = this; + for (auto complex_sel : extendee->elements()) { + Complex_Selector_Obj c = complex_sel; + + + // Ignore any parent selectors, until we find the first non Selectorerence head + Compound_Selector_Obj compound_sel = c->head(); + Complex_Selector_Obj pIter = complex_sel; + while (pIter) { + Compound_Selector_Obj pHead = pIter->head(); + if (pHead && Cast(pHead->elements()[0]) == NULL) { + compound_sel = pHead; + break; + } + + pIter = pIter->tail(); + } + + if (!pIter->head() || pIter->tail()) { + coreError("nested selectors may not be extended", c->pstate()); + } + + compound_sel->is_optional(extendee->is_optional()); + + for (size_t i = 0, L = extender->length(); i < L; ++i) { + extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel)); + } + } + }; + + void Compound_Selector::append(Simple_Selector_Ptr element) + { + Vectorized::append(element); + pstate_.offset += element->pstate().offset; + } + + Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs) + { + Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate()); + // result->has_parent_reference(has_parent_reference()); + + // not very efficient because it needs to preserve order + for (size_t i = 0, L = length(); i < L; ++i) + { + bool found = false; + std::string thisSelector((*this)[i]->to_string()); + for (size_t j = 0, M = rhs->length(); j < M; ++j) + { + if (thisSelector == (*rhs)[j]->to_string()) + { + found = true; + break; + } + } + if (!found) result->append((*this)[i]); + } + + return result; + } + + void Compound_Selector::mergeSources(ComplexSelectorSet& sources) + { + for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { + this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); + } + } + + Argument_Obj Arguments::get_rest_argument() + { + if (this->has_rest_argument()) { + for (Argument_Obj arg : this->elements()) { + if (arg->is_rest_argument()) { + return arg; + } + } + } + return NULL; + } + + Argument_Obj Arguments::get_keyword_argument() + { + if (this->has_keyword_argument()) { + for (Argument_Obj arg : this->elements()) { + if (arg->is_keyword_argument()) { + return arg; + } + } + } + return NULL; + } + + void Arguments::adjust_after_pushing(Argument_Obj a) + { + if (!a->name().empty()) { + if (has_keyword_argument()) { + coreError("named arguments must precede variable-length argument", a->pstate()); + } + has_named_arguments(true); + } + else if (a->is_rest_argument()) { + if (has_rest_argument()) { + coreError("functions and mixins may only be called with one variable-length argument", a->pstate()); + } + if (has_keyword_argument_) { + coreError("only keyword arguments may follow variable arguments", a->pstate()); + } + has_rest_argument(true); + } + else if (a->is_keyword_argument()) { + if (has_keyword_argument()) { + coreError("functions and mixins may only be called with one keyword argument", a->pstate()); + } + has_keyword_argument(true); + } + else { + if (has_rest_argument()) { + coreError("ordinal arguments must precede variable-length arguments", a->pstate()); + } + if (has_named_arguments()) { + coreError("ordinal arguments must precede named arguments", a->pstate()); + } + } + } + + bool Ruleset::is_invisible() const { + if (Selector_List_Ptr sl = Cast(selector())) { + for (size_t i = 0, L = sl->length(); i < L; ++i) + if (!(*sl)[i]->has_placeholder()) return false; + } + return true; + } + + bool Media_Block::is_invisible() const { + for (size_t i = 0, L = block()->length(); i < L; ++i) { + Statement_Obj stm = block()->at(i); + if (!stm->is_invisible()) return false; + } + return true; + } + + Number::Number(ParserState pstate, double val, std::string u, bool zero) + : Value(pstate), + Units(), + value_(val), + zero_(zero), + hash_(0) + { + size_t l = 0; + size_t r; + if (!u.empty()) { + bool nominator = true; + while (true) { + r = u.find_first_of("*/", l); + std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); + if (!unit.empty()) { + if (nominator) numerators.push_back(unit); + else denominators.push_back(unit); + } + if (r == std::string::npos) break; + // ToDo: should error for multiple slashes + // if (!nominator && u[r] == '/') error(...) + if (u[r] == '/') + nominator = false; + // strange math parsing? + // else if (u[r] == '*') + // nominator = true; + l = r + 1; + } + } + concrete_type(NUMBER); + } + + // cancel out unnecessary units + void Number::reduce() + { + // apply conversion factor + value_ *= this->Units::reduce(); + } + + void Number::normalize() + { + // apply conversion factor + value_ *= this->Units::normalize(); + } + + bool Custom_Warning::operator== (const Expression& rhs) const + { + if (Custom_Warning_Ptr_Const r = Cast(&rhs)) { + return message() == r->message(); + } + return false; + } + + bool Custom_Error::operator== (const Expression& rhs) const + { + if (Custom_Error_Ptr_Const r = Cast(&rhs)) { + return message() == r->message(); + } + return false; + } + + bool Number::operator== (const Expression& rhs) const + { + if (auto rhsnr = Cast(&rhs)) { + return *this == *rhsnr; + } + return false; + } + + bool Number::operator== (const Number& rhs) const + { + Number l(*this), r(rhs); l.reduce(); r.reduce(); + size_t lhs_units = l.numerators.size() + l.denominators.size(); + size_t rhs_units = r.numerators.size() + r.denominators.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return NEAR_EQUAL(l.value(), r.value()); + } + l.normalize(); r.normalize(); + Units &lhs_unit = l, &rhs_unit = r; + return lhs_unit == rhs_unit && + NEAR_EQUAL(l.value(), r.value()); + } + + bool Number::operator< (const Number& rhs) const + { + Number l(*this), r(rhs); l.reduce(); r.reduce(); + size_t lhs_units = l.numerators.size() + l.denominators.size(); + size_t rhs_units = r.numerators.size() + r.denominators.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return l.value() < r.value(); + } + l.normalize(); r.normalize(); + Units &lhs_unit = l, &rhs_unit = r; + if (!(lhs_unit == rhs_unit)) { + /* ToDo: do we always get usefull backtraces? */ + throw Exception::IncompatibleUnits(rhs, *this); + } + return lhs_unit < rhs_unit || + l.value() < r.value(); + } + + bool String_Quoted::operator== (const Expression& rhs) const + { + if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { + return (value() == qstr->value()); + } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { + return (value() == cstr->value()); + } + return false; + } + + bool String_Constant::is_invisible() const { + return value_.empty() && quote_mark_ == 0; + } + + bool String_Constant::operator== (const Expression& rhs) const + { + if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { + return (value() == qstr->value()); + } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { + return (value() == cstr->value()); + } + return false; + } + + bool String_Schema::is_left_interpolant(void) const + { + return length() && first()->is_left_interpolant(); + } + bool String_Schema::is_right_interpolant(void) const + { + return length() && last()->is_right_interpolant(); + } + + bool String_Schema::operator== (const Expression& rhs) const + { + if (String_Schema_Ptr_Const r = Cast(&rhs)) { + if (length() != r->length()) return false; + for (size_t i = 0, L = length(); i < L; ++i) { + Expression_Obj rv = (*r)[i]; + Expression_Obj lv = (*this)[i]; + if (!lv || !rv) return false; + if (!(*lv == *rv)) return false; + } + return true; + } + return false; + } + + bool Boolean::operator== (const Expression& rhs) const + { + if (Boolean_Ptr_Const r = Cast(&rhs)) { + return (value() == r->value()); + } + return false; + } + + bool Color::operator== (const Expression& rhs) const + { + if (Color_Ptr_Const r = Cast(&rhs)) { + return r_ == r->r() && + g_ == r->g() && + b_ == r->b() && + a_ == r->a(); + } + return false; + } + + bool List::operator== (const Expression& rhs) const + { + if (List_Ptr_Const r = Cast(&rhs)) { + if (length() != r->length()) return false; + if (separator() != r->separator()) return false; + if (is_bracketed() != r->is_bracketed()) return false; + for (size_t i = 0, L = length(); i < L; ++i) { + Expression_Obj rv = r->at(i); + Expression_Obj lv = this->at(i); + if (!lv || !rv) return false; + if (!(*lv == *rv)) return false; + } + return true; + } + return false; + } + + bool Map::operator== (const Expression& rhs) const + { + if (Map_Ptr_Const r = Cast(&rhs)) { + if (length() != r->length()) return false; + for (auto key : keys()) { + Expression_Obj lv = at(key); + Expression_Obj rv = r->at(key); + if (!rv || !lv) return false; + if (!(*lv == *rv)) return false; + } + return true; + } + return false; + } + + bool Null::operator== (const Expression& rhs) const + { + return rhs.concrete_type() == NULL_VAL; + } + + bool Function::operator== (const Expression& rhs) const + { + if (Function_Ptr_Const r = Cast(&rhs)) { + Definition_Ptr_Const d1 = Cast(definition()); + Definition_Ptr_Const d2 = Cast(r->definition()); + return d1 && d2 && d1 == d2 && is_css() == r->is_css(); + } + return false; + } + + size_t List::size() const { + if (!is_arglist_) return length(); + // arglist expects a list of arguments + // so we need to break before keywords + for (size_t i = 0, L = length(); i < L; ++i) { + Expression_Obj obj = this->at(i); + if (Argument_Ptr arg = Cast(obj)) { + if (!arg->name().empty()) return i; + } + } + return length(); + } + + Expression_Obj Hashed::at(Expression_Obj k) const + { + if (elements_.count(k)) + { return elements_.at(k); } + else { return NULL; } + } + + bool Binary_Expression::is_left_interpolant(void) const + { + return is_interpolant() || (left() && left()->is_left_interpolant()); + } + bool Binary_Expression::is_right_interpolant(void) const + { + return is_interpolant() || (right() && right()->is_right_interpolant()); + } + + const std::string AST_Node::to_string(Sass_Inspect_Options opt) const + { + Sass_Output_Options out(opt); + Emitter emitter(out); + Inspect i(emitter); + i.in_declaration = true; + // ToDo: inspect should be const + const_cast(this)->perform(&i); + return i.get_buffer(); + } + + const std::string AST_Node::to_string() const + { + return to_string({ NESTED, 5 }); + } + + std::string String_Quoted::inspect() const + { + return quote(value_, '*'); + } + + std::string String_Constant::inspect() const + { + return quote(value_, '*'); + } + + bool Declaration::is_invisible() const + { + if (is_custom_property()) return false; + + return !(value_ && value_->concrete_type() != Expression::NULL_VAL); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Additional method on Lists to retrieve values directly or from an encompassed Argument. + ////////////////////////////////////////////////////////////////////////////////////////// + Expression_Obj List::value_at_index(size_t i) { + Expression_Obj obj = this->at(i); + if (is_arglist_) { + if (Argument_Ptr arg = Cast(obj)) { + return arg->value(); + } else { + return obj; + } + } else { + return obj; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Convert map to (key, value) list. + ////////////////////////////////////////////////////////////////////////////////////////// + List_Obj Map::to_list(ParserState& pstate) { + List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); + + for (auto key : keys()) { + List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); + l->append(key); + l->append(at(key)); + ret->append(l); + } + + return ret; + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Copy implementations + ////////////////////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUG_SHARED_PTR + + #define IMPLEMENT_AST_OPERATORS(klass) \ + klass##_Ptr klass::copy(std::string file, size_t line) const { \ + klass##_Ptr cpy = new klass(this); \ + cpy->trace(file, line); \ + return cpy; \ + } \ + klass##_Ptr klass::clone(std::string file, size_t line) const { \ + klass##_Ptr cpy = copy(file, line); \ + cpy->cloneChildren(); \ + return cpy; \ + } \ + + #else + + #define IMPLEMENT_AST_OPERATORS(klass) \ + klass##_Ptr klass::copy() const { \ + return new klass(this); \ + } \ + klass##_Ptr klass::clone() const { \ + klass##_Ptr cpy = copy(); \ + cpy->cloneChildren(); \ + return cpy; \ + } \ + + #endif + + IMPLEMENT_AST_OPERATORS(Supports_Operator); + IMPLEMENT_AST_OPERATORS(Supports_Negation); + IMPLEMENT_AST_OPERATORS(Compound_Selector); + IMPLEMENT_AST_OPERATORS(Complex_Selector); + IMPLEMENT_AST_OPERATORS(Element_Selector); + IMPLEMENT_AST_OPERATORS(Class_Selector); + IMPLEMENT_AST_OPERATORS(Id_Selector); + IMPLEMENT_AST_OPERATORS(Pseudo_Selector); + IMPLEMENT_AST_OPERATORS(Wrapped_Selector); + IMPLEMENT_AST_OPERATORS(Selector_List); + IMPLEMENT_AST_OPERATORS(Ruleset); + IMPLEMENT_AST_OPERATORS(Media_Block); + IMPLEMENT_AST_OPERATORS(Custom_Warning); + IMPLEMENT_AST_OPERATORS(Custom_Error); + IMPLEMENT_AST_OPERATORS(List); + IMPLEMENT_AST_OPERATORS(Map); + IMPLEMENT_AST_OPERATORS(Function); + IMPLEMENT_AST_OPERATORS(Number); + IMPLEMENT_AST_OPERATORS(Binary_Expression); + IMPLEMENT_AST_OPERATORS(String_Schema); + IMPLEMENT_AST_OPERATORS(String_Constant); + IMPLEMENT_AST_OPERATORS(String_Quoted); + IMPLEMENT_AST_OPERATORS(Boolean); + IMPLEMENT_AST_OPERATORS(Color); + IMPLEMENT_AST_OPERATORS(Null); + IMPLEMENT_AST_OPERATORS(Parent_Selector); + IMPLEMENT_AST_OPERATORS(Import); + IMPLEMENT_AST_OPERATORS(Import_Stub); + IMPLEMENT_AST_OPERATORS(Function_Call); + IMPLEMENT_AST_OPERATORS(Directive); + IMPLEMENT_AST_OPERATORS(At_Root_Block); + IMPLEMENT_AST_OPERATORS(Supports_Block); + IMPLEMENT_AST_OPERATORS(While); + IMPLEMENT_AST_OPERATORS(Each); + IMPLEMENT_AST_OPERATORS(For); + IMPLEMENT_AST_OPERATORS(If); + IMPLEMENT_AST_OPERATORS(Mixin_Call); + IMPLEMENT_AST_OPERATORS(Extension); + IMPLEMENT_AST_OPERATORS(Media_Query); + IMPLEMENT_AST_OPERATORS(Media_Query_Expression); + IMPLEMENT_AST_OPERATORS(Debug); + IMPLEMENT_AST_OPERATORS(Error); + IMPLEMENT_AST_OPERATORS(Warning); + IMPLEMENT_AST_OPERATORS(Assignment); + IMPLEMENT_AST_OPERATORS(Return); + IMPLEMENT_AST_OPERATORS(At_Root_Query); + IMPLEMENT_AST_OPERATORS(Variable); + IMPLEMENT_AST_OPERATORS(Comment); + IMPLEMENT_AST_OPERATORS(Attribute_Selector); + IMPLEMENT_AST_OPERATORS(Supports_Interpolation); + IMPLEMENT_AST_OPERATORS(Supports_Declaration); + IMPLEMENT_AST_OPERATORS(Supports_Condition); + IMPLEMENT_AST_OPERATORS(Parameters); + IMPLEMENT_AST_OPERATORS(Parameter); + IMPLEMENT_AST_OPERATORS(Arguments); + IMPLEMENT_AST_OPERATORS(Argument); + IMPLEMENT_AST_OPERATORS(Unary_Expression); + IMPLEMENT_AST_OPERATORS(Function_Call_Schema); + IMPLEMENT_AST_OPERATORS(Block); + IMPLEMENT_AST_OPERATORS(Content); + IMPLEMENT_AST_OPERATORS(Trace); + IMPLEMENT_AST_OPERATORS(Keyframe_Rule); + IMPLEMENT_AST_OPERATORS(Bubble); + IMPLEMENT_AST_OPERATORS(Selector_Schema); + IMPLEMENT_AST_OPERATORS(Placeholder_Selector); + IMPLEMENT_AST_OPERATORS(Definition); + IMPLEMENT_AST_OPERATORS(Declaration); +} diff --git a/src/ast.hpp b/src/ast.hpp new file mode 100644 index 000000000..a2be8685c --- /dev/null +++ b/src/ast.hpp @@ -0,0 +1,3049 @@ +#ifndef SASS_AST_H +#define SASS_AST_H + +#include "sass.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#ifdef DEBUG_SHARED_PTR + +#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy(std::string, size_t) const = 0; \ + virtual klass##_Ptr clone(std::string, size_t) const = 0; \ + +#define ATTACH_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy(std::string, size_t) const; \ + virtual klass##_Ptr clone(std::string, size_t) const; \ + +#else + +#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy() const = 0; \ + virtual klass##_Ptr clone() const = 0; \ + +#define ATTACH_AST_OPERATIONS(klass) \ + virtual klass##_Ptr copy() const; \ + virtual klass##_Ptr clone() const; \ + +#endif + +#ifdef __clang__ + +/* + * There are some overloads used here that trigger the clang overload + * hiding warning. Specifically: + * + * Type type() which hides string type() from Expression + * + */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Woverloaded-virtual" + +#endif + +#include "util.hpp" +#include "units.hpp" +#include "context.hpp" +#include "position.hpp" +#include "constants.hpp" +#include "operation.hpp" +#include "position.hpp" +#include "inspect.hpp" +#include "source_map.hpp" +#include "environment.hpp" +#include "error_handling.hpp" +#include "ast_def_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "source_map.hpp" + +#include "sass.h" + +namespace Sass { + + // easier to search with name + const bool DELAYED = true; + + // ToDo: should this really be hardcoded + // Note: most methods follow precision option + const double NUMBER_EPSILON = 0.00000000000001; + + // macro to test if numbers are equal within a small error margin + #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON + + // ToDo: where does this fit best? + // We don't share this with C-API? + class Operand { + public: + Operand(Sass_OP operand, bool ws_before = false, bool ws_after = false) + : operand(operand), ws_before(ws_before), ws_after(ws_after) + { } + public: + enum Sass_OP operand; + bool ws_before; + bool ws_after; + }; + + ////////////////////////////////////////////////////////// + // `hash_combine` comes from boost (functional/hash): + // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html + // Boost Software License - Version 1.0 + // http://www.boost.org/users/license.html + template + void hash_combine (std::size_t& seed, const T& val) + { + seed ^= std::hash()(val) + 0x9e3779b9 + + (seed<<6) + (seed>>2); + } + ////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////// + // Abstract base class for all abstract syntax tree nodes. + ////////////////////////////////////////////////////////// + class AST_Node : public SharedObj { + ADD_PROPERTY(ParserState, pstate) + public: + AST_Node(ParserState pstate) + : pstate_(pstate) + { } + AST_Node(const AST_Node* ptr) + : pstate_(ptr->pstate_) + { } + + // AST_Node(AST_Node& ptr) = delete; + + virtual ~AST_Node() = 0; + virtual size_t hash() { return 0; } + ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node); + virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } + virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } + virtual const std::string to_string(Sass_Inspect_Options opt) const; + virtual const std::string to_string() const; + virtual void cloneChildren() {}; + // generic find function (not fully implemented yet) + // ToDo: add specific implementions to all children + virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; + public: + void update_pstate(const ParserState& pstate); + public: + Offset off() { return pstate(); } + Position pos() { return pstate(); } + ATTACH_OPERATIONS() + }; + inline AST_Node::~AST_Node() { } + + ////////////////////////////////////////////////////////////////////// + // define cast template now (need complete type) + ////////////////////////////////////////////////////////////////////// + + template + T* Cast(AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + template + const T* Cast(const AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + ////////////////////////////////////////////////////////////////////// + // Abstract base class for expressions. This side of the AST hierarchy + // represents elements in value contexts, which exist primarily to be + // evaluated and returned. + ////////////////////////////////////////////////////////////////////// + class Expression : public AST_Node { + public: + enum Concrete_Type { + NONE, + BOOLEAN, + NUMBER, + COLOR, + STRING, + LIST, + MAP, + SELECTOR, + NULL_VAL, + FUNCTION_VAL, + C_WARNING, + C_ERROR, + FUNCTION, + VARIABLE, + NUM_TYPES + }; + enum Simple_Type { + SIMPLE, + ATTR_SEL, + PSEUDO_SEL, + WRAPPED_SEL, + }; + private: + // expressions in some contexts shouldn't be evaluated + ADD_PROPERTY(bool, is_delayed) + ADD_PROPERTY(bool, is_expanded) + ADD_PROPERTY(bool, is_interpolant) + ADD_PROPERTY(Concrete_Type, concrete_type) + public: + Expression(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : AST_Node(pstate), + is_delayed_(d), + is_expanded_(e), + is_interpolant_(i), + concrete_type_(ct) + { } + Expression(const Expression* ptr) + : AST_Node(ptr), + is_delayed_(ptr->is_delayed_), + is_expanded_(ptr->is_expanded_), + is_interpolant_(ptr->is_interpolant_), + concrete_type_(ptr->concrete_type_) + { } + virtual operator bool() { return true; } + virtual ~Expression() { } + virtual std::string type() const { return ""; /* TODO: raise an error? */ } + virtual bool is_invisible() const { return false; } + static std::string type_name() { return ""; } + virtual bool is_false() { return false; } + // virtual bool is_true() { return !is_false(); } + virtual bool operator== (const Expression& rhs) const { return false; } + virtual bool eq(const Expression& rhs) const { return *this == rhs; }; + virtual void set_delayed(bool delayed) { is_delayed(delayed); } + virtual bool has_interpolant() const { return is_interpolant(); } + virtual bool is_left_interpolant() const { return is_interpolant(); } + virtual bool is_right_interpolant() const { return is_interpolant(); } + virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } + virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } + ATTACH_VIRTUAL_AST_OPERATIONS(Expression); + virtual size_t hash() { return 0; } + }; + + ////////////////////////////////////////////////////////////////////// + // Still just an expression, but with a to_string method + ////////////////////////////////////////////////////////////////////// + class PreValue : public Expression { + public: + PreValue(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : Expression(pstate, d, e, i, ct) + { } + PreValue(const PreValue* ptr) + : Expression(ptr) + { } + ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); + virtual ~PreValue() { } + }; + + ////////////////////////////////////////////////////////////////////// + // base class for values that support operations + ////////////////////////////////////////////////////////////////////// + class Value : public Expression { + public: + Value(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : Expression(pstate, d, e, i, ct) + { } + Value(const Value* ptr) + : Expression(ptr) + { } + ATTACH_VIRTUAL_AST_OPERATIONS(Value); + virtual bool operator== (const Expression& rhs) const = 0; + }; +} + +///////////////////////////////////////////////////////////////////////////////////// +// Hash method specializations for std::unordered_map to work with Sass::Expression +///////////////////////////////////////////////////////////////////////////////////// + +namespace std { + template<> + struct hash + { + size_t operator()(Sass::Expression_Obj s) const + { + return s->hash(); + } + }; + template<> + struct equal_to + { + bool operator()( Sass::Expression_Obj lhs, Sass::Expression_Obj rhs) const + { + return lhs->hash() == rhs->hash(); + } + }; +} + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like vectors. Uses the + // "Template Method" design pattern to allow subclasses to adjust their flags + // when certain objects are pushed. + ///////////////////////////////////////////////////////////////////////////// + template + class Vectorized { + std::vector elements_; + protected: + size_t hash_; + void reset_hash() { hash_ = 0; } + virtual void adjust_after_pushing(T element) { } + public: + Vectorized(size_t s = 0) : elements_(std::vector()), hash_(0) + { elements_.reserve(s); } + virtual ~Vectorized() = 0; + size_t length() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + void clear() { return elements_.clear(); } + T last() const { return elements_.back(); } + T first() const { return elements_.front(); } + T& operator[](size_t i) { return elements_[i]; } + virtual const T& at(size_t i) const { return elements_.at(i); } + virtual T& at(size_t i) { return elements_.at(i); } + const T& operator[](size_t i) const { return elements_[i]; } + virtual void append(T element) + { + if (element) { + reset_hash(); + elements_.push_back(element); + adjust_after_pushing(element); + } + } + virtual void concat(Vectorized* v) + { + for (size_t i = 0, L = v->length(); i < L; ++i) this->append((*v)[i]); + } + Vectorized& unshift(T element) + { + elements_.insert(elements_.begin(), element); + return *this; + } + std::vector& elements() { return elements_; } + const std::vector& elements() const { return elements_; } + std::vector& elements(std::vector& e) { elements_ = e; return elements_; } + + virtual size_t hash() + { + if (hash_ == 0) { + for (T& el : elements_) { + hash_combine(hash_, el->hash()); + } + } + return hash_; + } + + typename std::vector::iterator end() { return elements_.end(); } + typename std::vector::iterator begin() { return elements_.begin(); } + typename std::vector::const_iterator end() const { return elements_.end(); } + typename std::vector::const_iterator begin() const { return elements_.begin(); } + typename std::vector::iterator erase(typename std::vector::iterator el) { return elements_.erase(el); } + typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { return elements_.erase(el); } + + }; + template + inline Vectorized::~Vectorized() { } + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like a hash table. Uses an + // extra internally to maintain insertion order for interation. + ///////////////////////////////////////////////////////////////////////////// + class Hashed { + private: + ExpressionMap elements_; + std::vector list_; + protected: + size_t hash_; + Expression_Obj duplicate_key_; + void reset_hash() { hash_ = 0; } + void reset_duplicate_key() { duplicate_key_ = 0; } + virtual void adjust_after_pushing(std::pair p) { } + public: + Hashed(size_t s = 0) + : elements_(ExpressionMap(s)), + list_(std::vector()), + hash_(0), duplicate_key_(NULL) + { elements_.reserve(s); list_.reserve(s); } + virtual ~Hashed(); + size_t length() const { return list_.size(); } + bool empty() const { return list_.empty(); } + bool has(Expression_Obj k) const { return elements_.count(k) == 1; } + Expression_Obj at(Expression_Obj k) const; + bool has_duplicate_key() const { return duplicate_key_ != 0; } + Expression_Obj get_duplicate_key() const { return duplicate_key_; } + const ExpressionMap elements() { return elements_; } + Hashed& operator<<(std::pair p) + { + reset_hash(); + + if (!has(p.first)) list_.push_back(p.first); + else if (!duplicate_key_) duplicate_key_ = p.first; + + elements_[p.first] = p.second; + + adjust_after_pushing(p); + return *this; + } + Hashed& operator+=(Hashed* h) + { + if (length() == 0) { + this->elements_ = h->elements_; + this->list_ = h->list_; + return *this; + } + + for (auto key : h->keys()) { + *this << std::make_pair(key, h->at(key)); + } + + reset_duplicate_key(); + return *this; + } + const ExpressionMap& pairs() const { return elements_; } + const std::vector& keys() const { return list_; } + +// std::unordered_map::iterator end() { return elements_.end(); } +// std::unordered_map::iterator begin() { return elements_.begin(); } +// std::unordered_map::const_iterator end() const { return elements_.end(); } +// std::unordered_map::const_iterator begin() const { return elements_.begin(); } + + }; + inline Hashed::~Hashed() { } + + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements. This side of the AST hierarchy + // represents elements in expansion contexts, which exist primarily to be + // rewritten and macro-expanded. + ///////////////////////////////////////////////////////////////////////// + class Statement : public AST_Node { + public: + enum Statement_Type { + NONE, + RULESET, + MEDIA, + DIRECTIVE, + SUPPORTS, + ATROOT, + BUBBLE, + CONTENT, + KEYFRAMERULE, + DECLARATION, + ASSIGNMENT, + IMPORT_STUB, + IMPORT, + COMMENT, + WARNING, + RETURN, + EXTEND, + ERROR, + DEBUGSTMT, + WHILE, + EACH, + FOR, + IF + }; + private: + ADD_PROPERTY(Statement_Type, statement_type) + ADD_PROPERTY(size_t, tabs) + ADD_PROPERTY(bool, group_end) + public: + Statement(ParserState pstate, Statement_Type st = NONE, size_t t = 0) + : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false) + { } + Statement(const Statement* ptr) + : AST_Node(ptr), + statement_type_(ptr->statement_type_), + tabs_(ptr->tabs_), + group_end_(ptr->group_end_) + { } + virtual ~Statement() = 0; + // needed for rearranging nested rulesets during CSS emission + virtual bool is_invisible() const { return false; } + virtual bool bubbles() { return false; } + virtual bool has_content() + { + return statement_type_ == CONTENT; + } + }; + inline Statement::~Statement() { } + + //////////////////////// + // Blocks of statements. + //////////////////////// + class Block : public Statement, public Vectorized { + ADD_PROPERTY(bool, is_root) + // needed for properly formatted CSS emission + protected: + void adjust_after_pushing(Statement_Obj s) + { + } + public: + Block(ParserState pstate, size_t s = 0, bool r = false) + : Statement(pstate), + Vectorized(s), + is_root_(r) + { } + Block(const Block* ptr) + : Statement(ptr), + Vectorized(*ptr), + is_root_(ptr->is_root_) + { } + virtual bool has_content() + { + for (size_t i = 0, L = elements().size(); i < L; ++i) { + if (elements()[i]->has_content()) return true; + } + return Statement::has_content(); + } + ATTACH_AST_OPERATIONS(Block) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for statements that contain blocks of statements. + //////////////////////////////////////////////////////////////////////// + class Has_Block : public Statement { + ADD_PROPERTY(Block_Obj, block) + public: + Has_Block(ParserState pstate, Block_Obj b) + : Statement(pstate), block_(b) + { } + Has_Block(const Has_Block* ptr) + : Statement(ptr), block_(ptr->block_) + { } + virtual bool has_content() + { + return (block_ && block_->has_content()) || Statement::has_content(); + } + virtual ~Has_Block() = 0; + }; + inline Has_Block::~Has_Block() { } + + ///////////////////////////////////////////////////////////////////////////// + // Rulesets (i.e., sets of styles headed by a selector and containing a block + // of style declarations. + ///////////////////////////////////////////////////////////////////////////// + class Ruleset : public Has_Block { + ADD_PROPERTY(Selector_List_Obj, selector) + ADD_PROPERTY(bool, is_root); + public: + Ruleset(ParserState pstate, Selector_List_Obj s = 0, Block_Obj b = 0) + : Has_Block(pstate, b), selector_(s), is_root_(false) + { statement_type(RULESET); } + Ruleset(const Ruleset* ptr) + : Has_Block(ptr), + selector_(ptr->selector_), + is_root_(ptr->is_root_) + { statement_type(RULESET); } + bool is_invisible() const; + ATTACH_AST_OPERATIONS(Ruleset) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Bubble. + ///////////////// + class Bubble : public Statement { + ADD_PROPERTY(Statement_Obj, node) + ADD_PROPERTY(bool, group_end) + public: + Bubble(ParserState pstate, Statement_Obj n, Statement_Obj g = 0, size_t t = 0) + : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == 0) + { } + Bubble(const Bubble* ptr) + : Statement(ptr), + node_(ptr->node_), + group_end_(ptr->group_end_) + { } + bool bubbles() { return true; } + ATTACH_AST_OPERATIONS(Bubble) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Trace. + ///////////////// + class Trace : public Has_Block { + ADD_CONSTREF(char, type) + ADD_CONSTREF(std::string, name) + public: + Trace(ParserState pstate, std::string n, Block_Obj b = 0, char type = 'm') + : Has_Block(pstate, b), type_(type), name_(n) + { } + Trace(const Trace* ptr) + : Has_Block(ptr), + type_(ptr->type_), + name_(ptr->name_) + { } + ATTACH_AST_OPERATIONS(Trace) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Media queries. + ///////////////// + class Media_Block : public Has_Block { + ADD_PROPERTY(List_Obj, media_queries) + public: + Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b) + : Has_Block(pstate, b), media_queries_(mqs) + { statement_type(MEDIA); } + Media_Block(const Media_Block* ptr) + : Has_Block(ptr), media_queries_(ptr->media_queries_) + { statement_type(MEDIA); } + bool bubbles() { return true; } + bool is_invisible() const; + ATTACH_AST_OPERATIONS(Media_Block) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // At-rules -- arbitrary directives beginning with "@" that may have an + // optional statement block. + /////////////////////////////////////////////////////////////////////// + class Directive : public Has_Block { + ADD_CONSTREF(std::string, keyword) + ADD_PROPERTY(Selector_List_Obj, selector) + ADD_PROPERTY(Expression_Obj, value) + public: + Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0) + : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed + { statement_type(DIRECTIVE); } + Directive(const Directive* ptr) + : Has_Block(ptr), + keyword_(ptr->keyword_), + selector_(ptr->selector_), + value_(ptr->value_) // set value manually if needed + { statement_type(DIRECTIVE); } + bool bubbles() { return is_keyframes() || is_media(); } + bool is_media() { + return keyword_.compare("@-webkit-media") == 0 || + keyword_.compare("@-moz-media") == 0 || + keyword_.compare("@-o-media") == 0 || + keyword_.compare("@media") == 0; + } + bool is_keyframes() { + return keyword_.compare("@-webkit-keyframes") == 0 || + keyword_.compare("@-moz-keyframes") == 0 || + keyword_.compare("@-o-keyframes") == 0 || + keyword_.compare("@keyframes") == 0; + } + ATTACH_AST_OPERATIONS(Directive) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // Keyframe-rules -- the child blocks of "@keyframes" nodes. + /////////////////////////////////////////////////////////////////////// + class Keyframe_Rule : public Has_Block { + // according to css spec, this should be + // = | + ADD_PROPERTY(Selector_List_Obj, name) + public: + Keyframe_Rule(ParserState pstate, Block_Obj b) + : Has_Block(pstate, b), name_() + { statement_type(KEYFRAMERULE); } + Keyframe_Rule(const Keyframe_Rule* ptr) + : Has_Block(ptr), name_(ptr->name_) + { statement_type(KEYFRAMERULE); } + ATTACH_AST_OPERATIONS(Keyframe_Rule) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Declarations -- style rules consisting of a property name and values. + //////////////////////////////////////////////////////////////////////// + class Declaration : public Has_Block { + ADD_PROPERTY(String_Obj, property) + ADD_PROPERTY(Expression_Obj, value) + ADD_PROPERTY(bool, is_important) + ADD_PROPERTY(bool, is_custom_property) + ADD_PROPERTY(bool, is_indented) + public: + Declaration(ParserState pstate, + String_Obj prop, Expression_Obj val, bool i = false, bool c = false, Block_Obj b = 0) + : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) + { statement_type(DECLARATION); } + Declaration(const Declaration* ptr) + : Has_Block(ptr), + property_(ptr->property_), + value_(ptr->value_), + is_important_(ptr->is_important_), + is_custom_property_(ptr->is_custom_property_), + is_indented_(ptr->is_indented_) + { statement_type(DECLARATION); } + virtual bool is_invisible() const; + ATTACH_AST_OPERATIONS(Declaration) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////// + // Assignments -- variable and value. + ///////////////////////////////////// + class Assignment : public Statement { + ADD_CONSTREF(std::string, variable) + ADD_PROPERTY(Expression_Obj, value) + ADD_PROPERTY(bool, is_default) + ADD_PROPERTY(bool, is_global) + public: + Assignment(ParserState pstate, + std::string var, Expression_Obj val, + bool is_default = false, + bool is_global = false) + : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global) + { statement_type(ASSIGNMENT); } + Assignment(const Assignment* ptr) + : Statement(ptr), + variable_(ptr->variable_), + value_(ptr->value_), + is_default_(ptr->is_default_), + is_global_(ptr->is_global_) + { statement_type(ASSIGNMENT); } + ATTACH_AST_OPERATIONS(Assignment) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Import directives. CSS and Sass import lists can be intermingled, so it's + // necessary to store a list of each in an Import node. + //////////////////////////////////////////////////////////////////////////// + class Import : public Statement { + std::vector urls_; + std::vector incs_; + ADD_PROPERTY(List_Obj, import_queries); + public: + Import(ParserState pstate) + : Statement(pstate), + urls_(std::vector()), + incs_(std::vector()), + import_queries_() + { statement_type(IMPORT); } + Import(const Import* ptr) + : Statement(ptr), + urls_(ptr->urls_), + incs_(ptr->incs_), + import_queries_(ptr->import_queries_) + { statement_type(IMPORT); } + std::vector& urls() { return urls_; } + std::vector& incs() { return incs_; } + ATTACH_AST_OPERATIONS(Import) + ATTACH_OPERATIONS() + }; + + // not yet resolved single import + // so far we only know requested name + class Import_Stub : public Statement { + Include resource_; + public: + std::string abs_path() { return resource_.abs_path; }; + std::string imp_path() { return resource_.imp_path; }; + Include resource() { return resource_; }; + + Import_Stub(ParserState pstate, Include res) + : Statement(pstate), resource_(res) + { statement_type(IMPORT_STUB); } + Import_Stub(const Import_Stub* ptr) + : Statement(ptr), resource_(ptr->resource_) + { statement_type(IMPORT_STUB); } + ATTACH_AST_OPERATIONS(Import_Stub) + ATTACH_OPERATIONS() + }; + + ////////////////////////////// + // The Sass `@warn` directive. + ////////////////////////////// + class Warning : public Statement { + ADD_PROPERTY(Expression_Obj, message) + public: + Warning(ParserState pstate, Expression_Obj msg) + : Statement(pstate), message_(msg) + { statement_type(WARNING); } + Warning(const Warning* ptr) + : Statement(ptr), message_(ptr->message_) + { statement_type(WARNING); } + ATTACH_AST_OPERATIONS(Warning) + ATTACH_OPERATIONS() + }; + + /////////////////////////////// + // The Sass `@error` directive. + /////////////////////////////// + class Error : public Statement { + ADD_PROPERTY(Expression_Obj, message) + public: + Error(ParserState pstate, Expression_Obj msg) + : Statement(pstate), message_(msg) + { statement_type(ERROR); } + Error(const Error* ptr) + : Statement(ptr), message_(ptr->message_) + { statement_type(ERROR); } + ATTACH_AST_OPERATIONS(Error) + ATTACH_OPERATIONS() + }; + + /////////////////////////////// + // The Sass `@debug` directive. + /////////////////////////////// + class Debug : public Statement { + ADD_PROPERTY(Expression_Obj, value) + public: + Debug(ParserState pstate, Expression_Obj val) + : Statement(pstate), value_(val) + { statement_type(DEBUGSTMT); } + Debug(const Debug* ptr) + : Statement(ptr), value_(ptr->value_) + { statement_type(DEBUGSTMT); } + ATTACH_AST_OPERATIONS(Debug) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////// + // CSS comments. These may be interpolated. + /////////////////////////////////////////// + class Comment : public Statement { + ADD_PROPERTY(String_Obj, text) + ADD_PROPERTY(bool, is_important) + public: + Comment(ParserState pstate, String_Obj txt, bool is_important) + : Statement(pstate), text_(txt), is_important_(is_important) + { statement_type(COMMENT); } + Comment(const Comment* ptr) + : Statement(ptr), + text_(ptr->text_), + is_important_(ptr->is_important_) + { statement_type(COMMENT); } + virtual bool is_invisible() const + { return /* is_important() == */ false; } + ATTACH_AST_OPERATIONS(Comment) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////// + // The Sass `@if` control directive. + //////////////////////////////////// + class If : public Has_Block { + ADD_PROPERTY(Expression_Obj, predicate) + ADD_PROPERTY(Block_Obj, alternative) + public: + If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0) + : Has_Block(pstate, con), predicate_(pred), alternative_(alt) + { statement_type(IF); } + If(const If* ptr) + : Has_Block(ptr), + predicate_(ptr->predicate_), + alternative_(ptr->alternative_) + { statement_type(IF); } + virtual bool has_content() + { + return Has_Block::has_content() || (alternative_ && alternative_->has_content()); + } + ATTACH_AST_OPERATIONS(If) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////// + // The Sass `@for` control directive. + ///////////////////////////////////// + class For : public Has_Block { + ADD_CONSTREF(std::string, variable) + ADD_PROPERTY(Expression_Obj, lower_bound) + ADD_PROPERTY(Expression_Obj, upper_bound) + ADD_PROPERTY(bool, is_inclusive) + public: + For(ParserState pstate, + std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc) + : Has_Block(pstate, b), + variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) + { statement_type(FOR); } + For(const For* ptr) + : Has_Block(ptr), + variable_(ptr->variable_), + lower_bound_(ptr->lower_bound_), + upper_bound_(ptr->upper_bound_), + is_inclusive_(ptr->is_inclusive_) + { statement_type(FOR); } + ATTACH_AST_OPERATIONS(For) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////// + // The Sass `@each` control directive. + ////////////////////////////////////// + class Each : public Has_Block { + ADD_PROPERTY(std::vector, variables) + ADD_PROPERTY(Expression_Obj, list) + public: + Each(ParserState pstate, std::vector vars, Expression_Obj lst, Block_Obj b) + : Has_Block(pstate, b), variables_(vars), list_(lst) + { statement_type(EACH); } + Each(const Each* ptr) + : Has_Block(ptr), variables_(ptr->variables_), list_(ptr->list_) + { statement_type(EACH); } + ATTACH_AST_OPERATIONS(Each) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////// + // The Sass `@while` control directive. + /////////////////////////////////////// + class While : public Has_Block { + ADD_PROPERTY(Expression_Obj, predicate) + public: + While(ParserState pstate, Expression_Obj pred, Block_Obj b) + : Has_Block(pstate, b), predicate_(pred) + { statement_type(WHILE); } + While(const While* ptr) + : Has_Block(ptr), predicate_(ptr->predicate_) + { statement_type(WHILE); } + ATTACH_AST_OPERATIONS(While) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////// + // The @return directive for use inside SassScript functions. + ///////////////////////////////////////////////////////////// + class Return : public Statement { + ADD_PROPERTY(Expression_Obj, value) + public: + Return(ParserState pstate, Expression_Obj val) + : Statement(pstate), value_(val) + { statement_type(RETURN); } + Return(const Return* ptr) + : Statement(ptr), value_(ptr->value_) + { statement_type(RETURN); } + ATTACH_AST_OPERATIONS(Return) + ATTACH_OPERATIONS() + }; + + //////////////////////////////// + // The Sass `@extend` directive. + //////////////////////////////// + class Extension : public Statement { + ADD_PROPERTY(Selector_List_Obj, selector) + public: + Extension(ParserState pstate, Selector_List_Obj s) + : Statement(pstate), selector_(s) + { statement_type(EXTEND); } + Extension(const Extension* ptr) + : Statement(ptr), selector_(ptr->selector_) + { statement_type(EXTEND); } + ATTACH_AST_OPERATIONS(Extension) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////////////// + // Definitions for both mixins and functions. The two cases are distinguished + // by a type tag. + ///////////////////////////////////////////////////////////////////////////// + struct Backtrace; + typedef const char* Signature; + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); + class Definition : public Has_Block { + public: + enum Type { MIXIN, FUNCTION }; + ADD_CONSTREF(std::string, name) + ADD_PROPERTY(Parameters_Obj, parameters) + ADD_PROPERTY(Env*, environment) + ADD_PROPERTY(Type, type) + ADD_PROPERTY(Native_Function, native_function) + ADD_PROPERTY(Sass_Function_Entry, c_function) + ADD_PROPERTY(void*, cookie) + ADD_PROPERTY(bool, is_overload_stub) + ADD_PROPERTY(Signature, signature) + public: + Definition(const Definition* ptr) + : Has_Block(ptr), + name_(ptr->name_), + parameters_(ptr->parameters_), + environment_(ptr->environment_), + type_(ptr->type_), + native_function_(ptr->native_function_), + c_function_(ptr->c_function_), + cookie_(ptr->cookie_), + is_overload_stub_(ptr->is_overload_stub_), + signature_(ptr->signature_) + { } + + Definition(ParserState pstate, + std::string n, + Parameters_Obj params, + Block_Obj b, + Type t) + : Has_Block(pstate, b), + name_(n), + parameters_(params), + environment_(0), + type_(t), + native_function_(0), + c_function_(0), + cookie_(0), + is_overload_stub_(false), + signature_(0) + { } + Definition(ParserState pstate, + Signature sig, + std::string n, + Parameters_Obj params, + Native_Function func_ptr, + bool overload_stub = false) + : Has_Block(pstate, 0), + name_(n), + parameters_(params), + environment_(0), + type_(FUNCTION), + native_function_(func_ptr), + c_function_(0), + cookie_(0), + is_overload_stub_(overload_stub), + signature_(sig) + { } + Definition(ParserState pstate, + Signature sig, + std::string n, + Parameters_Obj params, + Sass_Function_Entry c_func, + bool whatever, + bool whatever2) + : Has_Block(pstate, 0), + name_(n), + parameters_(params), + environment_(0), + type_(FUNCTION), + native_function_(0), + c_function_(c_func), + cookie_(sass_function_get_cookie(c_func)), + is_overload_stub_(false), + signature_(sig) + { } + ATTACH_AST_OPERATIONS(Definition) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////// + // Mixin calls (i.e., `@include ...`). + ////////////////////////////////////// + class Mixin_Call : public Has_Block { + ADD_CONSTREF(std::string, name) + ADD_PROPERTY(Arguments_Obj, arguments) + public: + Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0) + : Has_Block(pstate, b), name_(n), arguments_(args) + { } + Mixin_Call(const Mixin_Call* ptr) + : Has_Block(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_) + { } + ATTACH_AST_OPERATIONS(Mixin_Call) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////// + // The @content directive for mixin content blocks. + /////////////////////////////////////////////////// + class Content : public Statement { + ADD_PROPERTY(Media_Block_Ptr, media_block) + public: + Content(ParserState pstate) + : Statement(pstate), + media_block_(NULL) + { statement_type(CONTENT); } + Content(const Content* ptr) + : Statement(ptr), + media_block_(ptr->media_block_) + { statement_type(CONTENT); } + ATTACH_AST_OPERATIONS(Content) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // Lists of values, both comma- and space-separated (distinguished by a + // type-tag.) Also used to represent variable-length argument lists. + /////////////////////////////////////////////////////////////////////// + class List : public Value, public Vectorized { + void adjust_after_pushing(Expression_Obj e) { is_expanded(false); } + private: + ADD_PROPERTY(enum Sass_Separator, separator) + ADD_PROPERTY(bool, is_arglist) + ADD_PROPERTY(bool, is_bracketed) + ADD_PROPERTY(bool, from_selector) + public: + List(ParserState pstate, + size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false) + : Value(pstate), + Vectorized(size), + separator_(sep), + is_arglist_(argl), + is_bracketed_(bracket), + from_selector_(false) + { concrete_type(LIST); } + List(const List* ptr) + : Value(ptr), + Vectorized(*ptr), + separator_(ptr->separator_), + is_arglist_(ptr->is_arglist_), + is_bracketed_(ptr->is_bracketed_), + from_selector_(ptr->from_selector_) + { concrete_type(LIST); } + std::string type() const { return is_arglist_ ? "arglist" : "list"; } + static std::string type_name() { return "list"; } + const char* sep_string(bool compressed = false) const { + return separator() == SASS_SPACE ? + " " : (compressed ? "," : ", "); + } + bool is_invisible() const { return empty() && !is_bracketed(); } + Expression_Obj value_at_index(size_t i); + + virtual size_t size() const; + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(sep_string()); + hash_combine(hash_, std::hash()(is_bracketed())); + for (size_t i = 0, L = length(); i < L; ++i) + hash_combine(hash_, (elements()[i])->hash()); + } + return hash_; + } + + virtual void set_delayed(bool delayed) + { + is_delayed(delayed); + // don't set children + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(List) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////////////////////////// + // Key value paris. + /////////////////////////////////////////////////////////////////////// + class Map : public Value, public Hashed { + void adjust_after_pushing(std::pair p) { is_expanded(false); } + public: + Map(ParserState pstate, + size_t size = 0) + : Value(pstate), + Hashed(size) + { concrete_type(MAP); } + Map(const Map* ptr) + : Value(ptr), + Hashed(*ptr) + { concrete_type(MAP); } + std::string type() const { return "map"; } + static std::string type_name() { return "map"; } + bool is_invisible() const { return empty(); } + List_Obj to_list(ParserState& pstate); + + virtual size_t hash() + { + if (hash_ == 0) { + for (auto key : keys()) { + hash_combine(hash_, key->hash()); + hash_combine(hash_, at(key)->hash()); + } + } + + return hash_; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Map) + ATTACH_OPERATIONS() + }; + + inline static const std::string sass_op_to_name(enum Sass_OP op) { + switch (op) { + case AND: return "and"; + case OR: return "or"; + case EQ: return "eq"; + case NEQ: return "neq"; + case GT: return "gt"; + case GTE: return "gte"; + case LT: return "lt"; + case LTE: return "lte"; + case ADD: return "plus"; + case SUB: return "sub"; + case MUL: return "times"; + case DIV: return "div"; + case MOD: return "mod"; + // this is only used internally! + case NUM_OPS: return "[OPS]"; + default: return "invalid"; + } + } + + inline static const std::string sass_op_separator(enum Sass_OP op) { + switch (op) { + case AND: return "&&"; + case OR: return "||"; + case EQ: return "=="; + case NEQ: return "!="; + case GT: return ">"; + case GTE: return ">="; + case LT: return "<"; + case LTE: return "<="; + case ADD: return "+"; + case SUB: return "-"; + case MUL: return "*"; + case DIV: return "/"; + case MOD: return "%"; + // this is only used internally! + case NUM_OPS: return "[OPS]"; + default: return "invalid"; + } + } + + ////////////////////////////////////////////////////////////////////////// + // Binary expressions. Represents logical, relational, and arithmetic + // operations. Templatized to avoid large switch statements and repetitive + // subclassing. + ////////////////////////////////////////////////////////////////////////// + class Binary_Expression : public PreValue { + private: + HASH_PROPERTY(Operand, op) + HASH_PROPERTY(Expression_Obj, left) + HASH_PROPERTY(Expression_Obj, right) + size_t hash_; + public: + Binary_Expression(ParserState pstate, + Operand op, Expression_Obj lhs, Expression_Obj rhs) + : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) + { } + Binary_Expression(const Binary_Expression* ptr) + : PreValue(ptr), + op_(ptr->op_), + left_(ptr->left_), + right_(ptr->right_), + hash_(ptr->hash_) + { } + const std::string type_name() { + return sass_op_to_name(optype()); + } + const std::string separator() { + return sass_op_separator(optype()); + } + bool is_left_interpolant(void) const; + bool is_right_interpolant(void) const; + bool has_interpolant() const + { + return is_left_interpolant() || + is_right_interpolant(); + } + virtual void set_delayed(bool delayed) + { + right()->set_delayed(delayed); + left()->set_delayed(delayed); + is_delayed(delayed); + } + virtual bool operator==(const Expression& rhs) const + { + try + { + Binary_Expression_Ptr_Const m = Cast(&rhs); + if (m == 0) return false; + return type() == m->type() && + *left() == *m->left() && + *right() == *m->right(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(optype()); + hash_combine(hash_, left()->hash()); + hash_combine(hash_, right()->hash()); + } + return hash_; + } + enum Sass_OP optype() const { return op_.operand; } + ATTACH_AST_OPERATIONS(Binary_Expression) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Arithmetic negation (logical negation is just an ordinary function call). + //////////////////////////////////////////////////////////////////////////// + class Unary_Expression : public Expression { + public: + enum Type { PLUS, MINUS, NOT, SLASH }; + private: + HASH_PROPERTY(Type, optype) + HASH_PROPERTY(Expression_Obj, operand) + size_t hash_; + public: + Unary_Expression(ParserState pstate, Type t, Expression_Obj o) + : Expression(pstate), optype_(t), operand_(o), hash_(0) + { } + Unary_Expression(const Unary_Expression* ptr) + : Expression(ptr), + optype_(ptr->optype_), + operand_(ptr->operand_), + hash_(ptr->hash_) + { } + const std::string type_name() { + switch (optype_) { + case PLUS: return "plus"; + case MINUS: return "minus"; + case SLASH: return "slash"; + case NOT: return "not"; + default: return "invalid"; + } + } + virtual bool operator==(const Expression& rhs) const + { + try + { + Unary_Expression_Ptr_Const m = Cast(&rhs); + if (m == 0) return false; + return type() == m->type() && + *operand() == *m->operand(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(optype_); + hash_combine(hash_, operand()->hash()); + }; + return hash_; + } + ATTACH_AST_OPERATIONS(Unary_Expression) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////// + // Individual argument objects for mixin and function calls. + //////////////////////////////////////////////////////////// + class Argument : public Expression { + HASH_PROPERTY(Expression_Obj, value) + HASH_CONSTREF(std::string, name) + ADD_PROPERTY(bool, is_rest_argument) + ADD_PROPERTY(bool, is_keyword_argument) + size_t hash_; + public: + Argument(ParserState pstate, Expression_Obj val, std::string n = "", bool rest = false, bool keyword = false) + : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) + { + if (!name_.empty() && is_rest_argument_) { + coreError("variable-length argument may not be passed by name", pstate_); + } + } + Argument(const Argument* ptr) + : Expression(ptr), + value_(ptr->value_), + name_(ptr->name_), + is_rest_argument_(ptr->is_rest_argument_), + is_keyword_argument_(ptr->is_keyword_argument_), + hash_(ptr->hash_) + { + if (!name_.empty() && is_rest_argument_) { + coreError("variable-length argument may not be passed by name", pstate_); + } + } + + virtual void set_delayed(bool delayed); + virtual bool operator==(const Expression& rhs) const + { + try + { + Argument_Ptr_Const m = Cast(&rhs); + if (!(m && name() == m->name())) return false; + return *value() == *m->value(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(name()); + hash_combine(hash_, value()->hash()); + } + return hash_; + } + + ATTACH_AST_OPERATIONS(Argument) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Argument lists -- in their own class to facilitate context-sensitive + // error checking (e.g., ensuring that all ordinal arguments precede all + // named arguments). + //////////////////////////////////////////////////////////////////////// + class Arguments : public Expression, public Vectorized { + ADD_PROPERTY(bool, has_named_arguments) + ADD_PROPERTY(bool, has_rest_argument) + ADD_PROPERTY(bool, has_keyword_argument) + protected: + void adjust_after_pushing(Argument_Obj a); + public: + Arguments(ParserState pstate) + : Expression(pstate), + Vectorized(), + has_named_arguments_(false), + has_rest_argument_(false), + has_keyword_argument_(false) + { } + Arguments(const Arguments* ptr) + : Expression(ptr), + Vectorized(*ptr), + has_named_arguments_(ptr->has_named_arguments_), + has_rest_argument_(ptr->has_rest_argument_), + has_keyword_argument_(ptr->has_keyword_argument_) + { } + + virtual void set_delayed(bool delayed); + + Argument_Obj get_rest_argument(); + Argument_Obj get_keyword_argument(); + + ATTACH_AST_OPERATIONS(Arguments) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////// + // Function reference. + //////////////////////////////////////////////////// + class Function : public Value { + public: + ADD_PROPERTY(Definition_Obj, definition) + ADD_PROPERTY(bool, is_css) + public: + Function(ParserState pstate, Definition_Obj def, bool css) + : Value(pstate), definition_(def), is_css_(css) + { concrete_type(FUNCTION_VAL); } + Function(const Function* ptr) + : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) + { concrete_type(FUNCTION_VAL); } + + std::string type() const { return "function"; } + static std::string type_name() { return "function"; } + bool is_invisible() const { return true; } + + std::string name() { + if (definition_) { + return definition_->name(); + } + return ""; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Function) + ATTACH_OPERATIONS() + }; + + ////////////////// + // Function calls. + ////////////////// + class Function_Call : public PreValue { + HASH_CONSTREF(std::string, name) + HASH_PROPERTY(Arguments_Obj, arguments) + HASH_PROPERTY(Function_Obj, func) + ADD_PROPERTY(bool, via_call) + ADD_PROPERTY(void*, cookie) + size_t hash_; + public: + Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie) + : PreValue(pstate), name_(n), arguments_(args), func_(0), via_call_(false), cookie_(cookie), hash_(0) + { concrete_type(FUNCTION); } + Function_Call(ParserState pstate, std::string n, Arguments_Obj args, Function_Obj func) + : PreValue(pstate), name_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) + { concrete_type(FUNCTION); } + Function_Call(ParserState pstate, std::string n, Arguments_Obj args) + : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) + { concrete_type(FUNCTION); } + Function_Call(const Function_Call* ptr) + : PreValue(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_), + func_(ptr->func_), + via_call_(ptr->via_call_), + cookie_(ptr->cookie_), + hash_(ptr->hash_) + { concrete_type(FUNCTION); } + + bool is_css() { + if (func_) return func_->is_css(); + return false; + } + + virtual bool operator==(const Expression& rhs) const + { + try + { + Function_Call_Ptr_Const m = Cast(&rhs); + if (!(m && name() == m->name())) return false; + if (!(m && arguments()->length() == m->arguments()->length())) return false; + for (size_t i =0, L = arguments()->length(); i < L; ++i) + if (!(*(*arguments())[i] == *(*m->arguments())[i])) return false; + return true; + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(name()); + for (auto argument : arguments()->elements()) + hash_combine(hash_, argument->hash()); + } + return hash_; + } + ATTACH_AST_OPERATIONS(Function_Call) + ATTACH_OPERATIONS() + }; + + ///////////////////////// + // Function call schemas. + ///////////////////////// + class Function_Call_Schema : public Expression { + ADD_PROPERTY(String_Obj, name) + ADD_PROPERTY(Arguments_Obj, arguments) + public: + Function_Call_Schema(ParserState pstate, String_Obj n, Arguments_Obj args) + : Expression(pstate), name_(n), arguments_(args) + { concrete_type(STRING); } + Function_Call_Schema(const Function_Call_Schema* ptr) + : Expression(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_) + { concrete_type(STRING); } + ATTACH_AST_OPERATIONS(Function_Call_Schema) + ATTACH_OPERATIONS() + }; + + /////////////////////// + // Variable references. + /////////////////////// + class Variable : public PreValue { + ADD_CONSTREF(std::string, name) + public: + Variable(ParserState pstate, std::string n) + : PreValue(pstate), name_(n) + { concrete_type(VARIABLE); } + Variable(const Variable* ptr) + : PreValue(ptr), name_(ptr->name_) + { concrete_type(VARIABLE); } + + virtual bool operator==(const Expression& rhs) const + { + try + { + Variable_Ptr_Const e = Cast(&rhs); + return e && name() == e->name(); + } + catch (std::bad_cast&) + { + return false; + } + catch (...) { throw; } + } + + virtual size_t hash() + { + return std::hash()(name()); + } + + ATTACH_AST_OPERATIONS(Variable) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////// + // Numbers, percentages, dimensions, and colors. + //////////////////////////////////////////////// + class Number : public Value, public Units { + HASH_PROPERTY(double, value) + ADD_PROPERTY(bool, zero) + size_t hash_; + public: + Number(ParserState pstate, double val, std::string u = "", bool zero = true); + + Number(const Number* ptr) + : Value(ptr), + Units(ptr), + value_(ptr->value_), zero_(ptr->zero_), + hash_(ptr->hash_) + { concrete_type(NUMBER); } + + bool zero() { return zero_; } + std::string type() const { return "number"; } + static std::string type_name() { return "number"; } + + void reduce(); + void normalize(); + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + for (const auto numerator : numerators) + hash_combine(hash_, std::hash()(numerator)); + for (const auto denominator : denominators) + hash_combine(hash_, std::hash()(denominator)); + } + return hash_; + } + + virtual bool operator< (const Number& rhs) const; + virtual bool operator== (const Number& rhs) const; + virtual bool operator== (const Expression& rhs) const; + ATTACH_AST_OPERATIONS(Number) + ATTACH_OPERATIONS() + }; + + ////////// + // Colors. + ////////// + class Color : public Value { + HASH_PROPERTY(double, r) + HASH_PROPERTY(double, g) + HASH_PROPERTY(double, b) + HASH_PROPERTY(double, a) + ADD_CONSTREF(std::string, disp) + size_t hash_; + public: + Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "") + : Value(pstate), r_(r), g_(g), b_(b), a_(a), disp_(disp), + hash_(0) + { concrete_type(COLOR); } + Color(const Color* ptr) + : Value(ptr), + r_(ptr->r_), + g_(ptr->g_), + b_(ptr->b_), + a_(ptr->a_), + disp_(ptr->disp_), + hash_(ptr->hash_) + { concrete_type(COLOR); } + std::string type() const { return "color"; } + static std::string type_name() { return "color"; } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(a_); + hash_combine(hash_, std::hash()(r_)); + hash_combine(hash_, std::hash()(g_)); + hash_combine(hash_, std::hash()(b_)); + } + return hash_; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Color) + ATTACH_OPERATIONS() + }; + + ////////////////////////////// + // Errors from Sass_Values. + ////////////////////////////// + class Custom_Error : public Value { + ADD_CONSTREF(std::string, message) + public: + Custom_Error(ParserState pstate, std::string msg) + : Value(pstate), message_(msg) + { concrete_type(C_ERROR); } + Custom_Error(const Custom_Error* ptr) + : Value(ptr), message_(ptr->message_) + { concrete_type(C_ERROR); } + virtual bool operator== (const Expression& rhs) const; + ATTACH_AST_OPERATIONS(Custom_Error) + ATTACH_OPERATIONS() + }; + + ////////////////////////////// + // Warnings from Sass_Values. + ////////////////////////////// + class Custom_Warning : public Value { + ADD_CONSTREF(std::string, message) + public: + Custom_Warning(ParserState pstate, std::string msg) + : Value(pstate), message_(msg) + { concrete_type(C_WARNING); } + Custom_Warning(const Custom_Warning* ptr) + : Value(ptr), message_(ptr->message_) + { concrete_type(C_WARNING); } + virtual bool operator== (const Expression& rhs) const; + ATTACH_AST_OPERATIONS(Custom_Warning) + ATTACH_OPERATIONS() + }; + + //////////// + // Booleans. + //////////// + class Boolean : public Value { + HASH_PROPERTY(bool, value) + size_t hash_; + public: + Boolean(ParserState pstate, bool val) + : Value(pstate), value_(val), + hash_(0) + { concrete_type(BOOLEAN); } + Boolean(const Boolean* ptr) + : Value(ptr), + value_(ptr->value_), + hash_(ptr->hash_) + { concrete_type(BOOLEAN); } + virtual operator bool() { return value_; } + std::string type() const { return "bool"; } + static std::string type_name() { return "bool"; } + virtual bool is_false() { return !value_; } + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + } + return hash_; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Boolean) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for Sass string values. Includes interpolated and + // "flat" strings. + //////////////////////////////////////////////////////////////////////// + class String : public Value { + public: + String(ParserState pstate, bool delayed = false) + : Value(pstate, delayed) + { concrete_type(STRING); } + String(const String* ptr) + : Value(ptr) + { concrete_type(STRING); } + static std::string type_name() { return "string"; } + virtual ~String() = 0; + virtual void rtrim() = 0; + virtual bool operator==(const Expression& rhs) const = 0; + virtual bool operator<(const Expression& rhs) const { + return this->to_string() < rhs.to_string(); + }; + ATTACH_VIRTUAL_AST_OPERATIONS(String); + ATTACH_OPERATIONS() + }; + inline String::~String() { }; + + /////////////////////////////////////////////////////////////////////// + // Interpolated strings. Meant to be reduced to flat strings during the + // evaluation phase. + /////////////////////////////////////////////////////////////////////// + class String_Schema : public String, public Vectorized { + ADD_PROPERTY(bool, css) + size_t hash_; + public: + String_Schema(ParserState pstate, size_t size = 0, bool css = true) + : String(pstate), Vectorized(size), css_(css), hash_(0) + { concrete_type(STRING); } + String_Schema(const String_Schema* ptr) + : String(ptr), + Vectorized(*ptr), + css_(ptr->css_), + hash_(ptr->hash_) + { concrete_type(STRING); } + + std::string type() const { return "string"; } + static std::string type_name() { return "string"; } + + bool is_left_interpolant(void) const; + bool is_right_interpolant(void) const; + // void has_interpolants(bool tc) { } + bool has_interpolants() { + for (auto el : elements()) { + if (el->is_interpolant()) return true; + } + return false; + } + virtual void rtrim(); + + virtual size_t hash() + { + if (hash_ == 0) { + for (auto string : elements()) + hash_combine(hash_, string->hash()); + } + return hash_; + } + + virtual void set_delayed(bool delayed) { + is_delayed(delayed); + } + + virtual bool operator==(const Expression& rhs) const; + ATTACH_AST_OPERATIONS(String_Schema) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////// + // Flat strings -- the lowest level of raw textual data. + //////////////////////////////////////////////////////// + class String_Constant : public String { + ADD_PROPERTY(char, quote_mark) + ADD_PROPERTY(bool, can_compress_whitespace) + HASH_CONSTREF(std::string, value) + protected: + size_t hash_; + public: + String_Constant(const String_Constant* ptr) + : String(ptr), + quote_mark_(ptr->quote_mark_), + can_compress_whitespace_(ptr->can_compress_whitespace_), + value_(ptr->value_), + hash_(ptr->hash_) + { } + String_Constant(ParserState pstate, std::string val, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0) + { } + String_Constant(ParserState pstate, const char* beg, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0) + { } + String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) + { } + String_Constant(ParserState pstate, const Token& tok, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) + { } + std::string type() const { return "string"; } + static std::string type_name() { return "string"; } + virtual bool is_invisible() const; + virtual void rtrim(); + + virtual size_t hash() + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + } + return hash_; + } + + virtual bool operator==(const Expression& rhs) const; + virtual std::string inspect() const; // quotes are forced on inspection + + // static char auto_quote() { return '*'; } + static char double_quote() { return '"'; } + static char single_quote() { return '\''; } + + ATTACH_AST_OPERATIONS(String_Constant) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////// + // Possibly quoted string (unquote on instantiation) + //////////////////////////////////////////////////////// + class String_Quoted : public String_Constant { + public: + String_Quoted(ParserState pstate, std::string val, char q = 0, + bool keep_utf8_escapes = false, bool skip_unquoting = false, + bool strict_unquoting = true, bool css = true) + : String_Constant(pstate, val, css) + { + if (skip_unquoting == false) { + value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); + } + if (q && quote_mark_) quote_mark_ = q; + } + String_Quoted(const String_Quoted* ptr) + : String_Constant(ptr) + { } + virtual bool operator==(const Expression& rhs) const; + virtual std::string inspect() const; // quotes are forced on inspection + ATTACH_AST_OPERATIONS(String_Quoted) + ATTACH_OPERATIONS() + }; + + ///////////////// + // Media queries. + ///////////////// + class Media_Query : public Expression, + public Vectorized { + ADD_PROPERTY(String_Obj, media_type) + ADD_PROPERTY(bool, is_negated) + ADD_PROPERTY(bool, is_restricted) + public: + Media_Query(ParserState pstate, + String_Obj t = 0, size_t s = 0, bool n = false, bool r = false) + : Expression(pstate), Vectorized(s), + media_type_(t), is_negated_(n), is_restricted_(r) + { } + Media_Query(const Media_Query* ptr) + : Expression(ptr), + Vectorized(*ptr), + media_type_(ptr->media_type_), + is_negated_(ptr->is_negated_), + is_restricted_(ptr->is_restricted_) + { } + ATTACH_AST_OPERATIONS(Media_Query) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////// + // Media expressions (for use inside media queries). + //////////////////////////////////////////////////// + class Media_Query_Expression : public Expression { + ADD_PROPERTY(Expression_Obj, feature) + ADD_PROPERTY(Expression_Obj, value) + ADD_PROPERTY(bool, is_interpolated) + public: + Media_Query_Expression(ParserState pstate, + Expression_Obj f, Expression_Obj v, bool i = false) + : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) + { } + Media_Query_Expression(const Media_Query_Expression* ptr) + : Expression(ptr), + feature_(ptr->feature_), + value_(ptr->value_), + is_interpolated_(ptr->is_interpolated_) + { } + ATTACH_AST_OPERATIONS(Media_Query_Expression) + ATTACH_OPERATIONS() + }; + + //////////////////// + // `@supports` rule. + //////////////////// + class Supports_Block : public Has_Block { + ADD_PROPERTY(Supports_Condition_Obj, condition) + public: + Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block = 0) + : Has_Block(pstate, block), condition_(condition) + { statement_type(SUPPORTS); } + Supports_Block(const Supports_Block* ptr) + : Has_Block(ptr), condition_(ptr->condition_) + { statement_type(SUPPORTS); } + bool bubbles() { return true; } + ATTACH_AST_OPERATIONS(Supports_Block) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////////////////////// + // The abstract superclass of all Supports conditions. + ////////////////////////////////////////////////////// + class Supports_Condition : public Expression { + public: + Supports_Condition(ParserState pstate) + : Expression(pstate) + { } + Supports_Condition(const Supports_Condition* ptr) + : Expression(ptr) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Condition) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////// + // An operator condition (e.g. `CONDITION1 and CONDITION2`). + //////////////////////////////////////////////////////////// + class Supports_Operator : public Supports_Condition { + public: + enum Operand { AND, OR }; + private: + ADD_PROPERTY(Supports_Condition_Obj, left); + ADD_PROPERTY(Supports_Condition_Obj, right); + ADD_PROPERTY(Operand, operand); + public: + Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o) + : Supports_Condition(pstate), left_(l), right_(r), operand_(o) + { } + Supports_Operator(const Supports_Operator* ptr) + : Supports_Condition(ptr), + left_(ptr->left_), + right_(ptr->right_), + operand_(ptr->operand_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const; + ATTACH_AST_OPERATIONS(Supports_Operator) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////////// + // A negation condition (`not CONDITION`). + ////////////////////////////////////////// + class Supports_Negation : public Supports_Condition { + private: + ADD_PROPERTY(Supports_Condition_Obj, condition); + public: + Supports_Negation(ParserState pstate, Supports_Condition_Obj c) + : Supports_Condition(pstate), condition_(c) + { } + Supports_Negation(const Supports_Negation* ptr) + : Supports_Condition(ptr), condition_(ptr->condition_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const; + ATTACH_AST_OPERATIONS(Supports_Negation) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////// + // A declaration condition (e.g. `(feature: value)`). + ///////////////////////////////////////////////////// + class Supports_Declaration : public Supports_Condition { + private: + ADD_PROPERTY(Expression_Obj, feature); + ADD_PROPERTY(Expression_Obj, value); + public: + Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v) + : Supports_Condition(pstate), feature_(f), value_(v) + { } + Supports_Declaration(const Supports_Declaration* ptr) + : Supports_Condition(ptr), + feature_(ptr->feature_), + value_(ptr->value_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Declaration) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////// + // An interpolation condition (e.g. `#{$var}`). + /////////////////////////////////////////////// + class Supports_Interpolation : public Supports_Condition { + private: + ADD_PROPERTY(Expression_Obj, value); + public: + Supports_Interpolation(ParserState pstate, Expression_Obj v) + : Supports_Condition(pstate), value_(v) + { } + Supports_Interpolation(const Supports_Interpolation* ptr) + : Supports_Condition(ptr), + value_(ptr->value_) + { } + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Interpolation) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////// + // At root expressions (for use inside @at-root). + ///////////////////////////////////////////////// + class At_Root_Query : public Expression { + private: + ADD_PROPERTY(Expression_Obj, feature) + ADD_PROPERTY(Expression_Obj, value) + public: + At_Root_Query(ParserState pstate, Expression_Obj f = 0, Expression_Obj v = 0, bool i = false) + : Expression(pstate), feature_(f), value_(v) + { } + At_Root_Query(const At_Root_Query* ptr) + : Expression(ptr), + feature_(ptr->feature_), + value_(ptr->value_) + { } + bool exclude(std::string str); + ATTACH_AST_OPERATIONS(At_Root_Query) + ATTACH_OPERATIONS() + }; + + /////////// + // At-root. + /////////// + class At_Root_Block : public Has_Block { + ADD_PROPERTY(At_Root_Query_Obj, expression) + public: + At_Root_Block(ParserState pstate, Block_Obj b = 0, At_Root_Query_Obj e = 0) + : Has_Block(pstate, b), expression_(e) + { statement_type(ATROOT); } + At_Root_Block(const At_Root_Block* ptr) + : Has_Block(ptr), expression_(ptr->expression_) + { statement_type(ATROOT); } + bool bubbles() { return true; } + bool exclude_node(Statement_Obj s) { + if (expression() == 0) + { + return s->statement_type() == Statement::RULESET; + } + + if (s->statement_type() == Statement::DIRECTIVE) + { + if (Directive_Obj dir = Cast(s)) + { + std::string keyword(dir->keyword()); + if (keyword.length() > 0) keyword.erase(0, 1); + return expression()->exclude(keyword); + } + } + if (s->statement_type() == Statement::MEDIA) + { + return expression()->exclude("media"); + } + if (s->statement_type() == Statement::RULESET) + { + return expression()->exclude("rule"); + } + if (s->statement_type() == Statement::SUPPORTS) + { + return expression()->exclude("supports"); + } + if (Directive_Obj dir = Cast(s)) + { + if (dir->is_keyframes()) return expression()->exclude("keyframes"); + } + return false; + } + ATTACH_AST_OPERATIONS(At_Root_Block) + ATTACH_OPERATIONS() + }; + + ////////////////// + // The null value. + ////////////////// + class Null : public Value { + public: + Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); } + Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); } + std::string type() const { return "null"; } + static std::string type_name() { return "null"; } + bool is_invisible() const { return true; } + operator bool() { return false; } + bool is_false() { return true; } + + virtual size_t hash() + { + return -1; + } + + virtual bool operator== (const Expression& rhs) const; + + ATTACH_AST_OPERATIONS(Null) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////// + // Thunks for delayed evaluation. + ///////////////////////////////// + class Thunk : public Expression { + ADD_PROPERTY(Expression_Obj, expression) + ADD_PROPERTY(Env*, environment) + public: + Thunk(ParserState pstate, Expression_Obj exp, Env* env = 0) + : Expression(pstate), expression_(exp), environment_(env) + { } + }; + + ///////////////////////////////////////////////////////// + // Individual parameter objects for mixins and functions. + ///////////////////////////////////////////////////////// + class Parameter : public AST_Node { + ADD_CONSTREF(std::string, name) + ADD_PROPERTY(Expression_Obj, default_value) + ADD_PROPERTY(bool, is_rest_parameter) + public: + Parameter(ParserState pstate, + std::string n, Expression_Obj def = 0, bool rest = false) + : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) + { + // tried to come up with a spec test for this, but it does no longer + // get past the parser (it error out earlier). A spec test was added! + // if (default_value_ && is_rest_parameter_) { + // error("variable-length parameter may not have a default value", pstate_); + // } + } + Parameter(const Parameter* ptr) + : AST_Node(ptr), + name_(ptr->name_), + default_value_(ptr->default_value_), + is_rest_parameter_(ptr->is_rest_parameter_) + { + // tried to come up with a spec test for this, but it does no longer + // get past the parser (it error out earlier). A spec test was added! + // if (default_value_ && is_rest_parameter_) { + // error("variable-length parameter may not have a default value", pstate_); + // } + } + ATTACH_AST_OPERATIONS(Parameter) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////////// + // Parameter lists -- in their own class to facilitate context-sensitive + // error checking (e.g., ensuring that all optional parameters follow all + // required parameters). + ///////////////////////////////////////////////////////////////////////// + class Parameters : public AST_Node, public Vectorized { + ADD_PROPERTY(bool, has_optional_parameters) + ADD_PROPERTY(bool, has_rest_parameter) + protected: + void adjust_after_pushing(Parameter_Obj p) + { + if (p->default_value()) { + if (has_rest_parameter()) { + coreError("optional parameters may not be combined with variable-length parameters", p->pstate()); + } + has_optional_parameters(true); + } + else if (p->is_rest_parameter()) { + if (has_rest_parameter()) { + coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate()); + } + has_rest_parameter(true); + } + else { + if (has_rest_parameter()) { + coreError("required parameters must precede variable-length parameters", p->pstate()); + } + if (has_optional_parameters()) { + coreError("required parameters must precede optional parameters", p->pstate()); + } + } + } + public: + Parameters(ParserState pstate) + : AST_Node(pstate), + Vectorized(), + has_optional_parameters_(false), + has_rest_parameter_(false) + { } + Parameters(const Parameters* ptr) + : AST_Node(ptr), + Vectorized(*ptr), + has_optional_parameters_(ptr->has_optional_parameters_), + has_rest_parameter_(ptr->has_rest_parameter_) + { } + ATTACH_AST_OPERATIONS(Parameters) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////// + // Abstract base class for CSS selectors. + ///////////////////////////////////////// + class Selector : public Expression { + // ADD_PROPERTY(bool, has_reference) + // line break before list separator + ADD_PROPERTY(bool, has_line_feed) + // line break after list separator + ADD_PROPERTY(bool, has_line_break) + // maybe we have optional flag + ADD_PROPERTY(bool, is_optional) + // parent block pointers + + // must not be a reference counted object + // otherwise we create circular references + ADD_PROPERTY(Media_Block_Ptr, media_block) + protected: + size_t hash_; + public: + Selector(ParserState pstate) + : Expression(pstate), + has_line_feed_(false), + has_line_break_(false), + is_optional_(false), + media_block_(0), + hash_(0) + { concrete_type(SELECTOR); } + Selector(const Selector* ptr) + : Expression(ptr), + // has_reference_(ptr->has_reference_), + has_line_feed_(ptr->has_line_feed_), + has_line_break_(ptr->has_line_break_), + is_optional_(ptr->is_optional_), + media_block_(ptr->media_block_), + hash_(ptr->hash_) + { concrete_type(SELECTOR); } + virtual ~Selector() = 0; + virtual size_t hash() = 0; + virtual unsigned long specificity() const = 0; + virtual void set_media_block(Media_Block_Ptr mb) { + media_block(mb); + } + virtual bool has_parent_ref() const { + return false; + } + virtual bool has_real_parent_ref() const { + return false; + } + // dispatch to correct handlers + virtual bool operator<(const Selector& rhs) const = 0; + virtual bool operator==(const Selector& rhs) const = 0; + ATTACH_VIRTUAL_AST_OPERATIONS(Selector); + }; + inline Selector::~Selector() { } + + ///////////////////////////////////////////////////////////////////////// + // Interpolated selectors -- the interpolated String will be expanded and + // re-parsed into a normal selector class. + ///////////////////////////////////////////////////////////////////////// + class Selector_Schema : public AST_Node { + ADD_PROPERTY(String_Obj, contents) + ADD_PROPERTY(bool, connect_parent); + // must not be a reference counted object + // otherwise we create circular references + ADD_PROPERTY(Media_Block_Ptr, media_block) + // store computed hash + size_t hash_; + public: + Selector_Schema(ParserState pstate, String_Obj c) + : AST_Node(pstate), + contents_(c), + connect_parent_(true), + media_block_(NULL), + hash_(0) + { } + Selector_Schema(const Selector_Schema* ptr) + : AST_Node(ptr), + contents_(ptr->contents_), + connect_parent_(ptr->connect_parent_), + media_block_(ptr->media_block_), + hash_(ptr->hash_) + { } + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + // selector schema is not yet a final selector, so we do not + // have a specificity for it yet. We need to + virtual unsigned long specificity() const { return 0; } + virtual size_t hash() { + if (hash_ == 0) { + hash_combine(hash_, contents_->hash()); + } + return hash_; + } + ATTACH_AST_OPERATIONS(Selector_Schema) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////// + // Abstract base class for simple selectors. + //////////////////////////////////////////// + class Simple_Selector : public Selector { + ADD_CONSTREF(std::string, ns) + ADD_CONSTREF(std::string, name) + ADD_PROPERTY(Simple_Type, simple_type) + ADD_PROPERTY(bool, has_ns) + public: + Simple_Selector(ParserState pstate, std::string n = "") + : Selector(pstate), ns_(""), name_(n), has_ns_(false) + { + simple_type(SIMPLE); + size_t pos = n.find('|'); + // found some namespace + if (pos != std::string::npos) { + has_ns_ = true; + ns_ = n.substr(0, pos); + name_ = n.substr(pos + 1); + } + } + Simple_Selector(const Simple_Selector* ptr) + : Selector(ptr), + ns_(ptr->ns_), + name_(ptr->name_), + has_ns_(ptr->has_ns_) + { simple_type(SIMPLE); } + virtual std::string ns_name() const + { + std::string name(""); + if (has_ns_) + name += ns_ + "|"; + return name + name_; + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, std::hash()(SELECTOR)); + hash_combine(hash_, std::hash()(ns())); + hash_combine(hash_, std::hash()(name())); + } + return hash_; + } + // namespace compare functions + bool is_ns_eq(const Simple_Selector& r) const; + // namespace query functions + bool is_universal_ns() const + { + return has_ns_ && ns_ == "*"; + } + bool has_universal_ns() const + { + return !has_ns_ || ns_ == "*"; + } + bool is_empty_ns() const + { + return !has_ns_ || ns_ == ""; + } + bool has_empty_ns() const + { + return has_ns_ && ns_ == ""; + } + bool has_qualified_ns() const + { + return has_ns_ && ns_ != "" && ns_ != "*"; + } + // name query functions + bool is_universal() const + { + return name_ == "*"; + } + + virtual bool has_placeholder() { + return false; + } + + virtual ~Simple_Selector() = 0; + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); + virtual bool has_parent_ref() const { return false; }; + virtual bool has_real_parent_ref() const { return false; }; + virtual bool is_pseudo_element() const { return false; } + + virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; } + + virtual bool operator==(const Selector& rhs) const; + virtual bool operator==(const Simple_Selector& rhs) const; + inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); } + + bool operator<(const Selector& rhs) const; + bool operator<(const Simple_Selector& rhs) const; + // default implementation should work for most of the simple selectors (otherwise overload) + ATTACH_VIRTUAL_AST_OPERATIONS(Simple_Selector); + ATTACH_OPERATIONS(); + }; + inline Simple_Selector::~Simple_Selector() { } + + + ////////////////////////////////// + // The Parent Selector Expression. + ////////////////////////////////// + // parent selectors can occur in selectors but also + // inside strings in declarations (Compound_Selector). + // only one simple parent selector means the first case. + class Parent_Selector : public Simple_Selector { + ADD_PROPERTY(bool, real) + public: + Parent_Selector(ParserState pstate, bool r = true) + : Simple_Selector(pstate, "&"), real_(r) + { /* has_reference(true); */ } + Parent_Selector(const Parent_Selector* ptr) + : Simple_Selector(ptr), real_(ptr->real_) + { /* has_reference(true); */ } + bool is_real_parent_ref() const { return real(); }; + virtual bool has_parent_ref() const { return true; }; + virtual bool has_real_parent_ref() const { return is_real_parent_ref(); }; + virtual unsigned long specificity() const + { + return 0; + } + std::string type() const { return "selector"; } + static std::string type_name() { return "selector"; } + ATTACH_AST_OPERATIONS(Parent_Selector) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////////// + // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. + ///////////////////////////////////////////////////////////////////////// + class Placeholder_Selector : public Simple_Selector { + public: + Placeholder_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Placeholder_Selector(const Placeholder_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual unsigned long specificity() const + { + return Constants::Specificity_Base; + } + virtual bool has_placeholder() { + return true; + } + virtual ~Placeholder_Selector() {}; + ATTACH_AST_OPERATIONS(Placeholder_Selector) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////////////////////////// + // Element selectors (and the universal selector) -- e.g., div, span, *. + ///////////////////////////////////////////////////////////////////// + class Element_Selector : public Simple_Selector { + public: + Element_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Element_Selector(const Element_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual unsigned long specificity() const + { + if (name() == "*") return 0; + else return Constants::Specificity_Element; + } + virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr); + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Element_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Element_Selector& rhs) const; + ATTACH_AST_OPERATIONS(Element_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////// + // Class selectors -- i.e., .foo. + //////////////////////////////////////////////// + class Class_Selector : public Simple_Selector { + public: + Class_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Class_Selector(const Class_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual unsigned long specificity() const + { + return Constants::Specificity_Class; + } + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); + ATTACH_AST_OPERATIONS(Class_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////// + // ID selectors -- i.e., #foo. + //////////////////////////////////////////////// + class Id_Selector : public Simple_Selector { + public: + Id_Selector(ParserState pstate, std::string n) + : Simple_Selector(pstate, n) + { } + Id_Selector(const Id_Selector* ptr) + : Simple_Selector(ptr) + { } + virtual unsigned long specificity() const + { + return Constants::Specificity_ID; + } + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); + ATTACH_AST_OPERATIONS(Id_Selector) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////////////////////// + // Attribute selectors -- e.g., [src*=".jpg"], etc. + /////////////////////////////////////////////////// + class Attribute_Selector : public Simple_Selector { + ADD_CONSTREF(std::string, matcher) + // this cannot be changed to obj atm!!!!!!????!!!!!!! + ADD_PROPERTY(String_Obj, value) // might be interpolated + ADD_PROPERTY(char, modifier); + public: + Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0) + : Simple_Selector(pstate, n), matcher_(m), value_(v), modifier_(o) + { simple_type(ATTR_SEL); } + Attribute_Selector(const Attribute_Selector* ptr) + : Simple_Selector(ptr), + matcher_(ptr->matcher_), + value_(ptr->value_), + modifier_(ptr->modifier_) + { simple_type(ATTR_SEL); } + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + hash_combine(hash_, std::hash()(matcher())); + if (value_) hash_combine(hash_, value_->hash()); + } + return hash_; + } + virtual unsigned long specificity() const + { + return Constants::Specificity_Attr; + } + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Attribute_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Attribute_Selector& rhs) const; + ATTACH_AST_OPERATIONS(Attribute_Selector) + ATTACH_OPERATIONS() + }; + + ////////////////////////////////////////////////////////////////// + // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. + ////////////////////////////////////////////////////////////////// + /* '::' starts a pseudo-element, ':' a pseudo-class */ + /* Except :first-line, :first-letter, :before and :after */ + /* Note that pseudo-elements are restricted to one per selector */ + /* and occur only in the last simple_selector_sequence. */ + inline bool is_pseudo_class_element(const std::string& name) + { + return name == ":before" || + name == ":after" || + name == ":first-line" || + name == ":first-letter"; + } + + // Pseudo Selector cannot have any namespace? + class Pseudo_Selector : public Simple_Selector { + ADD_PROPERTY(String_Obj, expression) + public: + Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr = 0) + : Simple_Selector(pstate, n), expression_(expr) + { simple_type(PSEUDO_SEL); } + Pseudo_Selector(const Pseudo_Selector* ptr) + : Simple_Selector(ptr), expression_(ptr->expression_) + { simple_type(PSEUDO_SEL); } + + // A pseudo-element is made of two colons (::) followed by the name. + // The `::` notation is introduced by the current document in order to + // establish a discrimination between pseudo-classes and pseudo-elements. + // For compatibility with existing style sheets, user agents must also + // accept the previous one-colon notation for pseudo-elements introduced + // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and + // :after). This compatibility is not allowed for the new pseudo-elements + // introduced in this specification. + virtual bool is_pseudo_element() const + { + return (name_[0] == ':' && name_[1] == ':') + || is_pseudo_class_element(name_); + } + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, Simple_Selector::hash()); + if (expression_) hash_combine(hash_, expression_->hash()); + } + return hash_; + } + virtual unsigned long specificity() const + { + if (is_pseudo_element()) + return Constants::Specificity_Element; + return Constants::Specificity_Pseudo; + } + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Pseudo_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Pseudo_Selector& rhs) const; + virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); + ATTACH_AST_OPERATIONS(Pseudo_Selector) + ATTACH_OPERATIONS() + }; + + ///////////////////////////////////////////////// + // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir) + ///////////////////////////////////////////////// + class Wrapped_Selector : public Simple_Selector { + ADD_PROPERTY(Selector_List_Obj, selector) + public: + Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel) + : Simple_Selector(pstate, n), selector_(sel) + { simple_type(WRAPPED_SEL); } + Wrapped_Selector(const Wrapped_Selector* ptr) + : Simple_Selector(ptr), selector_(ptr->selector_) + { simple_type(WRAPPED_SEL); } + virtual bool is_superselector_of(Wrapped_Selector_Obj sub); + // Selectors inside the negation pseudo-class are counted like any + // other, but the negation itself does not count as a pseudo-class. + virtual size_t hash(); + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + virtual unsigned long specificity() const; + virtual bool find ( bool (*f)(AST_Node_Obj) ); + virtual bool operator==(const Simple_Selector& rhs) const; + virtual bool operator==(const Wrapped_Selector& rhs) const; + virtual bool operator<(const Simple_Selector& rhs) const; + virtual bool operator<(const Wrapped_Selector& rhs) const; + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Wrapped_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Simple selector sequences. Maintains flags indicating whether it contains + // any parent references or placeholders, to simplify expansion. + //////////////////////////////////////////////////////////////////////////// + class Compound_Selector : public Selector, public Vectorized { + private: + ComplexSelectorSet sources_; + ADD_PROPERTY(bool, extended); + ADD_PROPERTY(bool, has_parent_reference); + protected: + void adjust_after_pushing(Simple_Selector_Obj s) + { + // if (s->has_reference()) has_reference(true); + // if (s->has_placeholder()) has_placeholder(true); + } + public: + Compound_Selector(ParserState pstate, size_t s = 0) + : Selector(pstate), + Vectorized(s), + extended_(false), + has_parent_reference_(false) + { } + Compound_Selector(const Compound_Selector* ptr) + : Selector(ptr), + Vectorized(*ptr), + extended_(ptr->extended_), + has_parent_reference_(ptr->has_parent_reference_) + { } + bool contains_placeholder() { + for (size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->has_placeholder()) return true; + } + return false; + }; + + void append(Simple_Selector_Ptr element); + + bool is_universal() const + { + return length() == 1 && (*this)[0]->is_universal(); + } + + Complex_Selector_Obj to_complex(); + Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs); + // virtual Placeholder_Selector_Ptr find_placeholder(); + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + Simple_Selector_Ptr base() const { + if (length() == 0) return 0; + // ToDo: why is this needed? + if (Cast((*this)[0])) + return (*this)[0]; + return 0; + } + virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = ""); + virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapped = ""); + virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapped = ""); + virtual size_t hash() + { + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, std::hash()(SELECTOR)); + if (length()) hash_combine(Selector::hash_, Vectorized::hash()); + } + return Selector::hash_; + } + virtual unsigned long specificity() const + { + int sum = 0; + for (size_t i = 0, L = length(); i < L; ++i) + { sum += (*this)[i]->specificity(); } + return sum; + } + + virtual bool has_placeholder() + { + if (length() == 0) return false; + if (Simple_Selector_Obj ss = elements().front()) { + if (ss->has_placeholder()) return true; + } + return false; + } + + bool is_empty_reference() + { + return length() == 1 && + Cast((*this)[0]); + } + + virtual bool find ( bool (*f)(AST_Node_Obj) ); + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + virtual bool operator<(const Compound_Selector& rhs) const; + virtual bool operator==(const Compound_Selector& rhs) const; + inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); } + + ComplexSelectorSet& sources() { return sources_; } + void clearSources() { sources_.clear(); } + void mergeSources(ComplexSelectorSet& sources); + + Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs); + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Compound_Selector) + ATTACH_OPERATIONS() + }; + + //////////////////////////////////////////////////////////////////////////// + // General selectors -- i.e., simple sequences combined with one of the four + // CSS selector combinators (">", "+", "~", and whitespace). Essentially a + // linked list. + //////////////////////////////////////////////////////////////////////////// + class Complex_Selector : public Selector { + public: + enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE }; + private: + HASH_CONSTREF(Combinator, combinator) + HASH_PROPERTY(Compound_Selector_Obj, head) + HASH_PROPERTY(Complex_Selector_Obj, tail) + HASH_PROPERTY(String_Obj, reference); + public: + bool contains_placeholder() { + if (head() && head()->contains_placeholder()) return true; + if (tail() && tail()->contains_placeholder()) return true; + return false; + }; + Complex_Selector(ParserState pstate, + Combinator c = ANCESTOR_OF, + Compound_Selector_Obj h = 0, + Complex_Selector_Obj t = 0, + String_Obj r = 0) + : Selector(pstate), + combinator_(c), + head_(h), tail_(t), + reference_(r) + {} + Complex_Selector(const Complex_Selector* ptr) + : Selector(ptr), + combinator_(ptr->combinator_), + head_(ptr->head_), tail_(ptr->tail_), + reference_(ptr->reference_) + {}; + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + + Complex_Selector_Obj skip_empty_reference() + { + if ((!head_ || !head_->length() || head_->is_empty_reference()) && + combinator() == Combinator::ANCESTOR_OF) + { + if (!tail_) return 0; + tail_->has_line_feed_ = this->has_line_feed_; + // tail_->has_line_break_ = this->has_line_break_; + return tail_->skip_empty_reference(); + } + return this; + } + + // can still have a tail + bool is_empty_ancestor() const + { + return (!head() || head()->length() == 0) && + combinator() == Combinator::ANCESTOR_OF; + } + + Selector_List_Ptr tails(Selector_List_Ptr tails); + + // front returns the first real tail + // skips over parent and empty ones + Complex_Selector_Obj first(); + // last returns the last real tail + Complex_Selector_Obj last(); + + // some shortcuts that should be removed + Complex_Selector_Obj innermost() { return last(); }; + + size_t length() const; + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); + virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); + Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs); + Combinator clear_innermost(); + void append(Complex_Selector_Obj, Backtraces& traces); + void set_innermost(Complex_Selector_Obj, Combinator); + virtual size_t hash() + { + if (hash_ == 0) { + hash_combine(hash_, std::hash()(SELECTOR)); + hash_combine(hash_, std::hash()(combinator_)); + if (head_) hash_combine(hash_, head_->hash()); + if (tail_) hash_combine(hash_, tail_->hash()); + } + return hash_; + } + virtual unsigned long specificity() const + { + int sum = 0; + if (head()) sum += head()->specificity(); + if (tail()) sum += tail()->specificity(); + return sum; + } + virtual void set_media_block(Media_Block_Ptr mb) { + media_block(mb); + if (tail_) tail_->set_media_block(mb); + if (head_) head_->set_media_block(mb); + } + virtual bool has_placeholder() { + if (head_ && head_->has_placeholder()) return true; + if (tail_ && tail_->has_placeholder()) return true; + return false; + } + virtual bool find ( bool (*f)(AST_Node_Obj) ); + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + virtual bool operator<(const Complex_Selector& rhs) const; + virtual bool operator==(const Complex_Selector& rhs) const; + inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); } + const ComplexSelectorSet sources() + { + //s = Set.new + //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} + //s + + ComplexSelectorSet srcs; + + Compound_Selector_Obj pHead = head(); + Complex_Selector_Obj pTail = tail(); + + if (pHead) { + const ComplexSelectorSet& headSources = pHead->sources(); + srcs.insert(headSources.begin(), headSources.end()); + } + + if (pTail) { + const ComplexSelectorSet& tailSources = pTail->sources(); + srcs.insert(tailSources.begin(), tailSources.end()); + } + + return srcs; + } + void addSources(ComplexSelectorSet& sources) { + // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} + Complex_Selector_Ptr pIter = this; + while (pIter) { + Compound_Selector_Ptr pHead = pIter->head(); + + if (pHead) { + pHead->mergeSources(sources); + } + + pIter = pIter->tail(); + } + } + void clearSources() { + Complex_Selector_Ptr pIter = this; + while (pIter) { + Compound_Selector_Ptr pHead = pIter->head(); + + if (pHead) { + pHead->clearSources(); + } + + pIter = pIter->tail(); + } + } + + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Complex_Selector) + ATTACH_OPERATIONS() + }; + + /////////////////////////////////// + // Comma-separated selector groups. + /////////////////////////////////// + class Selector_List : public Selector, public Vectorized { + ADD_PROPERTY(Selector_Schema_Obj, schema) + ADD_CONSTREF(std::vector, wspace) + protected: + void adjust_after_pushing(Complex_Selector_Obj c); + public: + Selector_List(ParserState pstate, size_t s = 0) + : Selector(pstate), + Vectorized(s), + schema_(NULL), + wspace_(0) + { } + Selector_List(const Selector_List* ptr) + : Selector(ptr), + Vectorized(*ptr), + schema_(ptr->schema_), + wspace_(ptr->wspace_) + { } + std::string type() const { return "list"; } + // remove parent selector references + // basically unwraps parsed selectors + virtual bool has_parent_ref() const; + virtual bool has_real_parent_ref() const; + void remove_parent_selectors(); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); + virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); + virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); + Selector_List_Ptr unify_with(Selector_List_Ptr); + void populate_extends(Selector_List_Obj, Subset_Map&); + Selector_List_Obj eval(Eval& eval); + virtual size_t hash() + { + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, std::hash()(SELECTOR)); + hash_combine(Selector::hash_, Vectorized::hash()); + } + return Selector::hash_; + } + virtual unsigned long specificity() const + { + unsigned long sum = 0; + unsigned long specificity; + for (size_t i = 0, L = length(); i < L; ++i) + { + specificity = (*this)[i]->specificity(); + if (sum < specificity) sum = specificity; + } + return sum; + } + virtual void set_media_block(Media_Block_Ptr mb) { + media_block(mb); + for (Complex_Selector_Obj cs : elements()) { + cs->set_media_block(mb); + } + } + virtual bool has_placeholder() { + for (Complex_Selector_Obj cs : elements()) { + if (cs->has_placeholder()) return true; + } + return false; + } + virtual bool find ( bool (*f)(AST_Node_Obj) ); + virtual bool operator<(const Selector& rhs) const; + virtual bool operator==(const Selector& rhs) const; + virtual bool operator<(const Selector_List& rhs) const; + virtual bool operator==(const Selector_List& rhs) const; + // Selector Lists can be compared to comma lists + virtual bool operator==(const Expression& rhs) const; + virtual void cloneChildren(); + ATTACH_AST_OPERATIONS(Selector_List) + ATTACH_OPERATIONS() + }; + + // compare function for sorting and probably other other uses + struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } }; + struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } }; + struct cmp_simple_selector { inline bool operator() (const Simple_Selector_Obj l, const Simple_Selector_Obj r) { return (*l < *r); } }; + +} + +#ifdef __clang__ + +#pragma clang diagnostic pop + +#endif + +#endif diff --git a/src/ast_def_macros.hpp b/src/ast_def_macros.hpp new file mode 100644 index 000000000..b3a7f8d16 --- /dev/null +++ b/src/ast_def_macros.hpp @@ -0,0 +1,80 @@ +#ifndef SASS_AST_DEF_MACROS_H +#define SASS_AST_DEF_MACROS_H + +// Helper class to switch a flag and revert once we go out of scope +template +class LocalOption { + private: + T* var; // pointer to original variable + T orig; // copy of the original option + public: + LocalOption(T& var) + { + this->var = &var; + this->orig = var; + } + LocalOption(T& var, T orig) + { + this->var = &var; + this->orig = var; + *(this->var) = orig; + } + void reset() + { + *(this->var) = this->orig; + } + ~LocalOption() { + *(this->var) = this->orig; + } +}; + +#define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) +#define LOCAL_COUNT(name,opt) LocalOption cnt_##name(name, opt) + +#define NESTING_GUARD(name) \ + LocalOption cnt_##name(name, name + 1); \ + if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ + +#define ATTACH_OPERATIONS()\ +virtual void perform(Operation* op) { (*op)(this); }\ +virtual AST_Node_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual Statement_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual Expression_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual Selector_Ptr perform(Operation* op) { return (*op)(this); }\ +virtual std::string perform(Operation* op) { return (*op)(this); }\ +virtual union Sass_Value* perform(Operation* op) { return (*op)(this); }\ +virtual Value_Ptr perform(Operation* op) { return (*op)(this); } + +#define ADD_PROPERTY(type, name)\ +protected:\ + type name##_;\ +public:\ + type name() const { return name##_; }\ + type name(type name##__) { return name##_ = name##__; }\ +private: + +#define HASH_PROPERTY(type, name)\ +protected:\ + type name##_;\ +public:\ + type name() const { return name##_; }\ + type name(type name##__) { hash_ = 0; return name##_ = name##__; }\ +private: + +#define ADD_CONSTREF(type, name) \ +protected: \ + type name##_; \ +public: \ + const type& name() const { return name##_; } \ + void name(type name##__) { name##_ = name##__; } \ +private: + +#define HASH_CONSTREF(type, name) \ +protected: \ + type name##_; \ +public: \ + const type& name() const { return name##_; } \ + void name(type name##__) { hash_ = 0; name##_ = name##__; } \ +private: + +#endif diff --git a/src/ast_fwd_decl.cpp b/src/ast_fwd_decl.cpp new file mode 100644 index 000000000..c9c76727a --- /dev/null +++ b/src/ast_fwd_decl.cpp @@ -0,0 +1,29 @@ +#include "ast.hpp" + +namespace Sass { + + #define IMPLEMENT_BASE_CAST(T) \ + template<> \ + T* Cast(AST_Node* ptr) { \ + return dynamic_cast(ptr); \ + }; \ + \ + template<> \ + const T* Cast(const AST_Node* ptr) { \ + return dynamic_cast(ptr); \ + }; \ + + IMPLEMENT_BASE_CAST(AST_Node) + IMPLEMENT_BASE_CAST(Expression) + IMPLEMENT_BASE_CAST(Statement) + IMPLEMENT_BASE_CAST(Has_Block) + IMPLEMENT_BASE_CAST(PreValue) + IMPLEMENT_BASE_CAST(Value) + IMPLEMENT_BASE_CAST(List) + IMPLEMENT_BASE_CAST(String) + IMPLEMENT_BASE_CAST(String_Constant) + IMPLEMENT_BASE_CAST(Supports_Condition) + IMPLEMENT_BASE_CAST(Selector) + IMPLEMENT_BASE_CAST(Simple_Selector) + +} diff --git a/src/ast_fwd_decl.hpp b/src/ast_fwd_decl.hpp new file mode 100644 index 000000000..5145a092b --- /dev/null +++ b/src/ast_fwd_decl.hpp @@ -0,0 +1,463 @@ +#ifndef SASS_AST_FWD_DECL_H +#define SASS_AST_FWD_DECL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "memory/SharedPtr.hpp" +#include "sass/functions.h" + +///////////////////////////////////////////// +// Forward declarations for the AST visitors. +///////////////////////////////////////////// +namespace Sass { + + class AST_Node; + typedef AST_Node* AST_Node_Ptr; + typedef AST_Node const* AST_Node_Ptr_Const; + + class Has_Block; + typedef Has_Block* Has_Block_Ptr; + typedef Has_Block const* Has_Block_Ptr_Const; + + class Simple_Selector; + typedef Simple_Selector* Simple_Selector_Ptr; + typedef Simple_Selector const* Simple_Selector_Ptr_Const; + + class PreValue; + typedef PreValue* PreValue_Ptr; + typedef PreValue const* PreValue_Ptr_Const; + class Thunk; + typedef Thunk* Thunk_Ptr; + typedef Thunk const* Thunk_Ptr_Const; + class Block; + typedef Block* Block_Ptr; + typedef Block const* Block_Ptr_Const; + class Expression; + typedef Expression* Expression_Ptr; + typedef Expression const* Expression_Ptr_Const; + class Statement; + typedef Statement* Statement_Ptr; + typedef Statement const* Statement_Ptr_Const; + class Value; + typedef Value* Value_Ptr; + typedef Value const* Value_Ptr_Const; + class Declaration; + typedef Declaration* Declaration_Ptr; + typedef Declaration const* Declaration_Ptr_Const; + class Ruleset; + typedef Ruleset* Ruleset_Ptr; + typedef Ruleset const* Ruleset_Ptr_Const; + class Bubble; + typedef Bubble* Bubble_Ptr; + typedef Bubble const* Bubble_Ptr_Const; + class Trace; + typedef Trace* Trace_Ptr; + typedef Trace const* Trace_Ptr_Const; + + class Media_Block; + typedef Media_Block* Media_Block_Ptr; + typedef Media_Block const* Media_Block_Ptr_Const; + class Supports_Block; + typedef Supports_Block* Supports_Block_Ptr; + typedef Supports_Block const* Supports_Block_Ptr_Const; + class Directive; + typedef Directive* Directive_Ptr; + typedef Directive const* Directive_Ptr_Const; + + + class Keyframe_Rule; + typedef Keyframe_Rule* Keyframe_Rule_Ptr; + typedef Keyframe_Rule const* Keyframe_Rule_Ptr_Const; + class At_Root_Block; + typedef At_Root_Block* At_Root_Block_Ptr; + typedef At_Root_Block const* At_Root_Block_Ptr_Const; + class Assignment; + typedef Assignment* Assignment_Ptr; + typedef Assignment const* Assignment_Ptr_Const; + + class Import; + typedef Import* Import_Ptr; + typedef Import const* Import_Ptr_Const; + class Import_Stub; + typedef Import_Stub* Import_Stub_Ptr; + typedef Import_Stub const* Import_Stub_Ptr_Const; + class Warning; + typedef Warning* Warning_Ptr; + typedef Warning const* Warning_Ptr_Const; + + class Error; + typedef Error* Error_Ptr; + typedef Error const* Error_Ptr_Const; + class Debug; + typedef Debug* Debug_Ptr; + typedef Debug const* Debug_Ptr_Const; + class Comment; + typedef Comment* Comment_Ptr; + typedef Comment const* Comment_Ptr_Const; + + class If; + typedef If* If_Ptr; + typedef If const* If_Ptr_Const; + class For; + typedef For* For_Ptr; + typedef For const* For_Ptr_Const; + class Each; + typedef Each* Each_Ptr; + typedef Each const* Each_Ptr_Const; + class While; + typedef While* While_Ptr; + typedef While const* While_Ptr_Const; + class Return; + typedef Return* Return_Ptr; + typedef Return const* Return_Ptr_Const; + class Content; + typedef Content* Content_Ptr; + typedef Content const* Content_Ptr_Const; + class Extension; + typedef Extension* Extension_Ptr; + typedef Extension const* Extension_Ptr_Const; + class Definition; + typedef Definition* Definition_Ptr; + typedef Definition const* Definition_Ptr_Const; + + class List; + typedef List* List_Ptr; + typedef List const* List_Ptr_Const; + class Map; + typedef Map* Map_Ptr; + typedef Map const* Map_Ptr_Const; + class Function; + typedef Function* Function_Ptr; + typedef Function const* Function_Ptr_Const; + + class Mixin_Call; + typedef Mixin_Call* Mixin_Call_Ptr; + typedef Mixin_Call const* Mixin_Call_Ptr_Const; + class Binary_Expression; + typedef Binary_Expression* Binary_Expression_Ptr; + typedef Binary_Expression const* Binary_Expression_Ptr_Const; + class Unary_Expression; + typedef Unary_Expression* Unary_Expression_Ptr; + typedef Unary_Expression const* Unary_Expression_Ptr_Const; + class Function_Call; + typedef Function_Call* Function_Call_Ptr; + typedef Function_Call const* Function_Call_Ptr_Const; + class Function_Call_Schema; + typedef Function_Call_Schema* Function_Call_Schema_Ptr; + typedef Function_Call_Schema const* Function_Call_Schema_Ptr_Const; + class Custom_Warning; + typedef Custom_Warning* Custom_Warning_Ptr; + typedef Custom_Warning const* Custom_Warning_Ptr_Const; + class Custom_Error; + typedef Custom_Error* Custom_Error_Ptr; + typedef Custom_Error const* Custom_Error_Ptr_Const; + + class Variable; + typedef Variable* Variable_Ptr; + typedef Variable const* Variable_Ptr_Const; + class Number; + typedef Number* Number_Ptr; + typedef Number const* Number_Ptr_Const; + class Color; + typedef Color* Color_Ptr; + typedef Color const* Color_Ptr_Const; + class Boolean; + typedef Boolean* Boolean_Ptr; + typedef Boolean const* Boolean_Ptr_Const; + class String; + typedef String* String_Ptr; + typedef String const* String_Ptr_Const; + + class String_Schema; + typedef String_Schema* String_Schema_Ptr; + typedef String_Schema const* String_Schema_Ptr_Const; + class String_Constant; + typedef String_Constant* String_Constant_Ptr; + typedef String_Constant const* String_Constant_Ptr_Const; + class String_Quoted; + typedef String_Quoted* String_Quoted_Ptr; + typedef String_Quoted const* String_Quoted_Ptr_Const; + + class Media_Query; + typedef Media_Query* Media_Query_Ptr; + typedef Media_Query const* Media_Query_Ptr_Const; + class Media_Query_Expression; + typedef Media_Query_Expression* Media_Query_Expression_Ptr; + typedef Media_Query_Expression const* Media_Query_Expression_Ptr_Const; + class Supports_Condition; + typedef Supports_Condition* Supports_Condition_Ptr; + typedef Supports_Condition const* Supports_Condition_Ptr_Const; + class Supports_Operator; + typedef Supports_Operator* Supports_Operator_Ptr; + typedef Supports_Operator const* Supports_Operator_Ptr_Const; + class Supports_Negation; + typedef Supports_Negation* Supports_Negation_Ptr; + typedef Supports_Negation const* Supports_Negation_Ptr_Const; + class Supports_Declaration; + typedef Supports_Declaration* Supports_Declaration_Ptr; + typedef Supports_Declaration const* Supports_Declaration_Ptr_Const; + class Supports_Interpolation; + typedef Supports_Interpolation* Supports_Interpolation_Ptr; + typedef Supports_Interpolation const* Supports_Interpolation_Ptr_Const; + + + class Null; + typedef Null* Null_Ptr; + typedef Null const* Null_Ptr_Const; + + class At_Root_Query; + typedef At_Root_Query* At_Root_Query_Ptr; + typedef At_Root_Query const* At_Root_Query_Ptr_Const; + class Parent_Selector; + typedef Parent_Selector* Parent_Selector_Ptr; + typedef Parent_Selector const* Parent_Selector_Ptr_Const; + class Parameter; + typedef Parameter* Parameter_Ptr; + typedef Parameter const* Parameter_Ptr_Const; + class Parameters; + typedef Parameters* Parameters_Ptr; + typedef Parameters const* Parameters_Ptr_Const; + class Argument; + typedef Argument* Argument_Ptr; + typedef Argument const* Argument_Ptr_Const; + class Arguments; + typedef Arguments* Arguments_Ptr; + typedef Arguments const* Arguments_Ptr_Const; + class Selector; + typedef Selector* Selector_Ptr; + typedef Selector const* Selector_Ptr_Const; + + + class Selector_Schema; + typedef Selector_Schema* Selector_Schema_Ptr; + typedef Selector_Schema const* Selector_Schema_Ptr_Const; + class Placeholder_Selector; + typedef Placeholder_Selector* Placeholder_Selector_Ptr; + typedef Placeholder_Selector const* Placeholder_Selector_Ptr_Const; + class Element_Selector; + typedef Element_Selector* Element_Selector_Ptr; + typedef Element_Selector const* Element_Selector_Ptr_Const; + class Class_Selector; + typedef Class_Selector* Class_Selector_Ptr; + typedef Class_Selector const* Class_Selector_Ptr_Const; + class Id_Selector; + typedef Id_Selector* Id_Selector_Ptr; + typedef Id_Selector const* Id_Selector_Ptr_Const; + class Attribute_Selector; + typedef Attribute_Selector* Attribute_Selector_Ptr; + typedef Attribute_Selector const* Attribute_Selector_Ptr_Const; + + class Pseudo_Selector; + typedef Pseudo_Selector* Pseudo_Selector_Ptr; + typedef Pseudo_Selector const * Pseudo_Selector_Ptr_Const; + class Wrapped_Selector; + typedef Wrapped_Selector* Wrapped_Selector_Ptr; + typedef Wrapped_Selector const * Wrapped_Selector_Ptr_Const; + class Compound_Selector; + typedef Compound_Selector* Compound_Selector_Ptr; + typedef Compound_Selector const * Compound_Selector_Ptr_Const; + class Complex_Selector; + typedef Complex_Selector* Complex_Selector_Ptr; + typedef Complex_Selector const * Complex_Selector_Ptr_Const; + class Selector_List; + typedef Selector_List* Selector_List_Ptr; + typedef Selector_List const * Selector_List_Ptr_Const; + + + // common classes + class Context; + class Expand; + class Eval; + + // declare classes that are instances of memory nodes + // #define IMPL_MEM_OBJ(type) using type##_Obj = SharedImpl + #define IMPL_MEM_OBJ(type) typedef SharedImpl type##_Obj + + IMPL_MEM_OBJ(AST_Node); + IMPL_MEM_OBJ(Statement); + IMPL_MEM_OBJ(Block); + IMPL_MEM_OBJ(Ruleset); + IMPL_MEM_OBJ(Bubble); + IMPL_MEM_OBJ(Trace); + IMPL_MEM_OBJ(Media_Block); + IMPL_MEM_OBJ(Supports_Block); + IMPL_MEM_OBJ(Directive); + IMPL_MEM_OBJ(Keyframe_Rule); + IMPL_MEM_OBJ(At_Root_Block); + IMPL_MEM_OBJ(Declaration); + IMPL_MEM_OBJ(Assignment); + IMPL_MEM_OBJ(Import); + IMPL_MEM_OBJ(Import_Stub); + IMPL_MEM_OBJ(Warning); + IMPL_MEM_OBJ(Error); + IMPL_MEM_OBJ(Debug); + IMPL_MEM_OBJ(Comment); + IMPL_MEM_OBJ(PreValue); + IMPL_MEM_OBJ(Has_Block); + IMPL_MEM_OBJ(Thunk); + IMPL_MEM_OBJ(If); + IMPL_MEM_OBJ(For); + IMPL_MEM_OBJ(Each); + IMPL_MEM_OBJ(While); + IMPL_MEM_OBJ(Return); + IMPL_MEM_OBJ(Content); + IMPL_MEM_OBJ(Extension); + IMPL_MEM_OBJ(Definition); + IMPL_MEM_OBJ(Mixin_Call); + IMPL_MEM_OBJ(Value); + IMPL_MEM_OBJ(Expression); + IMPL_MEM_OBJ(List); + IMPL_MEM_OBJ(Map); + IMPL_MEM_OBJ(Function); + IMPL_MEM_OBJ(Binary_Expression); + IMPL_MEM_OBJ(Unary_Expression); + IMPL_MEM_OBJ(Function_Call); + IMPL_MEM_OBJ(Function_Call_Schema); + IMPL_MEM_OBJ(Custom_Warning); + IMPL_MEM_OBJ(Custom_Error); + IMPL_MEM_OBJ(Variable); + IMPL_MEM_OBJ(Number); + IMPL_MEM_OBJ(Color); + IMPL_MEM_OBJ(Boolean); + IMPL_MEM_OBJ(String_Schema); + IMPL_MEM_OBJ(String); + IMPL_MEM_OBJ(String_Constant); + IMPL_MEM_OBJ(String_Quoted); + IMPL_MEM_OBJ(Media_Query); + IMPL_MEM_OBJ(Media_Query_Expression); + IMPL_MEM_OBJ(Supports_Condition); + IMPL_MEM_OBJ(Supports_Operator); + IMPL_MEM_OBJ(Supports_Negation); + IMPL_MEM_OBJ(Supports_Declaration); + IMPL_MEM_OBJ(Supports_Interpolation); + IMPL_MEM_OBJ(At_Root_Query); + IMPL_MEM_OBJ(Null); + IMPL_MEM_OBJ(Parent_Selector); + IMPL_MEM_OBJ(Parameter); + IMPL_MEM_OBJ(Parameters); + IMPL_MEM_OBJ(Argument); + IMPL_MEM_OBJ(Arguments); + IMPL_MEM_OBJ(Selector); + IMPL_MEM_OBJ(Selector_Schema); + IMPL_MEM_OBJ(Simple_Selector); + IMPL_MEM_OBJ(Placeholder_Selector); + IMPL_MEM_OBJ(Element_Selector); + IMPL_MEM_OBJ(Class_Selector); + IMPL_MEM_OBJ(Id_Selector); + IMPL_MEM_OBJ(Attribute_Selector); + IMPL_MEM_OBJ(Pseudo_Selector); + IMPL_MEM_OBJ(Wrapped_Selector); + IMPL_MEM_OBJ(Compound_Selector); + IMPL_MEM_OBJ(Complex_Selector); + IMPL_MEM_OBJ(Selector_List); + + // ########################################################################### + // Implement compare, order and hashing operations for AST Nodes + // ########################################################################### + + struct HashNodes { + template + size_t operator() (const T& ex) const { + return ex.isNull() ? 0 : ex->hash(); + } + }; + struct OrderNodes { + template + bool operator() (const T& lhs, const T& rhs) const { + return !lhs.isNull() && !rhs.isNull() && *lhs < *rhs; + } + }; + struct CompareNodes { + template + bool operator() (const T& lhs, const T& rhs) const { + // code around sass logic issue. 1px == 1 is true + // but both items are still different keys in maps + if (dynamic_cast(lhs.ptr())) + if (dynamic_cast(rhs.ptr())) + return lhs->hash() == rhs->hash(); + return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs; + } + }; + + // ########################################################################### + // some often used typedefs + // ########################################################################### + + typedef std::unordered_map< + Expression_Obj, // key + Expression_Obj, // value + HashNodes, // hasher + CompareNodes // compare + > ExpressionMap; + typedef std::unordered_set< + Expression_Obj, // value + HashNodes, // hasher + CompareNodes // compare + > ExpressionSet; + + typedef std::string SubSetMapKey; + typedef std::vector SubSetMapKeys; + + typedef std::pair SubSetMapPair; + typedef std::pair SubSetMapLookup; + typedef std::vector SubSetMapPairs; + typedef std::vector SubSetMapLookups; + + typedef std::pair SubSetMapResult; + typedef std::vector SubSetMapResults; + + typedef std::deque ComplexSelectorDeque; + typedef std::set SimpleSelectorSet; + typedef std::set ComplexSelectorSet; + typedef std::set CompoundSelectorSet; + typedef std::unordered_set SimpleSelectorDict; + + typedef std::vector* ImporterStack; + + // only to switch implementations for testing + #define environment_map std::map + + // ########################################################################### + // explicit type conversion functions + // ########################################################################### + + template + T* Cast(AST_Node* ptr); + + template + const T* Cast(const AST_Node* ptr); + + // sometimes you know the class you want to cast to is final + // in this case a simple typeid check is faster and safe to use + + #define DECLARE_BASE_CAST(T) \ + template<> T* Cast(AST_Node* ptr); \ + template<> const T* Cast(const AST_Node* ptr); \ + + // ########################################################################### + // implement specialization for final classes + // ########################################################################### + + DECLARE_BASE_CAST(AST_Node) + DECLARE_BASE_CAST(Expression) + DECLARE_BASE_CAST(Statement) + DECLARE_BASE_CAST(Has_Block) + DECLARE_BASE_CAST(PreValue) + DECLARE_BASE_CAST(Value) + DECLARE_BASE_CAST(List) + DECLARE_BASE_CAST(String) + DECLARE_BASE_CAST(String_Constant) + DECLARE_BASE_CAST(Supports_Condition) + DECLARE_BASE_CAST(Selector) + DECLARE_BASE_CAST(Simple_Selector) + +} + +#endif diff --git a/src/b64/cencode.h b/src/b64/cencode.h new file mode 100644 index 000000000..1d71e83fd --- /dev/null +++ b/src/b64/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/src/b64/encode.h b/src/b64/encode.h new file mode 100644 index 000000000..92df8ec70 --- /dev/null +++ b/src/b64/encode.h @@ -0,0 +1,79 @@ +// :mode=c++: +/* +encode.h - c++ wrapper for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ +#ifndef BASE64_ENCODE_H +#define BASE64_ENCODE_H + +#include + +namespace base64 +{ + extern "C" + { + #include "cencode.h" + } + + struct encoder + { + base64_encodestate _state; + int _buffersize; + + encoder(int buffersize_in = BUFFERSIZE) + : _buffersize(buffersize_in) + { + base64_init_encodestate(&_state); + } + + int encode(char value_in) + { + return base64_encode_value(value_in); + } + + int encode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_encode_block(code_in, length_in, plaintext_out, &_state); + } + + int encode_end(char* plaintext_out) + { + return base64_encode_blockend(plaintext_out, &_state); + } + + void encode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_encodestate(&_state); + // + const int N = _buffersize; + char* plaintext = new char[N]; + char* code = new char[2*N]; + int plainlength; + int codelength; + + do + { + istream_in.read(plaintext, N); + plainlength = static_cast(istream_in.gcount()); + // + codelength = encode(plaintext, plainlength, code); + ostream_in.write(code, codelength); + } + while (istream_in.good() && plainlength > 0); + + codelength = encode_end(code); + ostream_in.write(code, codelength); + // + base64_init_encodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_ENCODE_H + diff --git a/src/backtrace.cpp b/src/backtrace.cpp new file mode 100644 index 000000000..8da963a72 --- /dev/null +++ b/src/backtrace.cpp @@ -0,0 +1,46 @@ +#include "backtrace.hpp" + +namespace Sass { + + const std::string traces_to_string(Backtraces traces, std::string indent) { + + std::stringstream ss; + std::string cwd(File::get_cwd()); + + bool first = true; + size_t i_beg = traces.size() - 1; + size_t i_end = std::string::npos; + for (size_t i = i_beg; i != i_end; i --) { + + const Backtrace& trace = traces[i]; + + // make path relative to the current directory + std::string rel_path(File::abs2rel(trace.pstate.path, cwd, cwd)); + + // skip functions on error cases (unsure why ruby sass does this) + // if (trace.caller.substr(0, 6) == ", in f") continue; + + if (first) { + ss << indent; + ss << "on line "; + ss << trace.pstate.line + 1; + ss << " of " << rel_path; + // ss << trace.caller; + first = false; + } else { + ss << trace.caller; + ss << std::endl; + ss << indent; + ss << "from line "; + ss << trace.pstate.line + 1; + ss << " of " << rel_path; + } + + } + + ss << std::endl; + return ss.str(); + + } + +}; diff --git a/src/backtrace.hpp b/src/backtrace.hpp new file mode 100644 index 000000000..72d5fe517 --- /dev/null +++ b/src/backtrace.hpp @@ -0,0 +1,29 @@ +#ifndef SASS_BACKTRACE_H +#define SASS_BACKTRACE_H + +#include +#include +#include "file.hpp" +#include "position.hpp" + +namespace Sass { + + struct Backtrace { + + ParserState pstate; + std::string caller; + + Backtrace(ParserState pstate, std::string c = "") + : pstate(pstate), + caller(c) + { } + + }; + + typedef std::vector Backtraces; + + const std::string traces_to_string(Backtraces traces, std::string indent = "\t"); + +} + +#endif diff --git a/src/base64vlq.cpp b/src/base64vlq.cpp new file mode 100644 index 000000000..be2fb4926 --- /dev/null +++ b/src/base64vlq.cpp @@ -0,0 +1,44 @@ +#include "sass.hpp" +#include "base64vlq.hpp" + +namespace Sass { + + std::string Base64VLQ::encode(const int number) const + { + std::string encoded = ""; + + int vlq = to_vlq_signed(number); + + do { + int digit = vlq & VLQ_BASE_MASK; + vlq >>= VLQ_BASE_SHIFT; + if (vlq > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64_encode(digit); + } while (vlq > 0); + + return encoded; + } + + char Base64VLQ::base64_encode(const int number) const + { + int index = number; + if (index < 0) index = 0; + if (index > 63) index = 63; + return CHARACTERS[index]; + } + + int Base64VLQ::to_vlq_signed(const int number) const + { + return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; + } + + const char* Base64VLQ::CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const int Base64VLQ::VLQ_BASE_SHIFT = 5; + const int Base64VLQ::VLQ_BASE = 1 << VLQ_BASE_SHIFT; + const int Base64VLQ::VLQ_BASE_MASK = VLQ_BASE - 1; + const int Base64VLQ::VLQ_CONTINUATION_BIT = VLQ_BASE; + +} diff --git a/src/base64vlq.hpp b/src/base64vlq.hpp new file mode 100644 index 000000000..aca315a21 --- /dev/null +++ b/src/base64vlq.hpp @@ -0,0 +1,30 @@ +#ifndef SASS_BASE64VLQ_H +#define SASS_BASE64VLQ_H + +#include + +namespace Sass { + + class Base64VLQ { + + public: + + std::string encode(const int number) const; + + private: + + char base64_encode(const int number) const; + + int to_vlq_signed(const int number) const; + + static const char* CHARACTERS; + + static const int VLQ_BASE_SHIFT; + static const int VLQ_BASE; + static const int VLQ_BASE_MASK; + static const int VLQ_CONTINUATION_BIT; + }; + +} + +#endif diff --git a/src/bind.cpp b/src/bind.cpp new file mode 100644 index 000000000..ec20ac838 --- /dev/null +++ b/src/bind.cpp @@ -0,0 +1,311 @@ +#include "sass.hpp" +#include "bind.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "expand.hpp" +#include "eval.hpp" +#include +#include +#include + +namespace Sass { + + void bind(std::string type, std::string name, Parameters_Obj ps, Arguments_Obj as, Context* ctx, Env* env, Eval* eval) + { + std::string callee(type + " " + name); + + std::map param_map; + List_Obj varargs = SASS_MEMORY_NEW(List, as->pstate()); + varargs->is_arglist(true); // enable keyword size handling + + for (size_t i = 0, L = as->length(); i < L; ++i) { + if (auto str = Cast((*as)[i]->value())) { + // force optional quotes (only if needed) + if (str->quote_mark()) { + str->quote_mark('*'); + } + } + } + + // Set up a map to ensure named arguments refer to actual parameters. Also + // eval each default value left-to-right, wrt env, populating env as we go. + for (size_t i = 0, L = ps->length(); i < L; ++i) { + Parameter_Obj p = ps->at(i); + param_map[p->name()] = p; + // if (p->default_value()) { + // env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env)); + // } + } + + // plug in all args; if we have leftover params, deal with it later + size_t ip = 0, LP = ps->length(); + size_t ia = 0, LA = as->length(); + while (ia < LA) { + Argument_Obj a = as->at(ia); + if (ip >= LP) { + // skip empty rest arguments + if (a->is_rest_argument()) { + if (List_Obj l = Cast(a->value())) { + if (l->length() == 0) { + ++ ia; continue; + } + } + } + std::stringstream msg; + msg << "wrong number of arguments (" << LA << " for " << LP << ")"; + msg << " for `" << name << "'"; + return error(msg.str(), as->pstate(), eval->exp.traces); + } + Parameter_Obj p = ps->at(ip); + + // If the current parameter is the rest parameter, process and break the loop + if (p->is_rest_parameter()) { + // The next argument by coincidence provides a rest argument + if (a->is_rest_argument()) { + + // We should always get a list for rest arguments + if (List_Obj rest = Cast(a->value())) { + // create a new list object for wrapped items + List_Ptr arglist = SASS_MEMORY_NEW(List, + p->pstate(), + 0, + rest->separator(), + true); + // wrap each item from list as an argument + for (Expression_Obj item : rest->elements()) { + if (Argument_Obj arg = Cast(item)) { + arglist->append(SASS_MEMORY_COPY(arg)); // copy + } else { + arglist->append(SASS_MEMORY_NEW(Argument, + item->pstate(), + item, + "", + false, + false)); + } + } + // assign new arglist to environment + env->local_frame()[p->name()] = arglist; + } + // invalid state + else { + throw std::runtime_error("invalid state"); + } + } else if (a->is_keyword_argument()) { + + // expand keyword arguments into their parameters + List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); + env->local_frame()[p->name()] = arglist; + Map_Obj argmap = Cast(a->value()); + for (auto key : argmap->keys()) { + if (String_Constant_Obj str = Cast(key)) { + std::string param = unquote(str->value()); + arglist->append(SASS_MEMORY_NEW(Argument, + key->pstate(), + argmap->at(key), + "$" + param, + false, + false)); + } else { + eval->exp.traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); + } + } + + } else { + + // create a new list object for wrapped items + List_Obj arglist = SASS_MEMORY_NEW(List, + p->pstate(), + 0, + SASS_COMMA, + true); + // consume the next args + while (ia < LA) { + // get and post inc + a = (*as)[ia++]; + // maybe we have another list as argument + List_Obj ls = Cast(a->value()); + // skip any list completely if empty + if (ls && ls->empty() && a->is_rest_argument()) continue; + + Expression_Obj value = a->value(); + if (Argument_Obj arg = Cast(value)) { + arglist->append(arg); + } + // check if we have rest argument + else if (a->is_rest_argument()) { + // preserve the list separator from rest args + if (List_Obj rest = Cast(a->value())) { + arglist->separator(rest->separator()); + + for (size_t i = 0, L = rest->length(); i < L; ++i) { + Expression_Obj obj = rest->value_at_index(i); + arglist->append(SASS_MEMORY_NEW(Argument, + obj->pstate(), + obj, + "", + false, + false)); + } + } + // no more arguments + break; + } + // wrap all other value types into Argument + else { + arglist->append(SASS_MEMORY_NEW(Argument, + a->pstate(), + a->value(), + a->name(), + false, + false)); + } + } + // assign new arglist to environment + env->local_frame()[p->name()] = arglist; + } + // consumed parameter + ++ip; + // no more paramaters + break; + } + + // If the current argument is the rest argument, extract a value for processing + else if (a->is_rest_argument()) { + // normal param and rest arg + List_Obj arglist = Cast(a->value()); + if (!arglist) { + if (Expression_Obj arg = Cast(a->value())) { + arglist = SASS_MEMORY_NEW(List, a->pstate(), 1); + arglist->append(arg); + } + } + + // empty rest arg - treat all args as default values + if (!arglist || !arglist->length()) { + break; + } else { + if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { + size_t arg_count = (arglist->length() + LA - 1); + std::stringstream msg; + msg << callee << " takes " << LP; + msg << (LP == 1 ? " argument" : " arguments"); + msg << " but " << arg_count; + msg << (arg_count == 1 ? " was passed" : " were passed."); + deprecated_bind(msg.str(), as->pstate()); + + while (arglist->length() > LP - ip) { + arglist->elements().erase(arglist->elements().end() - 1); + } + } + } + // otherwise move one of the rest args into the param, converting to argument if necessary + Expression_Obj obj = arglist->at(0); + if (!(a = Cast(obj))) { + Expression_Ptr a_to_convert = obj; + a = SASS_MEMORY_NEW(Argument, + a_to_convert->pstate(), + a_to_convert, + "", + false, + false); + } + arglist->elements().erase(arglist->elements().begin()); + if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) { + ++ia; + } + + } else if (a->is_keyword_argument()) { + Map_Obj argmap = Cast(a->value()); + + for (auto key : argmap->keys()) { + String_Constant_Ptr val = Cast(key); + if (val == NULL) { + eval->exp.traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); + } + std::string param = "$" + unquote(val->value()); + + if (!param_map.count(param)) { + std::stringstream msg; + msg << callee << " has no parameter named " << param; + error(msg.str(), a->pstate(), eval->exp.traces); + } + env->local_frame()[param] = argmap->at(key); + } + ++ia; + continue; + } else { + ++ia; + } + + if (a->name().empty()) { + if (env->has_local(p->name())) { + std::stringstream msg; + msg << "parameter " << p->name() + << " provided more than once in call to " << callee; + error(msg.str(), a->pstate(), eval->exp.traces); + } + // ordinal arg -- bind it to the next param + env->local_frame()[p->name()] = a->value(); + ++ip; + } + else { + // named arg -- bind it to the appropriately named param + if (!param_map.count(a->name())) { + if (ps->has_rest_parameter()) { + varargs->append(a); + } else { + std::stringstream msg; + msg << callee << " has no parameter named " << a->name(); + error(msg.str(), a->pstate(), eval->exp.traces); + } + } + if (param_map[a->name()]) { + if (param_map[a->name()]->is_rest_parameter()) { + std::stringstream msg; + msg << "argument " << a->name() << " of " << callee + << "cannot be used as named argument"; + error(msg.str(), a->pstate(), eval->exp.traces); + } + } + if (env->has_local(a->name())) { + std::stringstream msg; + msg << "parameter " << p->name() + << "provided more than once in call to " << callee; + error(msg.str(), a->pstate(), eval->exp.traces); + } + env->local_frame()[a->name()] = a->value(); + } + } + // EO while ia + + // If we make it here, we're out of args but may have leftover params. + // That's only okay if they have default values, or were already bound by + // named arguments, or if it's a single rest-param. + for (size_t i = ip; i < LP; ++i) { + Parameter_Obj leftover = ps->at(i); + // cerr << "env for default params:" << endl; + // env->print(); + // cerr << "********" << endl; + if (!env->has_local(leftover->name())) { + if (leftover->is_rest_parameter()) { + env->local_frame()[leftover->name()] = varargs; + } + else if (leftover->default_value()) { + Expression_Ptr dv = leftover->default_value()->perform(eval); + env->local_frame()[leftover->name()] = dv; + } + else { + // param is unbound and has no default value -- error + throw Exception::MissingArgument(as->pstate(), eval->exp.traces, name, leftover->name(), type); + } + } + } + + return; + } + + +} diff --git a/src/bind.hpp b/src/bind.hpp new file mode 100644 index 000000000..93a503aa6 --- /dev/null +++ b/src/bind.hpp @@ -0,0 +1,13 @@ +#ifndef SASS_BIND_H +#define SASS_BIND_H + +#include +#include "environment.hpp" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Context*, Env*, Eval*); +} + +#endif diff --git a/src/c99func.c b/src/c99func.c new file mode 100644 index 000000000..f846eee80 --- /dev/null +++ b/src/c99func.c @@ -0,0 +1,54 @@ +/* + Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#if defined(_MSC_VER) && _MSC_VER < 1900 + +#include +#include +#include + +static int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +int snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} + +#endif diff --git a/src/cencode.c b/src/cencode.c new file mode 100644 index 000000000..9109f4b22 --- /dev/null +++ b/src/cencode.c @@ -0,0 +1,108 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "b64/cencode.h" + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + #ifndef _MSC_VER + /* fall through */ + #endif + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + #ifndef _MSC_VER + /* fall through */ + #endif + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + } + } + /* control should not reach here */ + return (int)(codechar - code_out); +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return (int)(codechar - code_out); +} + diff --git a/src/check_nesting.cpp b/src/check_nesting.cpp new file mode 100644 index 000000000..880bcca37 --- /dev/null +++ b/src/check_nesting.cpp @@ -0,0 +1,398 @@ +#include "sass.hpp" +#include + +#include "check_nesting.hpp" + +namespace Sass { + + CheckNesting::CheckNesting() + : parents(std::vector()), + traces(std::vector()), + parent(0), current_mixin_definition(0) + { } + + void error(AST_Node_Ptr node, Backtraces traces, std::string msg) { + traces.push_back(Backtrace(node->pstate())); + throw Exception::InvalidSass(node->pstate(), traces, msg); + } + + Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) + { + Statement_Ptr old_parent = this->parent; + + if (At_Root_Block_Ptr root = Cast(parent)) { + std::vector old_parents = this->parents; + std::vector new_parents; + + for (size_t i = 0, L = this->parents.size(); i < L; i++) { + Statement_Ptr p = this->parents.at(i); + if (!root->exclude_node(p)) { + new_parents.push_back(p); + } + } + this->parents = new_parents; + + for (size_t i = this->parents.size(); i > 0; i--) { + Statement_Ptr p = 0; + Statement_Ptr gp = 0; + if (i > 0) p = this->parents.at(i - 1); + if (i > 1) gp = this->parents.at(i - 2); + + if (!this->is_transparent_parent(p, gp)) { + this->parent = p; + break; + } + } + + At_Root_Block_Ptr ar = Cast(parent); + Block_Ptr ret = ar->block(); + + if (ret != NULL) { + for (auto n : ret->elements()) { + n->perform(this); + } + } + + this->parent = old_parent; + this->parents = old_parents; + + return ret; + } + + if (!this->is_transparent_parent(parent, old_parent)) { + this->parent = parent; + } + + this->parents.push_back(parent); + + Block_Ptr b = Cast(parent); + + if (Trace_Ptr trace = Cast(parent)) { + if (trace->type() == 'i') { + this->traces.push_back(Backtrace(trace->pstate())); + } + } + + if (!b) { + if (Has_Block_Ptr bb = Cast(parent)) { + b = bb->block(); + } + } + + if (b) { + for (auto n : b->elements()) { + n->perform(this); + } + } + + this->parent = old_parent; + this->parents.pop_back(); + + if (Trace_Ptr trace = Cast(parent)) { + if (trace->type() == 'i') { + this->traces.pop_back(); + } + } + + return b; + } + + + Statement_Ptr CheckNesting::operator()(Block_Ptr b) + { + return this->visit_children(b); + } + + Statement_Ptr CheckNesting::operator()(Definition_Ptr n) + { + if (!this->should_visit(n)) return NULL; + if (!is_mixin(n)) { + visit_children(n); + return n; + } + + Definition_Ptr old_mixin_definition = this->current_mixin_definition; + this->current_mixin_definition = n; + + visit_children(n); + + this->current_mixin_definition = old_mixin_definition; + + return n; + } + + Statement_Ptr CheckNesting::operator()(If_Ptr i) + { + this->visit_children(i); + + if (Block_Ptr b = Cast(i->alternative())) { + for (auto n : b->elements()) n->perform(this); + } + + return i; + } + + Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) + { + Block_Ptr b1 = Cast(s); + Has_Block_Ptr b2 = Cast(s); + return b1 || b2 ? visit_children(s) : s; + } + + bool CheckNesting::should_visit(Statement_Ptr node) + { + if (!this->parent) return true; + + if (Cast(node)) + { this->invalid_content_parent(this->parent, node); } + + if (is_charset(node)) + { this->invalid_charset_parent(this->parent, node); } + + if (Cast(node)) + { this->invalid_extend_parent(this->parent, node); } + + // if (Cast(node)) + // { this->invalid_import_parent(this->parent); } + + if (this->is_mixin(node)) + { this->invalid_mixin_definition_parent(this->parent, node); } + + if (this->is_function(node)) + { this->invalid_function_parent(this->parent, node); } + + if (this->is_function(this->parent)) + { this->invalid_function_child(node); } + + if (Declaration_Ptr d = Cast(node)) + { + this->invalid_prop_parent(this->parent, node); + this->invalid_value_child(d->value()); + } + + if (Cast(this->parent)) + { this->invalid_prop_child(node); } + + if (Cast(node)) + { this->invalid_return_parent(this->parent, node); } + + return true; + } + + void CheckNesting::invalid_content_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + if (!this->current_mixin_definition) { + error(node, traces, "@content may only be used within a mixin."); + } + } + + void CheckNesting::invalid_charset_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + if (!( + is_root_node(parent) + )) { + error(node, traces, "@charset may only be used at the root of a document."); + } + } + + void CheckNesting::invalid_extend_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + if (!( + Cast(parent) || + Cast(parent) || + is_mixin(parent) + )) { + error(node, traces, "Extend directives may only be used within rules."); + } + } + + // void CheckNesting::invalid_import_parent(Statement_Ptr parent, AST_Node_Ptr node) + // { + // for (auto pp : this->parents) { + // if ( + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // Cast(pp) || + // is_mixin(pp) + // ) { + // error(node, traces, "Import directives may not be defined within control directives or other mixins."); + // } + // } + + // if (this->is_root_node(parent)) { + // return; + // } + + // if (false/*n.css_import?*/) { + // error(node, traces, "CSS import directives may only be used at the root of a document."); + // } + // } + + void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + for (Statement_Ptr pp : this->parents) { + if ( + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + is_mixin(pp) + ) { + error(node, traces, "Mixins may not be defined within control directives or other mixins."); + } + } + } + + void CheckNesting::invalid_function_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + for (Statement_Ptr pp : this->parents) { + if ( + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + Cast(pp) || + is_mixin(pp) + ) { + error(node, traces, "Functions may not be defined within control directives or other mixins."); + } + } + } + + void CheckNesting::invalid_function_child(Statement_Ptr child) + { + if (!( + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + // Ruby Sass doesn't distinguish variables and assignments + Cast(child) || + Cast(child) || + Cast(child) + )) { + error(child, traces, "Functions can only contain variable declarations and control directives."); + } + } + + void CheckNesting::invalid_prop_child(Statement_Ptr child) + { + if (!( + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) || + Cast(child) + )) { + error(child, traces, "Illegal nesting: Only properties may be nested beneath properties."); + } + } + + void CheckNesting::invalid_prop_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + if (!( + is_mixin(parent) || + is_directive_node(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) + )) { + error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties."); + } + } + + void CheckNesting::invalid_value_child(AST_Node_Ptr d) + { + if (Map_Ptr m = Cast(d)) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::InvalidValue(traces, *m); + } + if (Number_Ptr n = Cast(d)) { + if (!n->is_valid_css_unit()) { + traces.push_back(Backtrace(n->pstate())); + throw Exception::InvalidValue(traces, *n); + } + } + + // error(dbg + " isn't a valid CSS value.", m->pstate(),); + + } + + void CheckNesting::invalid_return_parent(Statement_Ptr parent, AST_Node_Ptr node) + { + if (!this->is_function(parent)) { + error(node, traces, "@return may only be used within a function."); + } + } + + bool CheckNesting::is_transparent_parent(Statement_Ptr parent, Statement_Ptr grandparent) + { + bool parent_bubbles = parent && parent->bubbles(); + + bool valid_bubble_node = parent_bubbles && + !is_root_node(grandparent) && + !is_at_root_node(grandparent); + + return Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || + Cast(parent) || + valid_bubble_node; + } + + bool CheckNesting::is_charset(Statement_Ptr n) + { + Directive_Ptr d = Cast(n); + return d && d->keyword() == "charset"; + } + + bool CheckNesting::is_mixin(Statement_Ptr n) + { + Definition_Ptr def = Cast(n); + return def && def->type() == Definition::MIXIN; + } + + bool CheckNesting::is_function(Statement_Ptr n) + { + Definition_Ptr def = Cast(n); + return def && def->type() == Definition::FUNCTION; + } + + bool CheckNesting::is_root_node(Statement_Ptr n) + { + if (Cast(n)) return false; + + Block_Ptr b = Cast(n); + return b && b->is_root(); + } + + bool CheckNesting::is_at_root_node(Statement_Ptr n) + { + return Cast(n) != NULL; + } + + bool CheckNesting::is_directive_node(Statement_Ptr n) + { + return Cast(n) || + Cast(n) || + Cast(n) || + Cast(n); + } +} diff --git a/src/check_nesting.hpp b/src/check_nesting.hpp new file mode 100644 index 000000000..62c38d9dc --- /dev/null +++ b/src/check_nesting.hpp @@ -0,0 +1,65 @@ +#ifndef SASS_CHECK_NESTING_H +#define SASS_CHECK_NESTING_H + +#include "ast.hpp" +#include "operation.hpp" + +namespace Sass { + + class CheckNesting : public Operation_CRTP { + + std::vector parents; + Backtraces traces; + Statement_Ptr parent; + Definition_Ptr current_mixin_definition; + + Statement_Ptr fallback_impl(Statement_Ptr); + Statement_Ptr before(Statement_Ptr); + Statement_Ptr visit_children(Statement_Ptr); + + public: + CheckNesting(); + ~CheckNesting() { } + + Statement_Ptr operator()(Block_Ptr); + Statement_Ptr operator()(Definition_Ptr); + Statement_Ptr operator()(If_Ptr); + + template + Statement_Ptr fallback(U x) { + Statement_Ptr n = Cast(x); + if (this->should_visit(n)) { + return fallback_impl(n); + } + return NULL; + } + + private: + void invalid_content_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_charset_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_extend_parent(Statement_Ptr, AST_Node_Ptr); + // void invalid_import_parent(Statement_Ptr); + void invalid_mixin_definition_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_function_parent(Statement_Ptr, AST_Node_Ptr); + + void invalid_function_child(Statement_Ptr); + void invalid_prop_child(Statement_Ptr); + void invalid_prop_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_return_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_value_child(AST_Node_Ptr); + + bool is_transparent_parent(Statement_Ptr, Statement_Ptr); + + bool should_visit(Statement_Ptr); + + bool is_charset(Statement_Ptr); + bool is_mixin(Statement_Ptr); + bool is_function(Statement_Ptr); + bool is_root_node(Statement_Ptr); + bool is_at_root_node(Statement_Ptr); + bool is_directive_node(Statement_Ptr); + }; + +} + +#endif diff --git a/src/color_maps.cpp b/src/color_maps.cpp new file mode 100644 index 000000000..129e47c5a --- /dev/null +++ b/src/color_maps.cpp @@ -0,0 +1,648 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "color_maps.hpp" + +namespace Sass { + + namespace ColorNames + { + const char aliceblue [] = "aliceblue"; + const char antiquewhite [] = "antiquewhite"; + const char cyan [] = "cyan"; + const char aqua [] = "aqua"; + const char aquamarine [] = "aquamarine"; + const char azure [] = "azure"; + const char beige [] = "beige"; + const char bisque [] = "bisque"; + const char black [] = "black"; + const char blanchedalmond [] = "blanchedalmond"; + const char blue [] = "blue"; + const char blueviolet [] = "blueviolet"; + const char brown [] = "brown"; + const char burlywood [] = "burlywood"; + const char cadetblue [] = "cadetblue"; + const char chartreuse [] = "chartreuse"; + const char chocolate [] = "chocolate"; + const char coral [] = "coral"; + const char cornflowerblue [] = "cornflowerblue"; + const char cornsilk [] = "cornsilk"; + const char crimson [] = "crimson"; + const char darkblue [] = "darkblue"; + const char darkcyan [] = "darkcyan"; + const char darkgoldenrod [] = "darkgoldenrod"; + const char darkgray [] = "darkgray"; + const char darkgrey [] = "darkgrey"; + const char darkgreen [] = "darkgreen"; + const char darkkhaki [] = "darkkhaki"; + const char darkmagenta [] = "darkmagenta"; + const char darkolivegreen [] = "darkolivegreen"; + const char darkorange [] = "darkorange"; + const char darkorchid [] = "darkorchid"; + const char darkred [] = "darkred"; + const char darksalmon [] = "darksalmon"; + const char darkseagreen [] = "darkseagreen"; + const char darkslateblue [] = "darkslateblue"; + const char darkslategray [] = "darkslategray"; + const char darkslategrey [] = "darkslategrey"; + const char darkturquoise [] = "darkturquoise"; + const char darkviolet [] = "darkviolet"; + const char deeppink [] = "deeppink"; + const char deepskyblue [] = "deepskyblue"; + const char dimgray [] = "dimgray"; + const char dimgrey [] = "dimgrey"; + const char dodgerblue [] = "dodgerblue"; + const char firebrick [] = "firebrick"; + const char floralwhite [] = "floralwhite"; + const char forestgreen [] = "forestgreen"; + const char magenta [] = "magenta"; + const char fuchsia [] = "fuchsia"; + const char gainsboro [] = "gainsboro"; + const char ghostwhite [] = "ghostwhite"; + const char gold [] = "gold"; + const char goldenrod [] = "goldenrod"; + const char gray [] = "gray"; + const char grey [] = "grey"; + const char green [] = "green"; + const char greenyellow [] = "greenyellow"; + const char honeydew [] = "honeydew"; + const char hotpink [] = "hotpink"; + const char indianred [] = "indianred"; + const char indigo [] = "indigo"; + const char ivory [] = "ivory"; + const char khaki [] = "khaki"; + const char lavender [] = "lavender"; + const char lavenderblush [] = "lavenderblush"; + const char lawngreen [] = "lawngreen"; + const char lemonchiffon [] = "lemonchiffon"; + const char lightblue [] = "lightblue"; + const char lightcoral [] = "lightcoral"; + const char lightcyan [] = "lightcyan"; + const char lightgoldenrodyellow [] = "lightgoldenrodyellow"; + const char lightgray [] = "lightgray"; + const char lightgrey [] = "lightgrey"; + const char lightgreen [] = "lightgreen"; + const char lightpink [] = "lightpink"; + const char lightsalmon [] = "lightsalmon"; + const char lightseagreen [] = "lightseagreen"; + const char lightskyblue [] = "lightskyblue"; + const char lightslategray [] = "lightslategray"; + const char lightslategrey [] = "lightslategrey"; + const char lightsteelblue [] = "lightsteelblue"; + const char lightyellow [] = "lightyellow"; + const char lime [] = "lime"; + const char limegreen [] = "limegreen"; + const char linen [] = "linen"; + const char maroon [] = "maroon"; + const char mediumaquamarine [] = "mediumaquamarine"; + const char mediumblue [] = "mediumblue"; + const char mediumorchid [] = "mediumorchid"; + const char mediumpurple [] = "mediumpurple"; + const char mediumseagreen [] = "mediumseagreen"; + const char mediumslateblue [] = "mediumslateblue"; + const char mediumspringgreen [] = "mediumspringgreen"; + const char mediumturquoise [] = "mediumturquoise"; + const char mediumvioletred [] = "mediumvioletred"; + const char midnightblue [] = "midnightblue"; + const char mintcream [] = "mintcream"; + const char mistyrose [] = "mistyrose"; + const char moccasin [] = "moccasin"; + const char navajowhite [] = "navajowhite"; + const char navy [] = "navy"; + const char oldlace [] = "oldlace"; + const char olive [] = "olive"; + const char olivedrab [] = "olivedrab"; + const char orange [] = "orange"; + const char orangered [] = "orangered"; + const char orchid [] = "orchid"; + const char palegoldenrod [] = "palegoldenrod"; + const char palegreen [] = "palegreen"; + const char paleturquoise [] = "paleturquoise"; + const char palevioletred [] = "palevioletred"; + const char papayawhip [] = "papayawhip"; + const char peachpuff [] = "peachpuff"; + const char peru [] = "peru"; + const char pink [] = "pink"; + const char plum [] = "plum"; + const char powderblue [] = "powderblue"; + const char purple [] = "purple"; + const char red [] = "red"; + const char rosybrown [] = "rosybrown"; + const char royalblue [] = "royalblue"; + const char saddlebrown [] = "saddlebrown"; + const char salmon [] = "salmon"; + const char sandybrown [] = "sandybrown"; + const char seagreen [] = "seagreen"; + const char seashell [] = "seashell"; + const char sienna [] = "sienna"; + const char silver [] = "silver"; + const char skyblue [] = "skyblue"; + const char slateblue [] = "slateblue"; + const char slategray [] = "slategray"; + const char slategrey [] = "slategrey"; + const char snow [] = "snow"; + const char springgreen [] = "springgreen"; + const char steelblue [] = "steelblue"; + const char tan [] = "tan"; + const char teal [] = "teal"; + const char thistle [] = "thistle"; + const char tomato [] = "tomato"; + const char turquoise [] = "turquoise"; + const char violet [] = "violet"; + const char wheat [] = "wheat"; + const char white [] = "white"; + const char whitesmoke [] = "whitesmoke"; + const char yellow [] = "yellow"; + const char yellowgreen [] = "yellowgreen"; + const char rebeccapurple [] = "rebeccapurple"; + const char transparent [] = "transparent"; + } + + namespace Colors { + const ParserState color_table("[COLOR TABLE]"); + const Color aliceblue(color_table, 240, 248, 255, 1); + const Color antiquewhite(color_table, 250, 235, 215, 1); + const Color cyan(color_table, 0, 255, 255, 1); + const Color aqua(color_table, 0, 255, 255, 1); + const Color aquamarine(color_table, 127, 255, 212, 1); + const Color azure(color_table, 240, 255, 255, 1); + const Color beige(color_table, 245, 245, 220, 1); + const Color bisque(color_table, 255, 228, 196, 1); + const Color black(color_table, 0, 0, 0, 1); + const Color blanchedalmond(color_table, 255, 235, 205, 1); + const Color blue(color_table, 0, 0, 255, 1); + const Color blueviolet(color_table, 138, 43, 226, 1); + const Color brown(color_table, 165, 42, 42, 1); + const Color burlywood(color_table, 222, 184, 135, 1); + const Color cadetblue(color_table, 95, 158, 160, 1); + const Color chartreuse(color_table, 127, 255, 0, 1); + const Color chocolate(color_table, 210, 105, 30, 1); + const Color coral(color_table, 255, 127, 80, 1); + const Color cornflowerblue(color_table, 100, 149, 237, 1); + const Color cornsilk(color_table, 255, 248, 220, 1); + const Color crimson(color_table, 220, 20, 60, 1); + const Color darkblue(color_table, 0, 0, 139, 1); + const Color darkcyan(color_table, 0, 139, 139, 1); + const Color darkgoldenrod(color_table, 184, 134, 11, 1); + const Color darkgray(color_table, 169, 169, 169, 1); + const Color darkgrey(color_table, 169, 169, 169, 1); + const Color darkgreen(color_table, 0, 100, 0, 1); + const Color darkkhaki(color_table, 189, 183, 107, 1); + const Color darkmagenta(color_table, 139, 0, 139, 1); + const Color darkolivegreen(color_table, 85, 107, 47, 1); + const Color darkorange(color_table, 255, 140, 0, 1); + const Color darkorchid(color_table, 153, 50, 204, 1); + const Color darkred(color_table, 139, 0, 0, 1); + const Color darksalmon(color_table, 233, 150, 122, 1); + const Color darkseagreen(color_table, 143, 188, 143, 1); + const Color darkslateblue(color_table, 72, 61, 139, 1); + const Color darkslategray(color_table, 47, 79, 79, 1); + const Color darkslategrey(color_table, 47, 79, 79, 1); + const Color darkturquoise(color_table, 0, 206, 209, 1); + const Color darkviolet(color_table, 148, 0, 211, 1); + const Color deeppink(color_table, 255, 20, 147, 1); + const Color deepskyblue(color_table, 0, 191, 255, 1); + const Color dimgray(color_table, 105, 105, 105, 1); + const Color dimgrey(color_table, 105, 105, 105, 1); + const Color dodgerblue(color_table, 30, 144, 255, 1); + const Color firebrick(color_table, 178, 34, 34, 1); + const Color floralwhite(color_table, 255, 250, 240, 1); + const Color forestgreen(color_table, 34, 139, 34, 1); + const Color magenta(color_table, 255, 0, 255, 1); + const Color fuchsia(color_table, 255, 0, 255, 1); + const Color gainsboro(color_table, 220, 220, 220, 1); + const Color ghostwhite(color_table, 248, 248, 255, 1); + const Color gold(color_table, 255, 215, 0, 1); + const Color goldenrod(color_table, 218, 165, 32, 1); + const Color gray(color_table, 128, 128, 128, 1); + const Color grey(color_table, 128, 128, 128, 1); + const Color green(color_table, 0, 128, 0, 1); + const Color greenyellow(color_table, 173, 255, 47, 1); + const Color honeydew(color_table, 240, 255, 240, 1); + const Color hotpink(color_table, 255, 105, 180, 1); + const Color indianred(color_table, 205, 92, 92, 1); + const Color indigo(color_table, 75, 0, 130, 1); + const Color ivory(color_table, 255, 255, 240, 1); + const Color khaki(color_table, 240, 230, 140, 1); + const Color lavender(color_table, 230, 230, 250, 1); + const Color lavenderblush(color_table, 255, 240, 245, 1); + const Color lawngreen(color_table, 124, 252, 0, 1); + const Color lemonchiffon(color_table, 255, 250, 205, 1); + const Color lightblue(color_table, 173, 216, 230, 1); + const Color lightcoral(color_table, 240, 128, 128, 1); + const Color lightcyan(color_table, 224, 255, 255, 1); + const Color lightgoldenrodyellow(color_table, 250, 250, 210, 1); + const Color lightgray(color_table, 211, 211, 211, 1); + const Color lightgrey(color_table, 211, 211, 211, 1); + const Color lightgreen(color_table, 144, 238, 144, 1); + const Color lightpink(color_table, 255, 182, 193, 1); + const Color lightsalmon(color_table, 255, 160, 122, 1); + const Color lightseagreen(color_table, 32, 178, 170, 1); + const Color lightskyblue(color_table, 135, 206, 250, 1); + const Color lightslategray(color_table, 119, 136, 153, 1); + const Color lightslategrey(color_table, 119, 136, 153, 1); + const Color lightsteelblue(color_table, 176, 196, 222, 1); + const Color lightyellow(color_table, 255, 255, 224, 1); + const Color lime(color_table, 0, 255, 0, 1); + const Color limegreen(color_table, 50, 205, 50, 1); + const Color linen(color_table, 250, 240, 230, 1); + const Color maroon(color_table, 128, 0, 0, 1); + const Color mediumaquamarine(color_table, 102, 205, 170, 1); + const Color mediumblue(color_table, 0, 0, 205, 1); + const Color mediumorchid(color_table, 186, 85, 211, 1); + const Color mediumpurple(color_table, 147, 112, 219, 1); + const Color mediumseagreen(color_table, 60, 179, 113, 1); + const Color mediumslateblue(color_table, 123, 104, 238, 1); + const Color mediumspringgreen(color_table, 0, 250, 154, 1); + const Color mediumturquoise(color_table, 72, 209, 204, 1); + const Color mediumvioletred(color_table, 199, 21, 133, 1); + const Color midnightblue(color_table, 25, 25, 112, 1); + const Color mintcream(color_table, 245, 255, 250, 1); + const Color mistyrose(color_table, 255, 228, 225, 1); + const Color moccasin(color_table, 255, 228, 181, 1); + const Color navajowhite(color_table, 255, 222, 173, 1); + const Color navy(color_table, 0, 0, 128, 1); + const Color oldlace(color_table, 253, 245, 230, 1); + const Color olive(color_table, 128, 128, 0, 1); + const Color olivedrab(color_table, 107, 142, 35, 1); + const Color orange(color_table, 255, 165, 0, 1); + const Color orangered(color_table, 255, 69, 0, 1); + const Color orchid(color_table, 218, 112, 214, 1); + const Color palegoldenrod(color_table, 238, 232, 170, 1); + const Color palegreen(color_table, 152, 251, 152, 1); + const Color paleturquoise(color_table, 175, 238, 238, 1); + const Color palevioletred(color_table, 219, 112, 147, 1); + const Color papayawhip(color_table, 255, 239, 213, 1); + const Color peachpuff(color_table, 255, 218, 185, 1); + const Color peru(color_table, 205, 133, 63, 1); + const Color pink(color_table, 255, 192, 203, 1); + const Color plum(color_table, 221, 160, 221, 1); + const Color powderblue(color_table, 176, 224, 230, 1); + const Color purple(color_table, 128, 0, 128, 1); + const Color red(color_table, 255, 0, 0, 1); + const Color rosybrown(color_table, 188, 143, 143, 1); + const Color royalblue(color_table, 65, 105, 225, 1); + const Color saddlebrown(color_table, 139, 69, 19, 1); + const Color salmon(color_table, 250, 128, 114, 1); + const Color sandybrown(color_table, 244, 164, 96, 1); + const Color seagreen(color_table, 46, 139, 87, 1); + const Color seashell(color_table, 255, 245, 238, 1); + const Color sienna(color_table, 160, 82, 45, 1); + const Color silver(color_table, 192, 192, 192, 1); + const Color skyblue(color_table, 135, 206, 235, 1); + const Color slateblue(color_table, 106, 90, 205, 1); + const Color slategray(color_table, 112, 128, 144, 1); + const Color slategrey(color_table, 112, 128, 144, 1); + const Color snow(color_table, 255, 250, 250, 1); + const Color springgreen(color_table, 0, 255, 127, 1); + const Color steelblue(color_table, 70, 130, 180, 1); + const Color tan(color_table, 210, 180, 140, 1); + const Color teal(color_table, 0, 128, 128, 1); + const Color thistle(color_table, 216, 191, 216, 1); + const Color tomato(color_table, 255, 99, 71, 1); + const Color turquoise(color_table, 64, 224, 208, 1); + const Color violet(color_table, 238, 130, 238, 1); + const Color wheat(color_table, 245, 222, 179, 1); + const Color white(color_table, 255, 255, 255, 1); + const Color whitesmoke(color_table, 245, 245, 245, 1); + const Color yellow(color_table, 255, 255, 0, 1); + const Color yellowgreen(color_table, 154, 205, 50, 1); + const Color rebeccapurple(color_table, 102, 51, 153, 1); + const Color transparent(color_table, 0, 0, 0, 0); + } + + const std::map colors_to_names { + { 240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue }, + { 250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite }, + { 0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan }, + { 127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine }, + { 240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure }, + { 245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige }, + { 255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque }, + { 0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black }, + { 255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond }, + { 0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue }, + { 138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet }, + { 165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown }, + { 222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood }, + { 95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue }, + { 127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse }, + { 210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate }, + { 255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral }, + { 100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue }, + { 255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk }, + { 220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson }, + { 0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue }, + { 0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan }, + { 184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod }, + { 169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray }, + { 0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen }, + { 189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki }, + { 139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta }, + { 85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen }, + { 255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange }, + { 153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid }, + { 139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred }, + { 233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon }, + { 143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen }, + { 72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue }, + { 47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray }, + { 0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise }, + { 148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet }, + { 255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink }, + { 0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue }, + { 105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray }, + { 30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue }, + { 178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick }, + { 255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite }, + { 34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen }, + { 255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta }, + { 220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro }, + { 248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite }, + { 255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold }, + { 218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod }, + { 128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray }, + { 0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green }, + { 173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow }, + { 240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew }, + { 255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink }, + { 205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred }, + { 75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo }, + { 255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory }, + { 240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki }, + { 230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender }, + { 255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush }, + { 124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen }, + { 255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon }, + { 173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue }, + { 240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral }, + { 224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan }, + { 250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow }, + { 211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray }, + { 144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen }, + { 255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink }, + { 255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon }, + { 32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen }, + { 135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue }, + { 119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray }, + { 176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue }, + { 255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow }, + { 0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime }, + { 50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen }, + { 250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen }, + { 128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon }, + { 102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine }, + { 0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue }, + { 186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid }, + { 147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple }, + { 60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen }, + { 123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue }, + { 0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen }, + { 72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise }, + { 199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred }, + { 25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue }, + { 245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream }, + { 255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose }, + { 255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin }, + { 255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite }, + { 0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy }, + { 253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace }, + { 128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive }, + { 107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab }, + { 255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange }, + { 255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered }, + { 218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid }, + { 238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod }, + { 152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen }, + { 175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise }, + { 219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred }, + { 255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip }, + { 255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff }, + { 205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru }, + { 255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink }, + { 221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum }, + { 176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue }, + { 128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple }, + { 255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red }, + { 188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown }, + { 65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue }, + { 139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown }, + { 250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon }, + { 244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown }, + { 46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen }, + { 255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell }, + { 160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna }, + { 192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver }, + { 135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue }, + { 106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue }, + { 112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray }, + { 255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow }, + { 0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen }, + { 70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue }, + { 210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan }, + { 0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal }, + { 216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle }, + { 255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato }, + { 64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise }, + { 238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet }, + { 245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat }, + { 255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white }, + { 245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke }, + { 255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow }, + { 154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen }, + { 102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple } + }; + + const std::map names_to_colors + { + { ColorNames::aliceblue, &Colors::aliceblue }, + { ColorNames::antiquewhite, &Colors::antiquewhite }, + { ColorNames::cyan, &Colors::cyan }, + { ColorNames::aqua, &Colors::aqua }, + { ColorNames::aquamarine, &Colors::aquamarine }, + { ColorNames::azure, &Colors::azure }, + { ColorNames::beige, &Colors::beige }, + { ColorNames::bisque, &Colors::bisque }, + { ColorNames::black, &Colors::black }, + { ColorNames::blanchedalmond, &Colors::blanchedalmond }, + { ColorNames::blue, &Colors::blue }, + { ColorNames::blueviolet, &Colors::blueviolet }, + { ColorNames::brown, &Colors::brown }, + { ColorNames::burlywood, &Colors::burlywood }, + { ColorNames::cadetblue, &Colors::cadetblue }, + { ColorNames::chartreuse, &Colors::chartreuse }, + { ColorNames::chocolate, &Colors::chocolate }, + { ColorNames::coral, &Colors::coral }, + { ColorNames::cornflowerblue, &Colors::cornflowerblue }, + { ColorNames::cornsilk, &Colors::cornsilk }, + { ColorNames::crimson, &Colors::crimson }, + { ColorNames::darkblue, &Colors::darkblue }, + { ColorNames::darkcyan, &Colors::darkcyan }, + { ColorNames::darkgoldenrod, &Colors::darkgoldenrod }, + { ColorNames::darkgray, &Colors::darkgray }, + { ColorNames::darkgrey, &Colors::darkgrey }, + { ColorNames::darkgreen, &Colors::darkgreen }, + { ColorNames::darkkhaki, &Colors::darkkhaki }, + { ColorNames::darkmagenta, &Colors::darkmagenta }, + { ColorNames::darkolivegreen, &Colors::darkolivegreen }, + { ColorNames::darkorange, &Colors::darkorange }, + { ColorNames::darkorchid, &Colors::darkorchid }, + { ColorNames::darkred, &Colors::darkred }, + { ColorNames::darksalmon, &Colors::darksalmon }, + { ColorNames::darkseagreen, &Colors::darkseagreen }, + { ColorNames::darkslateblue, &Colors::darkslateblue }, + { ColorNames::darkslategray, &Colors::darkslategray }, + { ColorNames::darkslategrey, &Colors::darkslategrey }, + { ColorNames::darkturquoise, &Colors::darkturquoise }, + { ColorNames::darkviolet, &Colors::darkviolet }, + { ColorNames::deeppink, &Colors::deeppink }, + { ColorNames::deepskyblue, &Colors::deepskyblue }, + { ColorNames::dimgray, &Colors::dimgray }, + { ColorNames::dimgrey, &Colors::dimgrey }, + { ColorNames::dodgerblue, &Colors::dodgerblue }, + { ColorNames::firebrick, &Colors::firebrick }, + { ColorNames::floralwhite, &Colors::floralwhite }, + { ColorNames::forestgreen, &Colors::forestgreen }, + { ColorNames::magenta, &Colors::magenta }, + { ColorNames::fuchsia, &Colors::fuchsia }, + { ColorNames::gainsboro, &Colors::gainsboro }, + { ColorNames::ghostwhite, &Colors::ghostwhite }, + { ColorNames::gold, &Colors::gold }, + { ColorNames::goldenrod, &Colors::goldenrod }, + { ColorNames::gray, &Colors::gray }, + { ColorNames::grey, &Colors::grey }, + { ColorNames::green, &Colors::green }, + { ColorNames::greenyellow, &Colors::greenyellow }, + { ColorNames::honeydew, &Colors::honeydew }, + { ColorNames::hotpink, &Colors::hotpink }, + { ColorNames::indianred, &Colors::indianred }, + { ColorNames::indigo, &Colors::indigo }, + { ColorNames::ivory, &Colors::ivory }, + { ColorNames::khaki, &Colors::khaki }, + { ColorNames::lavender, &Colors::lavender }, + { ColorNames::lavenderblush, &Colors::lavenderblush }, + { ColorNames::lawngreen, &Colors::lawngreen }, + { ColorNames::lemonchiffon, &Colors::lemonchiffon }, + { ColorNames::lightblue, &Colors::lightblue }, + { ColorNames::lightcoral, &Colors::lightcoral }, + { ColorNames::lightcyan, &Colors::lightcyan }, + { ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow }, + { ColorNames::lightgray, &Colors::lightgray }, + { ColorNames::lightgrey, &Colors::lightgrey }, + { ColorNames::lightgreen, &Colors::lightgreen }, + { ColorNames::lightpink, &Colors::lightpink }, + { ColorNames::lightsalmon, &Colors::lightsalmon }, + { ColorNames::lightseagreen, &Colors::lightseagreen }, + { ColorNames::lightskyblue, &Colors::lightskyblue }, + { ColorNames::lightslategray, &Colors::lightslategray }, + { ColorNames::lightslategrey, &Colors::lightslategrey }, + { ColorNames::lightsteelblue, &Colors::lightsteelblue }, + { ColorNames::lightyellow, &Colors::lightyellow }, + { ColorNames::lime, &Colors::lime }, + { ColorNames::limegreen, &Colors::limegreen }, + { ColorNames::linen, &Colors::linen }, + { ColorNames::maroon, &Colors::maroon }, + { ColorNames::mediumaquamarine, &Colors::mediumaquamarine }, + { ColorNames::mediumblue, &Colors::mediumblue }, + { ColorNames::mediumorchid, &Colors::mediumorchid }, + { ColorNames::mediumpurple, &Colors::mediumpurple }, + { ColorNames::mediumseagreen, &Colors::mediumseagreen }, + { ColorNames::mediumslateblue, &Colors::mediumslateblue }, + { ColorNames::mediumspringgreen, &Colors::mediumspringgreen }, + { ColorNames::mediumturquoise, &Colors::mediumturquoise }, + { ColorNames::mediumvioletred, &Colors::mediumvioletred }, + { ColorNames::midnightblue, &Colors::midnightblue }, + { ColorNames::mintcream, &Colors::mintcream }, + { ColorNames::mistyrose, &Colors::mistyrose }, + { ColorNames::moccasin, &Colors::moccasin }, + { ColorNames::navajowhite, &Colors::navajowhite }, + { ColorNames::navy, &Colors::navy }, + { ColorNames::oldlace, &Colors::oldlace }, + { ColorNames::olive, &Colors::olive }, + { ColorNames::olivedrab, &Colors::olivedrab }, + { ColorNames::orange, &Colors::orange }, + { ColorNames::orangered, &Colors::orangered }, + { ColorNames::orchid, &Colors::orchid }, + { ColorNames::palegoldenrod, &Colors::palegoldenrod }, + { ColorNames::palegreen, &Colors::palegreen }, + { ColorNames::paleturquoise, &Colors::paleturquoise }, + { ColorNames::palevioletred, &Colors::palevioletred }, + { ColorNames::papayawhip, &Colors::papayawhip }, + { ColorNames::peachpuff, &Colors::peachpuff }, + { ColorNames::peru, &Colors::peru }, + { ColorNames::pink, &Colors::pink }, + { ColorNames::plum, &Colors::plum }, + { ColorNames::powderblue, &Colors::powderblue }, + { ColorNames::purple, &Colors::purple }, + { ColorNames::red, &Colors::red }, + { ColorNames::rosybrown, &Colors::rosybrown }, + { ColorNames::royalblue, &Colors::royalblue }, + { ColorNames::saddlebrown, &Colors::saddlebrown }, + { ColorNames::salmon, &Colors::salmon }, + { ColorNames::sandybrown, &Colors::sandybrown }, + { ColorNames::seagreen, &Colors::seagreen }, + { ColorNames::seashell, &Colors::seashell }, + { ColorNames::sienna, &Colors::sienna }, + { ColorNames::silver, &Colors::silver }, + { ColorNames::skyblue, &Colors::skyblue }, + { ColorNames::slateblue, &Colors::slateblue }, + { ColorNames::slategray, &Colors::slategray }, + { ColorNames::slategrey, &Colors::slategrey }, + { ColorNames::snow, &Colors::snow }, + { ColorNames::springgreen, &Colors::springgreen }, + { ColorNames::steelblue, &Colors::steelblue }, + { ColorNames::tan, &Colors::tan }, + { ColorNames::teal, &Colors::teal }, + { ColorNames::thistle, &Colors::thistle }, + { ColorNames::tomato, &Colors::tomato }, + { ColorNames::turquoise, &Colors::turquoise }, + { ColorNames::violet, &Colors::violet }, + { ColorNames::wheat, &Colors::wheat }, + { ColorNames::white, &Colors::white }, + { ColorNames::whitesmoke, &Colors::whitesmoke }, + { ColorNames::yellow, &Colors::yellow }, + { ColorNames::yellowgreen, &Colors::yellowgreen }, + { ColorNames::rebeccapurple, &Colors::rebeccapurple }, + { ColorNames::transparent, &Colors::transparent } + }; + + Color_Ptr_Const name_to_color(const char* key) + { + return name_to_color(std::string(key)); + } + + Color_Ptr_Const name_to_color(const std::string& key) + { + // case insensitive lookup. See #2462 + std::string lower{key}; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + auto p = names_to_colors.find(lower.c_str()); + if (p != names_to_colors.end()) { + return p->second; + } + return 0; + } + + const char* color_to_name(const int key) + { + auto p = colors_to_names.find(key); + if (p != colors_to_names.end()) { + return p->second; + } + return 0; + } + + const char* color_to_name(const double key) + { + return color_to_name((int)key); + } + + const char* color_to_name(const Color& c) + { + double key = c.r() * 0x10000 + + c.g() * 0x100 + + c.b(); + return color_to_name(key); + } + +} diff --git a/src/color_maps.hpp b/src/color_maps.hpp new file mode 100644 index 000000000..d4fd41607 --- /dev/null +++ b/src/color_maps.hpp @@ -0,0 +1,331 @@ + +#ifndef SASS_COLOR_MAPS_H +#define SASS_COLOR_MAPS_H + +#include +#include "ast.hpp" + +namespace Sass { + + struct map_cmp_str + { + bool operator()(char const *a, char const *b) const + { + return std::strcmp(a, b) < 0; + } + }; + + namespace ColorNames + { + extern const char aliceblue[]; + extern const char antiquewhite[]; + extern const char cyan[]; + extern const char aqua[]; + extern const char aquamarine[]; + extern const char azure[]; + extern const char beige[]; + extern const char bisque[]; + extern const char black[]; + extern const char blanchedalmond[]; + extern const char blue[]; + extern const char blueviolet[]; + extern const char brown[]; + extern const char burlywood[]; + extern const char cadetblue[]; + extern const char chartreuse[]; + extern const char chocolate[]; + extern const char coral[]; + extern const char cornflowerblue[]; + extern const char cornsilk[]; + extern const char crimson[]; + extern const char darkblue[]; + extern const char darkcyan[]; + extern const char darkgoldenrod[]; + extern const char darkgray[]; + extern const char darkgrey[]; + extern const char darkgreen[]; + extern const char darkkhaki[]; + extern const char darkmagenta[]; + extern const char darkolivegreen[]; + extern const char darkorange[]; + extern const char darkorchid[]; + extern const char darkred[]; + extern const char darksalmon[]; + extern const char darkseagreen[]; + extern const char darkslateblue[]; + extern const char darkslategray[]; + extern const char darkslategrey[]; + extern const char darkturquoise[]; + extern const char darkviolet[]; + extern const char deeppink[]; + extern const char deepskyblue[]; + extern const char dimgray[]; + extern const char dimgrey[]; + extern const char dodgerblue[]; + extern const char firebrick[]; + extern const char floralwhite[]; + extern const char forestgreen[]; + extern const char magenta[]; + extern const char fuchsia[]; + extern const char gainsboro[]; + extern const char ghostwhite[]; + extern const char gold[]; + extern const char goldenrod[]; + extern const char gray[]; + extern const char grey[]; + extern const char green[]; + extern const char greenyellow[]; + extern const char honeydew[]; + extern const char hotpink[]; + extern const char indianred[]; + extern const char indigo[]; + extern const char ivory[]; + extern const char khaki[]; + extern const char lavender[]; + extern const char lavenderblush[]; + extern const char lawngreen[]; + extern const char lemonchiffon[]; + extern const char lightblue[]; + extern const char lightcoral[]; + extern const char lightcyan[]; + extern const char lightgoldenrodyellow[]; + extern const char lightgray[]; + extern const char lightgrey[]; + extern const char lightgreen[]; + extern const char lightpink[]; + extern const char lightsalmon[]; + extern const char lightseagreen[]; + extern const char lightskyblue[]; + extern const char lightslategray[]; + extern const char lightslategrey[]; + extern const char lightsteelblue[]; + extern const char lightyellow[]; + extern const char lime[]; + extern const char limegreen[]; + extern const char linen[]; + extern const char maroon[]; + extern const char mediumaquamarine[]; + extern const char mediumblue[]; + extern const char mediumorchid[]; + extern const char mediumpurple[]; + extern const char mediumseagreen[]; + extern const char mediumslateblue[]; + extern const char mediumspringgreen[]; + extern const char mediumturquoise[]; + extern const char mediumvioletred[]; + extern const char midnightblue[]; + extern const char mintcream[]; + extern const char mistyrose[]; + extern const char moccasin[]; + extern const char navajowhite[]; + extern const char navy[]; + extern const char oldlace[]; + extern const char olive[]; + extern const char olivedrab[]; + extern const char orange[]; + extern const char orangered[]; + extern const char orchid[]; + extern const char palegoldenrod[]; + extern const char palegreen[]; + extern const char paleturquoise[]; + extern const char palevioletred[]; + extern const char papayawhip[]; + extern const char peachpuff[]; + extern const char peru[]; + extern const char pink[]; + extern const char plum[]; + extern const char powderblue[]; + extern const char purple[]; + extern const char red[]; + extern const char rosybrown[]; + extern const char royalblue[]; + extern const char saddlebrown[]; + extern const char salmon[]; + extern const char sandybrown[]; + extern const char seagreen[]; + extern const char seashell[]; + extern const char sienna[]; + extern const char silver[]; + extern const char skyblue[]; + extern const char slateblue[]; + extern const char slategray[]; + extern const char slategrey[]; + extern const char snow[]; + extern const char springgreen[]; + extern const char steelblue[]; + extern const char tan[]; + extern const char teal[]; + extern const char thistle[]; + extern const char tomato[]; + extern const char turquoise[]; + extern const char violet[]; + extern const char wheat[]; + extern const char white[]; + extern const char whitesmoke[]; + extern const char yellow[]; + extern const char yellowgreen[]; + extern const char rebeccapurple[]; + extern const char transparent[]; + } + + namespace Colors { + extern const Color aliceblue; + extern const Color antiquewhite; + extern const Color cyan; + extern const Color aqua; + extern const Color aquamarine; + extern const Color azure; + extern const Color beige; + extern const Color bisque; + extern const Color black; + extern const Color blanchedalmond; + extern const Color blue; + extern const Color blueviolet; + extern const Color brown; + extern const Color burlywood; + extern const Color cadetblue; + extern const Color chartreuse; + extern const Color chocolate; + extern const Color coral; + extern const Color cornflowerblue; + extern const Color cornsilk; + extern const Color crimson; + extern const Color darkblue; + extern const Color darkcyan; + extern const Color darkgoldenrod; + extern const Color darkgray; + extern const Color darkgrey; + extern const Color darkgreen; + extern const Color darkkhaki; + extern const Color darkmagenta; + extern const Color darkolivegreen; + extern const Color darkorange; + extern const Color darkorchid; + extern const Color darkred; + extern const Color darksalmon; + extern const Color darkseagreen; + extern const Color darkslateblue; + extern const Color darkslategray; + extern const Color darkslategrey; + extern const Color darkturquoise; + extern const Color darkviolet; + extern const Color deeppink; + extern const Color deepskyblue; + extern const Color dimgray; + extern const Color dimgrey; + extern const Color dodgerblue; + extern const Color firebrick; + extern const Color floralwhite; + extern const Color forestgreen; + extern const Color magenta; + extern const Color fuchsia; + extern const Color gainsboro; + extern const Color ghostwhite; + extern const Color gold; + extern const Color goldenrod; + extern const Color gray; + extern const Color grey; + extern const Color green; + extern const Color greenyellow; + extern const Color honeydew; + extern const Color hotpink; + extern const Color indianred; + extern const Color indigo; + extern const Color ivory; + extern const Color khaki; + extern const Color lavender; + extern const Color lavenderblush; + extern const Color lawngreen; + extern const Color lemonchiffon; + extern const Color lightblue; + extern const Color lightcoral; + extern const Color lightcyan; + extern const Color lightgoldenrodyellow; + extern const Color lightgray; + extern const Color lightgrey; + extern const Color lightgreen; + extern const Color lightpink; + extern const Color lightsalmon; + extern const Color lightseagreen; + extern const Color lightskyblue; + extern const Color lightslategray; + extern const Color lightslategrey; + extern const Color lightsteelblue; + extern const Color lightyellow; + extern const Color lime; + extern const Color limegreen; + extern const Color linen; + extern const Color maroon; + extern const Color mediumaquamarine; + extern const Color mediumblue; + extern const Color mediumorchid; + extern const Color mediumpurple; + extern const Color mediumseagreen; + extern const Color mediumslateblue; + extern const Color mediumspringgreen; + extern const Color mediumturquoise; + extern const Color mediumvioletred; + extern const Color midnightblue; + extern const Color mintcream; + extern const Color mistyrose; + extern const Color moccasin; + extern const Color navajowhite; + extern const Color navy; + extern const Color oldlace; + extern const Color olive; + extern const Color olivedrab; + extern const Color orange; + extern const Color orangered; + extern const Color orchid; + extern const Color palegoldenrod; + extern const Color palegreen; + extern const Color paleturquoise; + extern const Color palevioletred; + extern const Color papayawhip; + extern const Color peachpuff; + extern const Color peru; + extern const Color pink; + extern const Color plum; + extern const Color powderblue; + extern const Color purple; + extern const Color red; + extern const Color rosybrown; + extern const Color royalblue; + extern const Color saddlebrown; + extern const Color salmon; + extern const Color sandybrown; + extern const Color seagreen; + extern const Color seashell; + extern const Color sienna; + extern const Color silver; + extern const Color skyblue; + extern const Color slateblue; + extern const Color slategray; + extern const Color slategrey; + extern const Color snow; + extern const Color springgreen; + extern const Color steelblue; + extern const Color tan; + extern const Color teal; + extern const Color thistle; + extern const Color tomato; + extern const Color turquoise; + extern const Color violet; + extern const Color wheat; + extern const Color white; + extern const Color whitesmoke; + extern const Color yellow; + extern const Color yellowgreen; + extern const Color rebeccapurple; + extern const Color transparent; + } + + Color_Ptr_Const name_to_color(const char*); + Color_Ptr_Const name_to_color(const std::string&); + const char* color_to_name(const int); + const char* color_to_name(const Color&); + const char* color_to_name(const double); + +} + +#endif diff --git a/src/constants.cpp b/src/constants.cpp new file mode 100644 index 000000000..0ba28e20c --- /dev/null +++ b/src/constants.cpp @@ -0,0 +1,179 @@ +#include "sass.hpp" +#include "constants.hpp" + +namespace Sass { + namespace Constants { + + extern const unsigned long MaxCallStack = 1024; + + // https://github.com/sass/libsass/issues/592 + // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity + // https://github.com/sass/sass/issues/1495#issuecomment-61189114 + extern const unsigned long Specificity_Star = 0; + extern const unsigned long Specificity_Universal = 0; + extern const unsigned long Specificity_Element = 1; + extern const unsigned long Specificity_Base = 1000; + extern const unsigned long Specificity_Class = 1000; + extern const unsigned long Specificity_Attr = 1000; + extern const unsigned long Specificity_Pseudo = 1000; + extern const unsigned long Specificity_ID = 1000000; + + // sass keywords + extern const char at_root_kwd[] = "@at-root"; + extern const char import_kwd[] = "@import"; + extern const char mixin_kwd[] = "@mixin"; + extern const char function_kwd[] = "@function"; + extern const char return_kwd[] = "@return"; + extern const char include_kwd[] = "@include"; + extern const char content_kwd[] = "@content"; + extern const char extend_kwd[] = "@extend"; + extern const char if_kwd[] = "@if"; + extern const char else_kwd[] = "@else"; + extern const char if_after_else_kwd[] = "if"; + extern const char for_kwd[] = "@for"; + extern const char from_kwd[] = "from"; + extern const char to_kwd[] = "to"; + extern const char through_kwd[] = "through"; + extern const char each_kwd[] = "@each"; + extern const char in_kwd[] = "in"; + extern const char while_kwd[] = "@while"; + extern const char warn_kwd[] = "@warn"; + extern const char error_kwd[] = "@error"; + extern const char debug_kwd[] = "@debug"; + extern const char default_kwd[] = "default"; + extern const char global_kwd[] = "global"; + extern const char null_kwd[] = "null"; + extern const char optional_kwd[] = "optional"; + extern const char with_kwd[] = "with"; + extern const char without_kwd[] = "without"; + extern const char all_kwd[] = "all"; + extern const char rule_kwd[] = "rule"; + + // css standard units + extern const char em_kwd[] = "em"; + extern const char ex_kwd[] = "ex"; + extern const char px_kwd[] = "px"; + extern const char cm_kwd[] = "cm"; + extern const char mm_kwd[] = "mm"; + extern const char pt_kwd[] = "pt"; + extern const char pc_kwd[] = "pc"; + extern const char deg_kwd[] = "deg"; + extern const char rad_kwd[] = "rad"; + extern const char grad_kwd[] = "grad"; + extern const char turn_kwd[] = "turn"; + extern const char ms_kwd[] = "ms"; + extern const char s_kwd[] = "s"; + extern const char Hz_kwd[] = "Hz"; + extern const char kHz_kwd[] = "kHz"; + + // vendor prefixes + extern const char vendor_opera_kwd[] = "-o-"; + extern const char vendor_webkit_kwd[] = "-webkit-"; + extern const char vendor_mozilla_kwd[] = "-moz-"; + extern const char vendor_ms_kwd[] = "-ms-"; + extern const char vendor_khtml_kwd[] = "-khtml-"; + + // css functions and keywords + extern const char charset_kwd[] = "@charset"; + extern const char media_kwd[] = "@media"; + extern const char supports_kwd[] = "@supports"; + extern const char keyframes_kwd[] = "keyframes"; + extern const char only_kwd[] = "only"; + extern const char rgb_fn_kwd[] = "rgb("; + extern const char url_fn_kwd[] = "url("; + extern const char url_kwd[] = "url"; + // extern const char url_prefix_fn_kwd[] = "url-prefix("; + extern const char important_kwd[] = "important"; + extern const char pseudo_not_fn_kwd[] = ":not("; + extern const char even_kwd[] = "even"; + extern const char odd_kwd[] = "odd"; + extern const char progid_kwd[] = "progid"; + extern const char expression_kwd[] = "expression"; + extern const char calc_fn_kwd[] = "calc"; + + extern const char almost_any_value_class[] = "\"'#!;{}"; + + // css selector keywords + extern const char sel_deep_kwd[] = "/deep/"; + + // css attribute-matching operators + extern const char tilde_equal[] = "~="; + extern const char pipe_equal[] = "|="; + extern const char caret_equal[] = "^="; + extern const char dollar_equal[] = "$="; + extern const char star_equal[] = "*="; + + // relational & logical operators and constants + extern const char and_kwd[] = "and"; + extern const char or_kwd[] = "or"; + extern const char not_kwd[] = "not"; + extern const char gt[] = ">"; + extern const char gte[] = ">="; + extern const char lt[] = "<"; + extern const char lte[] = "<="; + extern const char eq[] = "=="; + extern const char neq[] = "!="; + extern const char true_kwd[] = "true"; + extern const char false_kwd[] = "false"; + + // miscellaneous punctuation and delimiters + extern const char percent_str[] = "%"; + extern const char empty_str[] = ""; + extern const char slash_slash[] = "//"; + extern const char slash_star[] = "/*"; + extern const char star_slash[] = "*/"; + extern const char hash_lbrace[] = "#{"; + extern const char rbrace[] = "}"; + extern const char rparen[] = ")"; + extern const char sign_chars[] = "-+"; + extern const char op_chars[] = "-+"; + extern const char hyphen[] = "-"; + extern const char ellipsis[] = "..."; + // extern const char url_space_chars[] = " \t\r\n\f"; + // type names + extern const char numeric_name[] = "numeric value"; + extern const char number_name[] = "number"; + extern const char percentage_name[] = "percentage"; + extern const char dimension_name[] = "numeric dimension"; + extern const char string_name[] = "string"; + extern const char bool_name[] = "bool"; + extern const char color_name[] = "color"; + extern const char list_name[] = "list"; + extern const char map_name[] = "map"; + extern const char arglist_name[] = "arglist"; + + // constants for uri parsing (RFC 3986 Appendix A.) + extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; + extern const char real_uri_chars[] = "#%&"; + + // some specific constant character classes + // they must be static to be useable by lexer + extern const char static_ops[] = "*/%"; + // some character classes for the parser + extern const char selector_list_delims[] = "){};!"; + extern const char complex_selector_delims[] = ",){};!"; + extern const char selector_combinator_ops[] = "+~>"; + // optional modifiers for alternative compare context + extern const char attribute_compare_modifiers[] = "~|^$*"; + extern const char selector_lookahead_ops[] = "*&%,()[]"; + + // byte order marks + // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) + extern const unsigned char utf_8_bom[] = { 0xEF, 0xBB, 0xBF }; + extern const unsigned char utf_16_bom_be[] = { 0xFE, 0xFF }; + extern const unsigned char utf_16_bom_le[] = { 0xFF, 0xFE }; + extern const unsigned char utf_32_bom_be[] = { 0x00, 0x00, 0xFE, 0xFF }; + extern const unsigned char utf_32_bom_le[] = { 0xFF, 0xFE, 0x00, 0x00 }; + extern const unsigned char utf_7_bom_1[] = { 0x2B, 0x2F, 0x76, 0x38 }; + extern const unsigned char utf_7_bom_2[] = { 0x2B, 0x2F, 0x76, 0x39 }; + extern const unsigned char utf_7_bom_3[] = { 0x2B, 0x2F, 0x76, 0x2B }; + extern const unsigned char utf_7_bom_4[] = { 0x2B, 0x2F, 0x76, 0x2F }; + extern const unsigned char utf_7_bom_5[] = { 0x2B, 0x2F, 0x76, 0x38, 0x2D }; + extern const unsigned char utf_1_bom[] = { 0xF7, 0x64, 0x4C }; + extern const unsigned char utf_ebcdic_bom[] = { 0xDD, 0x73, 0x66, 0x73 }; + extern const unsigned char scsu_bom[] = { 0x0E, 0xFE, 0xFF }; + extern const unsigned char bocu_1_bom[] = { 0xFB, 0xEE, 0x28 }; + extern const unsigned char gb_18030_bom[] = { 0x84, 0x31, 0x95, 0x33 }; + + } +} diff --git a/src/constants.hpp b/src/constants.hpp new file mode 100644 index 000000000..4fe93571e --- /dev/null +++ b/src/constants.hpp @@ -0,0 +1,181 @@ +#ifndef SASS_CONSTANTS_H +#define SASS_CONSTANTS_H + +namespace Sass { + namespace Constants { + + // The maximum call stack that can be created + extern const unsigned long MaxCallStack; + + // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity + // The following list of selectors is by increasing specificity: + extern const unsigned long Specificity_Star; + extern const unsigned long Specificity_Universal; + extern const unsigned long Specificity_Element; + extern const unsigned long Specificity_Base; + extern const unsigned long Specificity_Class; + extern const unsigned long Specificity_Attr; + extern const unsigned long Specificity_Pseudo; + extern const unsigned long Specificity_ID; + + // sass keywords + extern const char at_root_kwd[]; + extern const char import_kwd[]; + extern const char mixin_kwd[]; + extern const char function_kwd[]; + extern const char return_kwd[]; + extern const char include_kwd[]; + extern const char content_kwd[]; + extern const char extend_kwd[]; + extern const char if_kwd[]; + extern const char else_kwd[]; + extern const char if_after_else_kwd[]; + extern const char for_kwd[]; + extern const char from_kwd[]; + extern const char to_kwd[]; + extern const char through_kwd[]; + extern const char each_kwd[]; + extern const char in_kwd[]; + extern const char while_kwd[]; + extern const char warn_kwd[]; + extern const char error_kwd[]; + extern const char debug_kwd[]; + extern const char default_kwd[]; + extern const char global_kwd[]; + extern const char null_kwd[]; + extern const char optional_kwd[]; + extern const char with_kwd[]; + extern const char without_kwd[]; + extern const char all_kwd[]; + extern const char rule_kwd[]; + + // css standard units + extern const char em_kwd[]; + extern const char ex_kwd[]; + extern const char px_kwd[]; + extern const char cm_kwd[]; + extern const char mm_kwd[]; + extern const char pt_kwd[]; + extern const char pc_kwd[]; + extern const char deg_kwd[]; + extern const char rad_kwd[]; + extern const char grad_kwd[]; + extern const char turn_kwd[]; + extern const char ms_kwd[]; + extern const char s_kwd[]; + extern const char Hz_kwd[]; + extern const char kHz_kwd[]; + + // vendor prefixes + extern const char vendor_opera_kwd[]; + extern const char vendor_webkit_kwd[]; + extern const char vendor_mozilla_kwd[]; + extern const char vendor_ms_kwd[]; + extern const char vendor_khtml_kwd[]; + + // css functions and keywords + extern const char charset_kwd[]; + extern const char media_kwd[]; + extern const char supports_kwd[]; + extern const char keyframes_kwd[]; + extern const char only_kwd[]; + extern const char rgb_fn_kwd[]; + extern const char url_fn_kwd[]; + extern const char url_kwd[]; + // extern const char url_prefix_fn_kwd[]; + extern const char important_kwd[]; + extern const char pseudo_not_fn_kwd[]; + extern const char even_kwd[]; + extern const char odd_kwd[]; + extern const char progid_kwd[]; + extern const char expression_kwd[]; + extern const char calc_fn_kwd[]; + + // char classes for "regular expressions" + extern const char almost_any_value_class[]; + + // css selector keywords + extern const char sel_deep_kwd[]; + + // css attribute-matching operators + extern const char tilde_equal[]; + extern const char pipe_equal[]; + extern const char caret_equal[]; + extern const char dollar_equal[]; + extern const char star_equal[]; + + // relational & logical operators and constants + extern const char and_kwd[]; + extern const char or_kwd[]; + extern const char not_kwd[]; + extern const char gt[]; + extern const char gte[]; + extern const char lt[]; + extern const char lte[]; + extern const char eq[]; + extern const char neq[]; + extern const char true_kwd[]; + extern const char false_kwd[]; + + // miscellaneous punctuation and delimiters + extern const char percent_str[]; + extern const char empty_str[]; + extern const char slash_slash[]; + extern const char slash_star[]; + extern const char star_slash[]; + extern const char hash_lbrace[]; + extern const char rbrace[]; + extern const char rparen[]; + extern const char sign_chars[]; + extern const char op_chars[]; + extern const char hyphen[]; + extern const char ellipsis[]; + // extern const char url_space_chars[]; + + // type names + extern const char numeric_name[]; + extern const char number_name[]; + extern const char percentage_name[]; + extern const char dimension_name[]; + extern const char string_name[]; + extern const char bool_name[]; + extern const char color_name[]; + extern const char list_name[]; + extern const char map_name[]; + extern const char arglist_name[]; + + // constants for uri parsing (RFC 3986 Appendix A.) + extern const char uri_chars[]; + extern const char real_uri_chars[]; + + // some specific constant character classes + // they must be static to be useable by lexer + extern const char static_ops[]; + extern const char selector_list_delims[]; + extern const char complex_selector_delims[]; + extern const char selector_combinator_ops[]; + extern const char attribute_compare_modifiers[]; + extern const char selector_lookahead_ops[]; + + // byte order marks + // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) + extern const unsigned char utf_8_bom[]; + extern const unsigned char utf_16_bom_be[]; + extern const unsigned char utf_16_bom_le[]; + extern const unsigned char utf_32_bom_be[]; + extern const unsigned char utf_32_bom_le[]; + extern const unsigned char utf_7_bom_1[]; + extern const unsigned char utf_7_bom_2[]; + extern const unsigned char utf_7_bom_3[]; + extern const unsigned char utf_7_bom_4[]; + extern const unsigned char utf_7_bom_5[]; + extern const unsigned char utf_1_bom[]; + extern const unsigned char utf_ebcdic_bom[]; + extern const unsigned char scsu_bom[]; + extern const unsigned char bocu_1_bom[]; + extern const unsigned char gb_18030_bom[]; + + } +} + +#endif diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 000000000..dae2cbd75 --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,880 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "util.hpp" +#include "sass.h" +#include "context.hpp" +#include "plugins.hpp" +#include "constants.hpp" +#include "parser.hpp" +#include "file.hpp" +#include "inspect.hpp" +#include "output.hpp" +#include "expand.hpp" +#include "eval.hpp" +#include "check_nesting.hpp" +#include "cssize.hpp" +#include "listize.hpp" +#include "extend.hpp" +#include "remove_placeholders.hpp" +#include "functions.hpp" +#include "sass_functions.hpp" +#include "backtrace.hpp" +#include "sass2scss.h" +#include "prelexer.hpp" +#include "emitter.hpp" + +namespace Sass { + using namespace Constants; + using namespace File; + using namespace Sass; + + inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) + { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } + + static std::string safe_input(const char* in_path) + { + // enforce some safe defaults + // used to create relative file links + std::string safe_path(in_path ? in_path : ""); + return safe_path == "" ? "stdin" : safe_path; + } + + static std::string safe_output(const char* out_path, const std::string& input_path = "") + { + std::string safe_path(out_path ? out_path : ""); + // maybe we can extract an output path from input path + if (safe_path == "" && input_path != "") { + int lastindex = static_cast(input_path.find_last_of(".")); + return (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css"; + } + // enforce some safe defaults + // used to create relative file links + return safe_path == "" ? "stdout" : safe_path; + } + + Context::Context(struct Sass_Context& c_ctx) + : CWD(File::get_cwd()), + c_options(c_ctx), + entry_path(""), + head_imports(0), + plugins(), + emitter(c_options), + + ast_gc(), + strings(), + resources(), + sheets(), + subset_map(), + import_stack(), + callee_stack(), + traces(), + c_compiler(NULL), + + c_headers (std::vector()), + c_importers (std::vector()), + c_functions (std::vector()), + + indent (safe_str(c_options.indent, " ")), + linefeed (safe_str(c_options.linefeed, "\n")), + + input_path (make_canonical_path(safe_input(c_options.input_path))), + output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), + source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), + source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) + + { + + // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. + // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. + // include_paths.push_back(CWD); + + // collect more paths from different options + collect_include_paths(c_options.include_path); + collect_include_paths(c_options.include_paths); + collect_plugin_paths(c_options.plugin_path); + collect_plugin_paths(c_options.plugin_paths); + + // load plugins and register custom behaviors + for(auto plug : plugin_paths) plugins.load_plugins(plug); + for(auto fn : plugins.get_headers()) c_headers.push_back(fn); + for(auto fn : plugins.get_importers()) c_importers.push_back(fn); + for(auto fn : plugins.get_functions()) c_functions.push_back(fn); + + // sort the items by priority (lowest first) + sort (c_headers.begin(), c_headers.end(), sort_importers); + sort (c_importers.begin(), c_importers.end(), sort_importers); + + emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); + + } + + void Context::add_c_function(Sass_Function_Entry function) + { + c_functions.push_back(function); + } + void Context::add_c_header(Sass_Importer_Entry header) + { + c_headers.push_back(header); + // need to sort the array afterwards (no big deal) + sort (c_headers.begin(), c_headers.end(), sort_importers); + } + void Context::add_c_importer(Sass_Importer_Entry importer) + { + c_importers.push_back(importer); + // need to sort the array afterwards (no big deal) + sort (c_importers.begin(), c_importers.end(), sort_importers); + } + + Context::~Context() + { + // resources were allocated by malloc + for (size_t i = 0; i < resources.size(); ++i) { + free(resources[i].contents); + free(resources[i].srcmap); + } + // free all strings we kept alive during compiler execution + for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); + // everything that gets put into sources will be freed by us + // this shouldn't have anything in it anyway!? + for (size_t m = 0; m < import_stack.size(); ++m) { + sass_import_take_source(import_stack[m]); + sass_import_take_srcmap(import_stack[m]); + sass_delete_import(import_stack[m]); + } + // clear inner structures (vectors) and input source + resources.clear(); import_stack.clear(); + subset_map.clear(), sheets.clear(); + } + + Data_Context::~Data_Context() + { + // --> this will be freed by resources + // make sure we free the source even if not processed! + // if (resources.size() == 0 && source_c_str) free(source_c_str); + // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); + // source_c_str = 0; srcmap_c_str = 0; + } + + File_Context::~File_Context() + { + } + + void Context::collect_include_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + } + } + + void Context::collect_include_paths(string_list* paths_array) + { + while (paths_array) + { + collect_include_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + void Context::collect_plugin_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + } + } + + void Context::collect_plugin_paths(string_list* paths_array) + { + while (paths_array) + { + collect_plugin_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + // resolve the imp_path in base_path or include_paths + // looks for alternatives and returns a list from one directory + std::vector Context::find_includes(const Importer& import) + { + // make sure we resolve against an absolute path + std::string base_path(rel2abs(import.base_path)); + // first try to resolve the load path relative to the base path + std::vector vec(resolve_includes(base_path, import.imp_path)); + // then search in every include path (but only if nothing found yet) + for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) + { + // call resolve_includes and individual base path and append all results + std::vector resolved(resolve_includes(include_paths[i], import.imp_path)); + if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); + } + // return vector + return vec; + } + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res) + { + + // do not parse same resource twice + // maybe raise an error in this case + // if (sheets.count(inc.abs_path)) { + // free(res.contents); free(res.srcmap); + // throw std::runtime_error("duplicate resource registered"); + // return; + // } + + // get index for this resource + size_t idx = resources.size(); + + // tell emitter about new resource + emitter.add_source_index(idx); + + // put resources under our control + // the memory will be freed later + resources.push_back(res); + + // add a relative link to the working directory + included_files.push_back(inc.abs_path); + // add a relative link to the source map output file + srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); + + // get pointer to the loaded content + Sass_Import_Entry import = sass_make_import( + inc.imp_path.c_str(), + inc.abs_path.c_str(), + res.contents, + res.srcmap + ); + // add the entry to the stack + import_stack.push_back(import); + + // get pointer to the loaded content + const char* contents = resources[idx].contents; + // keep a copy of the path around (for parserstates) + // ToDo: we clean it, but still not very elegant!? + strings.push_back(sass_copy_c_string(inc.abs_path.c_str())); + // create the initial parser state from resource + ParserState pstate(strings.back(), contents, idx); + + // check existing import stack for possible recursion + for (size_t i = 0; i < import_stack.size() - 2; ++i) { + auto parent = import_stack[i]; + if (std::strcmp(parent->abs_path, import->abs_path) == 0) { + std::string cwd(File::get_cwd()); + // make path relative to the current directory + std::string stack("An @import loop has been found:"); + for (size_t n = 1; n < i + 2; ++n) { + stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + + " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); + } + // implement error throw directly until we + // decided how to handle full stack traces + throw Exception::InvalidSyntax(pstate, traces, stack); + // error(stack, prstate ? *prstate : pstate, import_stack); + } + } + + // create a parser instance from the given c_str buffer + Parser p(Parser::from_c_str(contents, *this, traces, pstate)); + // do not yet dispose these buffers + sass_import_take_source(import); + sass_import_take_srcmap(import); + // then parse the root block + Block_Obj root = p.parse(); + // delete memory of current stack frame + sass_delete_import(import_stack.back()); + // remove current stack frame + import_stack.pop_back(); + // create key/value pair for ast node + std::pair + ast_pair(inc.abs_path, { res, root }); + // register resulting resource + sheets.insert(ast_pair); + } + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) + { + traces.push_back(Backtrace(prstate)); + register_resource(inc, res); + traces.pop_back(); + } + + // Add a new import to the context (called from `import_url`) + Include Context::load_import(const Importer& imp, ParserState pstate) + { + + // search for valid imports (ie. partials) on the filesystem + // this may return more than one valid result (ambiguous imp_path) + const std::vector resolved(find_includes(imp)); + + // error nicely on ambiguous imp_path + if (resolved.size() > 1) { + std::stringstream msg_stream; + msg_stream << "It's not clear which file to import for "; + msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; + msg_stream << "Candidates:" << "\n"; + for (size_t i = 0, L = resolved.size(); i < L; ++i) + { msg_stream << " " << resolved[i].imp_path << "\n"; } + msg_stream << "Please delete or rename all but one of these files." << "\n"; + error(msg_stream.str(), pstate, traces); + } + + // process the resolved entry + else if (resolved.size() == 1) { + bool use_cache = c_importers.size() == 0; + // use cache for the resource loading + if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; + // try to read the content of the resolved file entry + // the memory buffer returned must be freed by us! + if (char* contents = read_file(resolved[0].abs_path)) { + // register the newly resolved file resource + register_resource(resolved[0], { contents, 0 }, pstate); + // return resolved entry + return resolved[0]; + } + } + + // nothing found + return { imp, "" }; + + } + + void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { + + ParserState pstate(imp->pstate()); + std::string imp_path(unquote(load_path)); + std::string protocol("file"); + + using namespace Prelexer; + if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { + + protocol = std::string(imp_path.c_str(), proto - 3); + // if (protocol.compare("file") && true) { } + } + + // add urls (protocol other than file) and urls without procotol to `urls` member + // ToDo: if ctx_path is already a file resource, we should not add it here? + if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { + imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); + } + else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { + String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); + Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); + Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); + loc_args->append(loc_arg); + Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); + imp->urls().push_back(new_url); + } + else { + const Importer importer(imp_path, ctx_path); + Include include(load_import(importer, pstate)); + if (include.abs_path.empty()) { + error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); + } + imp->incs().push_back(include); + } + + } + + + // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet + bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) + { + // unique counter + size_t count = 0; + // need one correct import + bool has_import = false; + // process all custom importers (or custom headers) + for (Sass_Importer_Entry& importer_ent : importers) { + // int priority = sass_importer_get_priority(importer); + Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); + // skip importer if it returns NULL + if (Sass_Import_List includes = + fn(load_path.c_str(), importer_ent, c_compiler) + ) { + // get c pointer copy to iterate over + Sass_Import_List it_includes = includes; + while (*it_includes) { ++count; + // create unique path to use as key + std::string uniq_path = load_path; + if (!only_one && count) { + std::stringstream path_strm; + path_strm << uniq_path << ":" << count; + uniq_path = path_strm.str(); + } + // create the importer struct + Importer importer(uniq_path, ctx_path); + // query data from the current include + Sass_Import_Entry include_ent = *it_includes; + char* source = sass_import_take_source(include_ent); + char* srcmap = sass_import_take_srcmap(include_ent); + size_t line = sass_import_get_error_line(include_ent); + size_t column = sass_import_get_error_column(include_ent); + const char *abs_path = sass_import_get_abs_path(include_ent); + // handle error message passed back from custom importer + // it may (or may not) override the line and column info + if (const char* err_message = sass_import_get_error_message(include_ent)) { + if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); + if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); + else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); + } + // content for import was set + else if (source) { + // resolved abs_path should be set by custom importer + // use the created uniq_path as fallback (maybe enforce) + std::string path_key(abs_path ? abs_path : uniq_path); + // create the importer struct + Include include(importer, path_key); + // attach information to AST node + imp->incs().push_back(include); + // register the resource buffers + register_resource(include, { source, srcmap }, pstate); + } + // only a path was retuned + // try to load it like normal + else if(abs_path) { + // checks some urls to preserve + // `http://`, `https://` and `//` + // or dispatchs to `import_file` + // which will check for a `.css` extension + // or resolves the file on the filesystem + // added and resolved via `add_file` + // finally stores everything on `imp` + import_url(imp, abs_path, ctx_path); + } + // move to next + ++it_includes; + } + // deallocate the returned memory + sass_delete_import_list(includes); + // set success flag + has_import = true; + // break out of loop + if (only_one) break; + } + } + // return result + return has_import; + } + + void register_function(Context&, Signature sig, Native_Function f, Env* env); + void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); + void register_overload_stub(Context&, std::string name, Env* env); + void register_built_in_functions(Context&, Env* env); + void register_c_functions(Context&, Env* env, Sass_Function_List); + void register_c_function(Context&, Env* env, Sass_Function_Entry); + + char* Context::render(Block_Obj root) + { + // check for valid block + if (!root) return 0; + // start the render process + root->perform(&emitter); + // finish emitter stream + emitter.finalize(); + // get the resulting buffer from stream + OutputBuffer emitted = emitter.get_buffer(); + // should we append a source map url? + if (!c_options.omit_source_map_url) { + // generate an embeded source map + if (c_options.source_map_embed) { + emitted.buffer += linefeed; + emitted.buffer += format_embedded_source_map(); + } + // or just link the generated one + else if (source_map_file != "") { + emitted.buffer += linefeed; + emitted.buffer += format_source_mapping_url(source_map_file); + } + } + // create a copy of the resulting buffer string + // this must be freed or taken over by implementor + return sass_copy_c_string(emitted.buffer.c_str()); + } + + void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, ParserState pstate) + { + // create a custom import to resolve headers + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + // dispatch headers which will add custom functions + // custom headers are added to the import instance + call_headers(entry_path, ctx_path, pstate, imp); + // increase head count to skip later + head_imports += resources.size() - 1; + // add the statement if we have urls + if (!imp->urls().empty()) root->append(imp); + // process all other resources (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + Block_Obj File_Context::parse() + { + + // check if entry file is given + if (input_path.empty()) return 0; + + // create absolute path from input filename + // ToDo: this should be resolved via custom importers + std::string abs_path(rel2abs(input_path, CWD)); + + // try to load the entry file + char* contents = read_file(abs_path); + + // alternatively also look inside each include path folder + // I think this differs from ruby sass (IMO too late to remove) + for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { + // build absolute path for this include path entry + abs_path = rel2abs(input_path, include_paths[i]); + // try to load the resulting path + contents = read_file(abs_path); + } + + // abort early if no content could be loaded (various reasons) + if (!contents) throw std::runtime_error("File to read not found or unreadable: " + input_path); + + // store entry path + entry_path = abs_path; + + // create entry only for import stack + Sass_Import_Entry import = sass_make_import( + input_path.c_str(), + entry_path.c_str(), + contents, + 0 + ); + // add the entry to the stack + import_stack.push_back(import); + + // create the source entry for file entry + register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); + + // create root ast tree node + return compile(); + + } + + Block_Obj Data_Context::parse() + { + + // check if source string is given + if (!source_c_str) return 0; + + // convert indented sass syntax + if(c_options.is_indented_syntax_src) { + // call sass2scss to convert the string + char * converted = sass2scss(source_c_str, + // preserve the structure as much as possible + SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); + // replace old source_c_str with converted + free(source_c_str); source_c_str = converted; + } + + // remember entry path (defaults to stdin for string) + entry_path = input_path.empty() ? "stdin" : input_path; + + // ToDo: this may be resolved via custom importers + std::string abs_path(rel2abs(entry_path)); + char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); + strings.push_back(abs_path_c_str); + + // create entry only for the import stack + Sass_Import_Entry import = sass_make_import( + entry_path.c_str(), + abs_path_c_str, + source_c_str, + srcmap_c_str + ); + // add the entry to the stack + import_stack.push_back(import); + + // register a synthetic resource (path does not really exist, skip in includes) + register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); + + // create root ast tree node + return compile(); + } + + + + // parse root block from includes + Block_Obj Context::compile() + { + // abort if there is no data + if (resources.size() == 0) return 0; + // get root block from the first style sheet + Block_Obj root = sheets.at(entry_path).root; + // abort on invalid root + if (root.isNull()) return 0; + Env global; // create root environment + // register built-in functions on env + register_built_in_functions(*this, &global); + // register custom functions (defined via C-API) + for (size_t i = 0, S = c_functions.size(); i < S; ++i) + { register_c_function(*this, &global, c_functions[i]); } + // create initial backtrace entry + // create crtp visitor objects + Expand expand(*this, &global); + Cssize cssize(*this); + CheckNesting check_nesting; + // check nesting in all files + for (auto sheet : sheets) { + auto styles = sheet.second; + check_nesting(styles.root); + } + // expand and eval the tree + root = expand(root); + // check nesting + check_nesting(root); + // merge and bubble certain rules + root = cssize(root); + // should we extend something? + if (!subset_map.empty()) { + // create crtp visitor object + Extend extend(subset_map); + extend.setEval(expand.eval); + // extend tree nodes + extend(root); + } + + // clean up by removing empty placeholders + // ToDo: maybe we can do this somewhere else? + Remove_Placeholders remove_placeholders; + root->perform(&remove_placeholders); + // return processed tree + return root; + } + // EO compile + + std::string Context::format_embedded_source_map() + { + std::string map = emitter.render_srcmap(*this); + std::istringstream is( map ); + std::ostringstream buffer; + base64::encoder E; + E.encode(is, buffer); + std::string url = "data:application/json;base64," + buffer.str(); + url.erase(url.size() - 1); + return "/*# sourceMappingURL=" + url + " */"; + } + + std::string Context::format_source_mapping_url(const std::string& file) + { + std::string url = abs2rel(file, output_path, CWD); + return "/*# sourceMappingURL=" + url + " */"; + } + + char* Context::render_srcmap() + { + if (source_map_file == "") return 0; + std::string map = emitter.render_srcmap(*this); + return sass_copy_c_string(map.c_str()); + } + + + // for data context we want to start after "stdin" + // we probably always want to skip the header includes? + std::vector Context::get_included_files(bool skip, size_t headers) + { + // create a copy of the vector for manipulations + std::vector includes = included_files; + if (includes.size() == 0) return includes; + if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } + else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } + includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); + std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); + return includes; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + std::stringstream ss; + ss << def->name() << "[f]" << arity; + def->environment(env); + (*env)[ss.str()] = def; + } + + void register_overload_stub(Context& ctx, std::string name, Env* env) + { + Definition_Ptr stub = SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + 0, + name, + 0, + 0, + true); + (*env)[name + "[f]"] = stub; + } + + + void register_built_in_functions(Context& ctx, Env* env) + { + using namespace Functions; + // RGB Functions + register_function(ctx, rgb_sig, rgb, env); + register_overload_stub(ctx, "rgba", env); + register_function(ctx, rgba_4_sig, rgba_4, 4, env); + register_function(ctx, rgba_2_sig, rgba_2, 2, env); + register_function(ctx, red_sig, red, env); + register_function(ctx, green_sig, green, env); + register_function(ctx, blue_sig, blue, env); + register_function(ctx, mix_sig, mix, env); + // HSL Functions + register_function(ctx, hsl_sig, hsl, env); + register_function(ctx, hsla_sig, hsla, env); + register_function(ctx, hue_sig, hue, env); + register_function(ctx, saturation_sig, saturation, env); + register_function(ctx, lightness_sig, lightness, env); + register_function(ctx, adjust_hue_sig, adjust_hue, env); + register_function(ctx, lighten_sig, lighten, env); + register_function(ctx, darken_sig, darken, env); + register_function(ctx, saturate_sig, saturate, env); + register_function(ctx, desaturate_sig, desaturate, env); + register_function(ctx, grayscale_sig, grayscale, env); + register_function(ctx, complement_sig, complement, env); + register_function(ctx, invert_sig, invert, env); + // Opacity Functions + register_function(ctx, alpha_sig, alpha, env); + register_function(ctx, opacity_sig, alpha, env); + register_function(ctx, opacify_sig, opacify, env); + register_function(ctx, fade_in_sig, opacify, env); + register_function(ctx, transparentize_sig, transparentize, env); + register_function(ctx, fade_out_sig, transparentize, env); + // Other Color Functions + register_function(ctx, adjust_color_sig, adjust_color, env); + register_function(ctx, scale_color_sig, scale_color, env); + register_function(ctx, change_color_sig, change_color, env); + register_function(ctx, ie_hex_str_sig, ie_hex_str, env); + // String Functions + register_function(ctx, unquote_sig, sass_unquote, env); + register_function(ctx, quote_sig, sass_quote, env); + register_function(ctx, str_length_sig, str_length, env); + register_function(ctx, str_insert_sig, str_insert, env); + register_function(ctx, str_index_sig, str_index, env); + register_function(ctx, str_slice_sig, str_slice, env); + register_function(ctx, to_upper_case_sig, to_upper_case, env); + register_function(ctx, to_lower_case_sig, to_lower_case, env); + // Number Functions + register_function(ctx, percentage_sig, percentage, env); + register_function(ctx, round_sig, round, env); + register_function(ctx, ceil_sig, ceil, env); + register_function(ctx, floor_sig, floor, env); + register_function(ctx, abs_sig, abs, env); + register_function(ctx, min_sig, min, env); + register_function(ctx, max_sig, max, env); + register_function(ctx, random_sig, random, env); + // List Functions + register_function(ctx, length_sig, length, env); + register_function(ctx, nth_sig, nth, env); + register_function(ctx, set_nth_sig, set_nth, env); + register_function(ctx, index_sig, index, env); + register_function(ctx, join_sig, join, env); + register_function(ctx, append_sig, append, env); + register_function(ctx, zip_sig, zip, env); + register_function(ctx, list_separator_sig, list_separator, env); + register_function(ctx, is_bracketed_sig, is_bracketed, env); + // Map Functions + register_function(ctx, map_get_sig, map_get, env); + register_function(ctx, map_merge_sig, map_merge, env); + register_function(ctx, map_remove_sig, map_remove, env); + register_function(ctx, map_keys_sig, map_keys, env); + register_function(ctx, map_values_sig, map_values, env); + register_function(ctx, map_has_key_sig, map_has_key, env); + register_function(ctx, keywords_sig, keywords, env); + // Introspection Functions + register_function(ctx, type_of_sig, type_of, env); + register_function(ctx, unit_sig, unit, env); + register_function(ctx, unitless_sig, unitless, env); + register_function(ctx, comparable_sig, comparable, env); + register_function(ctx, variable_exists_sig, variable_exists, env); + register_function(ctx, global_variable_exists_sig, global_variable_exists, env); + register_function(ctx, function_exists_sig, function_exists, env); + register_function(ctx, mixin_exists_sig, mixin_exists, env); + register_function(ctx, feature_exists_sig, feature_exists, env); + register_function(ctx, call_sig, call, env); + register_function(ctx, content_exists_sig, content_exists, env); + register_function(ctx, get_function_sig, get_function, env); + // Boolean Functions + register_function(ctx, not_sig, sass_not, env); + register_function(ctx, if_sig, sass_if, env); + // Misc Functions + register_function(ctx, inspect_sig, inspect, env); + register_function(ctx, unique_id_sig, unique_id, env); + // Selector functions + register_function(ctx, selector_nest_sig, selector_nest, env); + register_function(ctx, selector_append_sig, selector_append, env); + register_function(ctx, selector_extend_sig, selector_extend, env); + register_function(ctx, selector_replace_sig, selector_replace, env); + register_function(ctx, selector_unify_sig, selector_unify, env); + register_function(ctx, is_superselector_sig, is_superselector, env); + register_function(ctx, simple_selectors_sig, simple_selectors, env); + register_function(ctx, selector_parse_sig, selector_parse, env); + } + + void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) + { + while (descrs && *descrs) { + register_c_function(ctx, env, *descrs); + ++descrs; + } + } + void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) + { + Definition_Ptr def = make_c_function(descr, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + +} diff --git a/src/context.hpp b/src/context.hpp new file mode 100644 index 000000000..d3caba13e --- /dev/null +++ b/src/context.hpp @@ -0,0 +1,152 @@ +#ifndef SASS_CONTEXT_H +#define SASS_CONTEXT_H + +#include +#include +#include + +#define BUFFERSIZE 255 +#include "b64/encode.h" + +#include "ast_fwd_decl.hpp" +#include "kwd_arg_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "sass_context.hpp" +#include "environment.hpp" +#include "source_map.hpp" +#include "subset_map.hpp" +#include "backtrace.hpp" +#include "output.hpp" +#include "plugins.hpp" +#include "file.hpp" + + +struct Sass_Function; + +namespace Sass { + + class Context { + public: + void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); + bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; + bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; + + private: + bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); + + public: + const std::string CWD; + struct Sass_Options& c_options; + std::string entry_path; + size_t head_imports; + Plugins plugins; + Output emitter; + + // generic ast node garbage container + // used to avoid possible circular refs + std::vector ast_gc; + // resources add under our control + // these are guaranteed to be freed + std::vector strings; + std::vector resources; + std::map sheets; + Subset_Map subset_map; + std::vector import_stack; + std::vector callee_stack; + std::vector traces; + + struct Sass_Compiler* c_compiler; + + // absolute paths to includes + std::vector included_files; + // relative includes for sourcemap + std::vector srcmap_links; + // vectors above have same size + + std::vector plugin_paths; // relative paths to load plugins + std::vector include_paths; // lookup paths for includes + + + + + + void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); + + std::vector c_headers; + std::vector c_importers; + std::vector c_functions; + + void add_c_header(Sass_Importer_Entry header); + void add_c_importer(Sass_Importer_Entry importer); + void add_c_function(Sass_Function_Entry function); + + const std::string indent; // String to be used for indentation + const std::string linefeed; // String to be used for line feeds + const std::string input_path; // for relative paths in src-map + const std::string output_path; // for relative paths to the output + const std::string source_map_file; // path to source map file (enables feature) + const std::string source_map_root; // path for sourceRoot property (pass-through) + + virtual ~Context(); + Context(struct Sass_Context&); + virtual Block_Obj parse() = 0; + virtual Block_Obj compile(); + virtual char* render(Block_Obj root); + virtual char* render_srcmap(); + + void register_resource(const Include&, const Resource&); + void register_resource(const Include&, const Resource&, ParserState&); + std::vector find_includes(const Importer& import); + Include load_import(const Importer&, ParserState pstate); + + Sass_Output_Style output_style() { return c_options.output_style; }; + std::vector get_included_files(bool skip = false, size_t headers = 0); + + private: + void collect_plugin_paths(const char* paths_str); + void collect_plugin_paths(string_list* paths_array); + void collect_include_paths(const char* paths_str); + void collect_include_paths(string_list* paths_array); + std::string format_embedded_source_map(); + std::string format_source_mapping_url(const std::string& out_path); + + + // void register_built_in_functions(Env* env); + // void register_function(Signature sig, Native_Function f, Env* env); + // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); + // void register_overload_stub(std::string name, Env* env); + + public: + const std::string& cwd() { return CWD; }; + }; + + class File_Context : public Context { + public: + File_Context(struct Sass_File_Context& ctx) + : Context(ctx) + { } + virtual ~File_Context(); + virtual Block_Obj parse(); + }; + + class Data_Context : public Context { + public: + char* source_c_str; + char* srcmap_c_str; + Data_Context(struct Sass_Data_Context& ctx) + : Context(ctx) + { + source_c_str = ctx.source_string; + srcmap_c_str = ctx.srcmap_string; + ctx.source_string = 0; // passed away + ctx.srcmap_string = 0; // passed away + } + virtual ~Data_Context(); + virtual Block_Obj parse(); + }; + +} + +#endif diff --git a/src/cssize.cpp b/src/cssize.cpp new file mode 100644 index 000000000..6a12fdf7b --- /dev/null +++ b/src/cssize.cpp @@ -0,0 +1,606 @@ +#include "sass.hpp" +#include +#include +#include + +#include "cssize.hpp" +#include "context.hpp" + +namespace Sass { + + Cssize::Cssize(Context& ctx) + : ctx(ctx), + traces(ctx.traces), + block_stack(std::vector()), + p_stack(std::vector()) + { } + + Statement_Ptr Cssize::parent() + { + return p_stack.size() ? p_stack.back() : block_stack.front(); + } + + Block_Ptr Cssize::operator()(Block_Ptr b) + { + Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); + // bb->tabs(b->tabs()); + block_stack.push_back(bb); + append_block(b, bb); + block_stack.pop_back(); + return bb.detach(); + } + + Statement_Ptr Cssize::operator()(Trace_Ptr t) + { + traces.push_back(Backtrace(t->pstate())); + auto result = t->block()->perform(this); + traces.pop_back(); + return result; + } + + Statement_Ptr Cssize::operator()(Declaration_Ptr d) + { + String_Obj property = Cast(d->property()); + + if (Declaration_Ptr dd = Cast(parent())) { + String_Obj parent_property = Cast(dd->property()); + property = SASS_MEMORY_NEW(String_Constant, + d->property()->pstate(), + parent_property->to_string() + "-" + property->to_string()); + if (!dd->value()) { + d->tabs(dd->tabs() + 1); + } + } + + Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, + d->pstate(), + property, + d->value(), + d->is_important(), + d->is_custom_property()); + dd->is_indented(d->is_indented()); + dd->tabs(d->tabs()); + + p_stack.push_back(dd); + Block_Obj bb = d->block() ? operator()(d->block()) : NULL; + p_stack.pop_back(); + + if (bb && bb->length()) { + if (dd->value() && !dd->value()->is_invisible()) { + bb->unshift(dd); + } + return bb.detach(); + } + else if (dd->value() && !dd->value()->is_invisible()) { + return dd.detach(); + } + + return 0; + } + + Statement_Ptr Cssize::operator()(Directive_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + if (parent()->statement_type() == Statement::RULESET) + { + return (r->is_keyframes()) ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); + } + + p_stack.push_back(r); + Directive_Obj rr = SASS_MEMORY_NEW(Directive, + r->pstate(), + r->keyword(), + r->selector(), + r->block() ? operator()(r->block()) : 0); + if (r->value()) rr->value(r->value()); + p_stack.pop_back(); + + bool directive_exists = false; + size_t L = rr->block() ? rr->block()->length() : 0; + for (size_t i = 0; i < L && !directive_exists; ++i) { + Statement_Obj s = r->block()->at(i); + if (s->statement_type() != Statement::BUBBLE) directive_exists = true; + else { + Bubble_Obj s_obj = Cast(s); + s = s_obj->node(); + if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; + else directive_exists = (Cast(s)->keyword() == rr->keyword()); + } + + } + + Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); + if (!(directive_exists || rr->is_keyframes())) + { + Directive_Ptr empty_node = Cast(rr); + empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); + result->append(empty_node); + } + + Block_Obj db = rr->block(); + if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); + Block_Obj ss = debubble(db, rr); + for (size_t i = 0, L = ss->length(); i < L; ++i) { + result->append(ss->at(i)); + } + + return result; + } + + Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, + r->pstate(), + operator()(r->block())); + if (!r->name().isNull()) rr->name(r->name()); + + return debubble(rr->block(), rr); + } + + Statement_Ptr Cssize::operator()(Ruleset_Ptr r) + { + p_stack.push_back(r); + // this can return a string schema + // string schema is not a statement! + // r->block() is already a string schema + // and that is comming from propset expand + Block_Ptr bb = operator()(r->block()); + // this should protect us (at least a bit) from our mess + // fixing this properly is harder that it should be ... + if (Cast(bb) == NULL) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + } + Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + r->selector(), + bb); + + rr->is_root(r->is_root()); + // rr->tabs(r->block()->tabs()); + p_stack.pop_back(); + + if (!rr->block()) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + } + + Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + for (size_t i = 0, L = rr->block()->length(); i < L; i++) + { + Statement_Ptr s = rr->block()->at(i); + if (bubblable(s)) rules->append(s); + if (!bubblable(s)) props->append(s); + } + + if (props->length()) + { + Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + pb->concat(props); + rr->block(pb); + + for (size_t i = 0, L = rules->length(); i < L; i++) + { + Statement_Ptr stm = rules->at(i); + stm->tabs(stm->tabs() + 1); + } + + rules->unshift(rr); + } + + Block_Ptr ptr = rules; + rules = debubble(rules); + void* lp = ptr; + void* rp = rules; + if (lp != rp) { + Block_Obj obj = ptr; + } + + if (!(!rules->length() || + !bubblable(rules->last()) || + parent()->statement_type() == Statement::RULESET)) + { + rules->last()->group_end(true); + } + return rules; + } + + Statement_Ptr Cssize::operator()(Null_Ptr m) + { + return 0; + } + + Statement_Ptr Cssize::operator()(Media_Block_Ptr m) + { + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + if (parent()->statement_type() == Statement::MEDIA) + { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } + + p_stack.push_back(m); + + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + m->media_queries(), + operator()(m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(mm->block(), mm); + } + + Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) + { + if (!m->block()->length()) + { return m; } + + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + p_stack.push_back(m); + + Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + m->condition(), + operator()(m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(mm->block(), mm); + } + + Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) + { + bool tmp = false; + for (size_t i = 0, L = p_stack.size(); i < L; ++i) { + Statement_Ptr s = p_stack[i]; + tmp |= m->exclude_node(s); + } + + if (!tmp && m->block()) + { + Block_Ptr bb = operator()(m->block()); + for (size_t i = 0, L = bb->length(); i < L; ++i) { + // (bb->elements())[i]->tabs(m->tabs()); + Statement_Obj stm = bb->at(i); + if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); + } + if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); + return bb; + } + + if (m->exclude_node(parent())) + { + return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + } + + return bubble(m); + } + + Statement_Ptr Cssize::bubble(Directive_Ptr m) + { + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); + wrapper_block->append(new_rule); + Directive_Obj mm = SASS_MEMORY_NEW(Directive, + m->pstate(), + m->keyword(), + m->selector(), + wrapper_block); + if (m->value()) mm->value(m->value()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) + { + if (!m || !m->block()) return NULL; + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + if (new_rule) { + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + wrapper_block->append(new_rule); + } + + At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, + m->pstate(), + wrapper_block, + m->expression()); + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) + { + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + m->condition(), + wrapper_block); + + mm->tabs(m->tabs()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Media_Block_Ptr m) + { + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + m->media_queries(), + wrapper_block); + + mm->tabs(m->tabs()); + + return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + } + + bool Cssize::bubblable(Statement_Ptr s) + { + return Cast(s) || s->bubbles(); + } + + Block_Ptr Cssize::flatten(Block_Ptr b) + { + Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr ss = b->at(i); + if (Block_Ptr bb = Cast(ss)) { + Block_Obj bs = flatten(bb); + for (size_t j = 0, K = bs->length(); j < K; ++j) { + result->append(bs->at(j)); + } + } + else { + result->append(ss); + } + } + return result; + } + + std::vector> Cssize::slice_by_bubble(Block_Ptr b) + { + std::vector> results; + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj value = b->at(i); + bool key = Cast(value) != NULL; + + if (!results.empty() && results.back().first == key) + { + Block_Obj wrapper_block = results.back().second; + wrapper_block->append(value); + } + else + { + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); + wrapper_block->append(value); + results.push_back(std::make_pair(key, wrapper_block)); + } + } + return results; + } + + Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) + { + Has_Block_Obj previous_parent = 0; + std::vector> baz = slice_by_bubble(children); + Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); + + for (size_t i = 0, L = baz.size(); i < L; ++i) { + bool is_bubble = baz[i].first; + Block_Obj slice = baz[i].second; + + if (!is_bubble) { + if (!parent) { + result->append(slice); + } + else if (previous_parent) { + previous_parent->block()->concat(slice); + } + else { + previous_parent = Cast(SASS_MEMORY_COPY(parent)); + previous_parent->block(slice); + previous_parent->tabs(parent->tabs()); + + result->append(previous_parent); + } + continue; + } + + for (size_t j = 0, K = slice->length(); j < K; ++j) + { + Statement_Ptr ss; + Statement_Obj stm = slice->at(j); + // this has to go now here (too bad) + Bubble_Obj node = Cast(stm); + Media_Block_Ptr m1 = NULL; + Media_Block_Ptr m2 = NULL; + if (parent) m1 = Cast(parent); + if (node) m2 = Cast(node->node()); + if (!parent || + parent->statement_type() != Statement::MEDIA || + node->node()->statement_type() != Statement::MEDIA || + (m1 && m2 && *m1->media_queries() == *m2->media_queries()) + ) + { + ss = node->node(); + } + else + { + List_Obj mq = merge_media_queries( + Cast(node->node()), + Cast(parent) + ); + if (!mq->length()) continue; + if (Media_Block* b = Cast(node->node())) { + b->media_queries(mq); + } + ss = node->node(); + } + + if (!ss) continue; + + ss->tabs(ss->tabs() + node->tabs()); + ss->group_end(node->group_end()); + + Block_Obj bb = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + bb->append(ss->perform(this)); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + + Block_Ptr wrapper = flatten(bb); + wrapper_block->append(wrapper); + + if (wrapper->length()) { + previous_parent = NULL; + } + + if (wrapper_block) { + result->append(wrapper_block); + } + } + } + + return flatten(result); + } + + Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + void Cssize::append_block(Block_Ptr b, Block_Ptr cur) + { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj ith = b->at(i)->perform(this); + if (Block_Ptr bb = Cast(ith)) { + for (size_t j = 0, K = bb->length(); j < K; ++j) { + cur->append(bb->at(j)); + } + } + else if (ith) { + cur->append(ith); + } + } + } + + List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) + { + List_Ptr qq = SASS_MEMORY_NEW(List, + m1->media_queries()->pstate(), + m1->media_queries()->length(), + SASS_COMMA); + + for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { + for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { + Expression_Obj l1 = m1->media_queries()->at(i); + Expression_Obj l2 = m2->media_queries()->at(j); + Media_Query_Ptr mq1 = Cast(l1); + Media_Query_Ptr mq2 = Cast(l2); + Media_Query_Ptr mq = merge_media_query(mq1, mq2); + if (mq) qq->append(mq); + } + } + + return qq; + } + + + Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) + { + + std::string type; + std::string mod; + + std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; + std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); + std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; + + + if (t1.empty()) t1 = t2; + if (t2.empty()) t2 = t1; + + if ((m1 == "not") ^ (m2 == "not")) { + if (t1 == t2) { + return 0; + } + type = m1 == "not" ? t2 : t1; + mod = m1 == "not" ? m2 : m1; + } + else if (m1 == "not" && m2 == "not") { + if (t1 != t2) { + return 0; + } + type = t1; + mod = "not"; + } + else if (t1 != t2) { + return 0; + } else { + type = t1; + mod = m1.empty() ? m2 : m1; + } + + Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, + mq1->pstate(), + 0, + mq1->length() + mq2->length(), + mod == "not", + mod == "only"); + + if (!type.empty()) { + mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); + } + + mm->concat(mq2); + mm->concat(mq1); + + return mm; + } +} diff --git a/src/cssize.hpp b/src/cssize.hpp new file mode 100644 index 000000000..5a6c704b0 --- /dev/null +++ b/src/cssize.hpp @@ -0,0 +1,77 @@ +#ifndef SASS_CSSIZE_H +#define SASS_CSSIZE_H + +#include "ast.hpp" +#include "context.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + struct Backtrace; + + class Cssize : public Operation_CRTP { + + Context& ctx; + Backtraces& traces; + std::vector block_stack; + std::vector p_stack; + + Statement_Ptr fallback_impl(AST_Node_Ptr n); + + public: + Cssize(Context&); + ~Cssize() { } + + Selector_List_Ptr selector(); + + Block_Ptr operator()(Block_Ptr); + Statement_Ptr operator()(Ruleset_Ptr); + // Statement_Ptr operator()(Bubble_Ptr); + Statement_Ptr operator()(Media_Block_Ptr); + Statement_Ptr operator()(Supports_Block_Ptr); + Statement_Ptr operator()(At_Root_Block_Ptr); + Statement_Ptr operator()(Directive_Ptr); + Statement_Ptr operator()(Keyframe_Rule_Ptr); + Statement_Ptr operator()(Trace_Ptr); + Statement_Ptr operator()(Declaration_Ptr); + // Statement_Ptr operator()(Assignment_Ptr); + // Statement_Ptr operator()(Import_Ptr); + // Statement_Ptr operator()(Import_Stub_Ptr); + // Statement_Ptr operator()(Warning_Ptr); + // Statement_Ptr operator()(Error_Ptr); + // Statement_Ptr operator()(Comment_Ptr); + // Statement_Ptr operator()(If_Ptr); + // Statement_Ptr operator()(For_Ptr); + // Statement_Ptr operator()(Each_Ptr); + // Statement_Ptr operator()(While_Ptr); + // Statement_Ptr operator()(Return_Ptr); + // Statement_Ptr operator()(Extension_Ptr); + // Statement_Ptr operator()(Definition_Ptr); + // Statement_Ptr operator()(Mixin_Call_Ptr); + // Statement_Ptr operator()(Content_Ptr); + Statement_Ptr operator()(Null_Ptr); + + Statement_Ptr parent(); + std::vector> slice_by_bubble(Block_Ptr); + Statement_Ptr bubble(Directive_Ptr); + Statement_Ptr bubble(At_Root_Block_Ptr); + Statement_Ptr bubble(Media_Block_Ptr); + Statement_Ptr bubble(Supports_Block_Ptr); + + Block_Ptr debubble(Block_Ptr children, Statement_Ptr parent = 0); + Block_Ptr flatten(Block_Ptr); + bool bubblable(Statement_Ptr); + + List_Ptr merge_media_queries(Media_Block_Ptr, Media_Block_Ptr); + Media_Query_Ptr merge_media_query(Media_Query_Ptr, Media_Query_Ptr); + + template + Statement_Ptr fallback(U x) { return fallback_impl(x); } + + void append_block(Block_Ptr, Block_Ptr); + }; + +} + +#endif diff --git a/src/debug.hpp b/src/debug.hpp new file mode 100644 index 000000000..43fe05e67 --- /dev/null +++ b/src/debug.hpp @@ -0,0 +1,43 @@ +#ifndef SASS_DEBUG_H +#define SASS_DEBUG_H + +#include + +#ifndef UINT32_MAX + #define UINT32_MAX 0xffffffffU +#endif + +enum dbg_lvl_t : uint32_t { + NONE = 0, + TRIM = 1, + CHUNKS = 2, + SUBWEAVE = 4, + WEAVE = 8, + EXTEND_COMPOUND = 16, + EXTEND_COMPLEX = 32, + LCS = 64, + EXTEND_OBJECT = 128, + ALL = UINT32_MAX +}; + +#ifdef DEBUG + +#ifndef DEBUG_LVL +const uint32_t debug_lvl = UINT32_MAX; +#else +const uint32_t debug_lvl = (DEBUG_LVL); +#endif // DEBUG_LVL + +#define DEBUG_PRINT(lvl, x) if((lvl) & debug_lvl) { std::cerr << x; } +#define DEBUG_PRINTLN(lvl, x) if((lvl) & debug_lvl) { std::cerr << x << std::endl; } +#define DEBUG_EXEC(lvl, x) if((lvl) & debug_lvl) { x; } + +#else // DEBUG + +#define DEBUG_PRINT(lvl, x) +#define DEBUG_PRINTLN(lvl, x) +#define DEBUG_EXEC(lvl, x) + +#endif // DEBUG + +#endif // SASS_DEBUG diff --git a/src/debugger.hpp b/src/debugger.hpp new file mode 100644 index 000000000..f1ceabd9a --- /dev/null +++ b/src/debugger.hpp @@ -0,0 +1,801 @@ +#ifndef SASS_DEBUGGER_H +#define SASS_DEBUGGER_H + +#include +#include +#include "node.hpp" +#include "ast_fwd_decl.hpp" + +using namespace Sass; + +inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); + +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { + debug_ast(const_cast(node), ind, env); +} + +inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : set) { + debug_ast(pair, ind + ""); + // debug_ast(set[pair], ind + "first: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) +{ + size_t pos = 0; + while((pos = str.find(oldStr, pos)) != std::string::npos) + { + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return str; +} + +inline std::string prettyprint(const std::string& str) { + std::string clean = str_replace(str, "\n", "\\n"); + clean = str_replace(clean, " ", "\\t"); + clean = str_replace(clean, "\r", "\\r"); + return clean; +} + +inline std::string longToHex(long long t) { + std::stringstream is; + is << std::hex << t; + return is.str(); +} + +inline std::string pstate_source_position(AST_Node_Ptr node) +{ + std::stringstream str; + Position start(node->pstate()); + Position end(start + node->pstate().offset); + str << (start.file == std::string::npos ? -1 : start.file) + << "@[" << start.line << ":" << start.column << "]" + << "-[" << end.line << ":" << end.column << "]"; +#ifdef DEBUG_SHARED_PTR + str << "x" << node->getRefCount() << "" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); +#endif + return str.str(); +} + +inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) +{ + if (node == 0) return; + if (ind == "") std::cerr << "####################################################################\n"; + if (Cast(node)) { + Bubble_Ptr bubble = Cast(node); + std::cerr << ind << "Bubble " << bubble; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << bubble->tabs(); + std::cerr << std::endl; + debug_ast(bubble->node(), ind + " ", env); + } else if (Cast(node)) { + Trace_Ptr trace = Cast(node); + std::cerr << ind << "Trace " << trace; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [name:" << trace->name() << ", type: " << trace->type() << "]" + << std::endl; + debug_ast(trace->block(), ind + " ", env); + } else if (Cast(node)) { + At_Root_Block_Ptr root_block = Cast(node); + std::cerr << ind << "At_Root_Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << root_block->tabs(); + std::cerr << std::endl; + debug_ast(root_block->expression(), ind + ":", env); + debug_ast(root_block->block(), ind + " ", env); + } else if (Cast(node)) { + Selector_List_Ptr selector = Cast(node); + std::cerr << ind << "Selector_List " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); + std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->schema(), ind + "#{} "); + + for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + +// } else if (Cast(node)) { +// Expression_Ptr expression = Cast(node); +// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; + + } else if (Cast(node)) { + Parent_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Parent_Selector " << selector; +// if (selector->not_selector()) cerr << " [in_declaration]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; +// debug_ast(selector->selector(), ind + "->", env); + + } else if (Cast(node)) { + Complex_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Complex_Selector " << selector + << " (" << pstate_source_position(node) << ")" + << " <" << selector->hash() << ">" + << " [length:" << longToHex(selector->length()) << "]" + << " [weight:" << longToHex(selector->specificity()) << "]" + << " [@media:" << selector->media_block() << "]" + << (selector->is_invisible() ? " [INVISIBLE]": " -") + << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_parent_ref() ? " [has parent]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << " -- "; + std::string del; + switch (selector->combinator()) { + case Complex_Selector::PARENT_OF: del = ">"; break; + case Complex_Selector::PRECEDES: del = "~"; break; + case Complex_Selector::ADJACENT_TO: del = "+"; break; + case Complex_Selector::ANCESTOR_OF: del = " "; break; + case Complex_Selector::REFERENCE: del = "//"; break; + } + // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); + if (selector->tail()) { + debug_ast(selector->tail(), ind + "{" + del + "}", env); + } else if(del != " ") { + std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; + } + ComplexSelectorSet set = selector->sources(); + // debug_sources_set(set, ind + " @--> "); + } else if (Cast(node)) { + Compound_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Compound_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->extended() ? " [extended]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Wrapped_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Wrapped_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->selector(), ind + " () ", env); + } else if (Cast(node)) { + Pseudo_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Pseudo_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->expression(), ind + " <= ", env); + } else if (Cast(node)) { + Attribute_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Attribute_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); + } else if (Cast(node)) { + Class_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Class_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (Cast(node)) { + Id_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Id_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (Cast(node)) { + Element_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Element_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; + std::cerr << std::endl; + } else if (Cast(node)) { + + Placeholder_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; + std::cerr << " (" << pstate_source_position(selector) << ")" + << " <" << selector->hash() << ">" + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (Cast(node)) { + Simple_Selector* selector = Cast(node); + std::cerr << ind << "Simple_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; + + } else if (Cast(node)) { + Selector_Schema_Ptr selector = Cast(node); + std::cerr << ind << "Selector_Schema " << selector; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [@media:" << selector->media_block() << "]" + << (selector->connect_parent() ? " [connect-parent]": " -") + << std::endl; + + debug_ast(selector->contents(), ind + " "); + // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Selector_Ptr selector = Cast(node); + std::cerr << ind << "Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (Cast(node)) { + Media_Query_Expression_Ptr block = Cast(node); + std::cerr << ind << "Media_Query_Expression " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + + } else if (Cast(node)) { + Media_Query_Ptr block = Cast(node); + std::cerr << ind << "Media_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_negated() ? " [is_negated]": " -") + << (block->is_restricted() ? " [is_restricted]": " -") + << std::endl; + debug_ast(block->media_type(), ind + " "); + for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Media_Block_Ptr block = Cast(node); + std::cerr << ind << "Media_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->media_queries(), ind + " =@ "); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Supports_Block_Ptr block = Cast(node); + std::cerr << ind << "Supports_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->condition(), ind + " =@ "); + debug_ast(block->block(), ind + " <>"); + } else if (Cast(node)) { + Supports_Operator_Ptr block = Cast(node); + std::cerr << ind << "Supports_Operator " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->left(), ind + " left) "); + debug_ast(block->right(), ind + " right) "); + } else if (Cast(node)) { + Supports_Negation_Ptr block = Cast(node); + std::cerr << ind << "Supports_Negation " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->condition(), ind + " condition) "); + } else if (Cast(node)) { + At_Root_Query_Ptr block = Cast(node); + std::cerr << ind << "At_Root_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Supports_Declaration_Ptr block = Cast(node); + std::cerr << ind << "Supports_Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Block_Ptr root_block = Cast(node); + std::cerr << ind << "Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (root_block->is_root()) std::cerr << " [root]"; + std::cerr << " " << root_block->tabs() << std::endl; + for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Warning_Ptr block = Cast(node); + std::cerr << ind << "Warning " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->message(), ind + " : "); + } else if (Cast(node)) { + Error_Ptr block = Cast(node); + std::cerr << ind << "Error " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Debug_Ptr block = Cast(node); + std::cerr << ind << "Debug " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->value(), ind + " "); + } else if (Cast(node)) { + Comment_Ptr block = Cast(node); + std::cerr << ind << "Comment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << + " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(block->text(), ind + "// ", env); + } else if (Cast(node)) { + If_Ptr block = Cast(node); + std::cerr << ind << "If " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->predicate(), ind + " = "); + debug_ast(block->block(), ind + " <>"); + debug_ast(block->alternative(), ind + " ><"); + } else if (Cast(node)) { + Return_Ptr block = Cast(node); + std::cerr << ind << "Return " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Extension_Ptr block = Cast(node); + std::cerr << ind << "Extension " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->selector(), ind + "-> ", env); + } else if (Cast(node)) { + Content_Ptr block = Cast(node); + std::cerr << ind << "Content " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << block->media_block() << "]"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Import_Stub_Ptr block = Cast(node); + std::cerr << ind << "Import_Stub " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->imp_path() << "] "; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Import_Ptr block = Cast(node); + std::cerr << ind << "Import " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + // std::vector files_; + for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); + debug_ast(block->import_queries(), ind + "@@ "); + } else if (Cast(node)) { + Assignment_Ptr block = Cast(node); + std::cerr << ind << "Assignment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; + debug_ast(block->value(), ind + "=", env); + } else if (Cast(node)) { + Declaration_Ptr block = Cast(node); + std::cerr << ind << "Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->property(), ind + " prop: ", env); + debug_ast(block->value(), ind + " value: ", env); + debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Keyframe_Rule_Ptr has_block = Cast(node); + std::cerr << ind << "Keyframe_Rule " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->name()) debug_ast(has_block->name(), ind + "@"); + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Directive_Ptr block = Cast(node); + std::cerr << ind << "Directive " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; + debug_ast(block->selector(), ind + "~", env); + debug_ast(block->value(), ind + "+", env); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Each_Ptr block = Cast(node); + std::cerr << ind << "Each " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + For_Ptr block = Cast(node); + std::cerr << ind << "For " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + While_Ptr block = Cast(node); + std::cerr << ind << "While " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Definition_Ptr block = Cast(node); + std::cerr << ind << "Definition " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << block->name() << "] "; + std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; + // this seems to lead to segfaults some times? + // std::cerr << " [signature: " << block->signature() << "] "; + std::cerr << " [native: " << block->native_function() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->parameters(), ind + " params: ", env); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Mixin_Call_Ptr block = Cast(node); + std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); + std::cerr << " (" << pstate_source_position(block) << ")"; + std::cerr << " [" << block->name() << "]"; + std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; + debug_ast(block->arguments(), ind + " args: "); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Ruleset_Ptr ruleset = Cast(node)) { + std::cerr << ind << "Ruleset " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << (ruleset->is_root() ? " [root]" : ""); + std::cerr << std::endl; + debug_ast(ruleset->selector(), ind + ">"); + debug_ast(ruleset->block(), ind + " "); + } else if (Cast(node)) { + Block_Ptr block = Cast(node); + std::cerr << ind << "Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << " [indent: " << block->tabs() << "]" << std::endl; + for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Variable_Ptr expression = Cast(node); + std::cerr << ind << "Variable " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]" << std::endl; + std::string name(expression->name()); + if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); + } else if (Cast(node)) { + Function_Call_Schema_Ptr expression = Cast(node); + std::cerr << ind << "Function_Call_Schema " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << "" << std::endl; + debug_ast(expression->name(), ind + "name: ", env); + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (Cast(node)) { + Function_Call_Ptr expression = Cast(node); + std::cerr << ind << "Function_Call " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->arguments(), ind + " args: ", env); + debug_ast(expression->func(), ind + " func: ", env); + } else if (Cast(node)) { + Function_Ptr expression = Cast(node); + std::cerr << ind << "Function " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->definition(), ind + " definition: ", env); + } else if (Cast(node)) { + Arguments_Ptr expression = Cast(node); + std::cerr << ind << "Arguments " << expression; + if (expression->is_delayed()) std::cerr << " [delayed]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; + if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; + if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; + std::cerr << std::endl; + for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Argument_Ptr expression = Cast(node); + std::cerr << ind << "Argument " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->value().ptr() << "]"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [rest: " << expression->is_rest_argument() << "] "; + std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; + debug_ast(expression->value(), ind + " value: ", env); + } else if (Cast(node)) { + Parameters_Ptr expression = Cast(node); + std::cerr << ind << "Parameters " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; + std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; + std::cerr << std::endl; + for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Parameter_Ptr expression = Cast(node); + std::cerr << ind << "Parameter " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [default: " << expression->default_value().ptr() << "] "; + std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; + } else if (Cast(node)) { + Unary_Expression_Ptr expression = Cast(node); + std::cerr << ind << "Unary_Expression " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type() << "]" << std::endl; + debug_ast(expression->operand(), ind + " operand: ", env); + } else if (Cast(node)) { + Binary_Expression_Ptr expression = Cast(node); + std::cerr << ind << "Binary_Expression " << expression; + if (expression->is_interpolant()) std::cerr << " [is interpolant] "; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [ws_before: " << expression->op().ws_before << "] "; + std::cerr << " [ws_after: " << expression->op().ws_after << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type_name() << "]" << std::endl; + debug_ast(expression->left(), ind + " left: ", env); + debug_ast(expression->right(), ind + " right: ", env); + } else if (Cast(node)) { + Map_Ptr expression = Cast(node); + std::cerr << ind << "Map " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [Hashed]" << std::endl; + for (const auto& i : expression->elements()) { + debug_ast(i.first, ind + " key: "); + debug_ast(i.second, ind + " val: "); + } + } else if (Cast(node)) { + List_Ptr expression = Cast(node); + std::cerr << ind << "List " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->length() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << + " [delayed: " << expression->is_delayed() << "] " << + " [interpolant: " << expression->is_interpolant() << "] " << + " [listized: " << expression->from_selector() << "] " << + " [arglist: " << expression->is_arglist() << "] " << + " [bracketed: " << expression->is_bracketed() << "] " << + " [expanded: " << expression->is_expanded() << "] " << + " [hash: " << expression->hash() << "] " << + std::endl; + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Content_Ptr expression = Cast(node); + std::cerr << ind << "Content " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << expression->media_block() << "]"; + std::cerr << " [Statement]" << std::endl; + } else if (Cast(node)) { + Boolean_Ptr expression = Cast(node); + std::cerr << ind << "Boolean " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << "]" << std::endl; + } else if (Cast(node)) { + Color_Ptr expression = Cast(node); + std::cerr << ind << "Color " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } else if (Cast(node)) { + Number_Ptr expression = Cast(node); + std::cerr << ind << "Number " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << expression->unit() << "]" << + " [hash: " << expression->hash() << "] " << + std::endl; + } else if (Cast(node)) { + Null_Ptr expression = Cast(node); + std::cerr << ind << "Null " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] " + // " [hash: " << expression->hash() << "] " + << std::endl; + } else if (Cast(node)) { + String_Quoted_Ptr expression = Cast(node); + std::cerr << ind << "String_Quoted " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + String_Constant_Ptr expression = Cast(node); + std::cerr << ind << "String_Constant " << expression; + if (expression->concrete_type()) { + std::cerr << " " << expression->concrete_type(); + } + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + String_Schema_Ptr expression = Cast(node); + std::cerr << ind << "String_Schema " << expression; + std::cerr << " (" << pstate_source_position(expression) << ")"; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->css()) std::cerr << " [css]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [is interpolant]"; + if (expression->has_interpolant()) std::cerr << " [has interpolant]"; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + String_Ptr expression = Cast(node); + std::cerr << ind << "String " << expression; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + Expression_Ptr expression = Cast(node); + std::cerr << ind << "Expression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + switch (expression->concrete_type()) { + case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; + case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; + case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; + case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; + case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; + case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; + case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; + case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; + case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; + case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; + case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; + case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; + case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; + case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; + case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; + } + std::cerr << std::endl; + } else if (Cast(node)) { + Has_Block_Ptr has_block = Cast(node); + std::cerr << ind << "Has_Block " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Statement_Ptr statement = Cast(node); + std::cerr << ind << "Statement " << statement; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << statement->tabs() << std::endl; + } + + if (ind == "") std::cerr << "####################################################################\n"; +} + +inline void debug_node(Node* node, std::string ind = "") +{ + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; + if (node->isCombinator()) { + std::cerr << ind; + std::cerr << "Combinator "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + switch (node->combinator()) { + case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; + case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; + case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; + case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; + case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; + } + std::cerr << std::endl; + // debug_ast(node->combinator(), ind + " "); + } else if (node->isSelector()) { + std::cerr << ind; + std::cerr << "Selector "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + debug_ast(node->selector(), ind + " "); + } else if (node->isCollection()) { + std::cerr << ind; + std::cerr << "Collection "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + for(auto n : (*node->collection())) { + debug_node(&n, ind + " "); + } + } else if (node->isNil()) { + std::cerr << ind; + std::cerr << "Nil "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } else { + std::cerr << ind; + std::cerr << "OTHER "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; +} + +/* +inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) +{ + debug_ast(const_cast(node), ind, env); +} +*/ +inline void debug_node(const Node* node, std::string ind = "") +{ + debug_node(const_cast(node), ind); +} + +inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &it : map.values()) { + debug_ast(it.first, ind + "first: "); + debug_ast(it.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : *entries) { + debug_ast(pair.first, ind + "first: "); + debug_ast(pair.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +#endif // SASS_DEBUGGER diff --git a/src/emitter.cpp b/src/emitter.cpp new file mode 100644 index 000000000..161e689a9 --- /dev/null +++ b/src/emitter.cpp @@ -0,0 +1,297 @@ +#include "sass.hpp" +#include "util.hpp" +#include "context.hpp" +#include "output.hpp" +#include "emitter.hpp" +#include "utf8_string.hpp" + +namespace Sass { + + Emitter::Emitter(struct Sass_Output_Options& opt) + : wbuf(), + opt(opt), + indentation(0), + scheduled_space(0), + scheduled_linefeed(0), + scheduled_delimiter(false), + scheduled_crutch(0), + scheduled_mapping(0), + in_custom_property(false), + in_comment(false), + in_wrapped(false), + in_media_block(false), + in_declaration(false), + in_space_array(false), + in_comma_array(false) + { } + + // return buffer as string + std::string Emitter::get_buffer(void) + { + return wbuf.buffer; + } + + Sass_Output_Style Emitter::output_style(void) const + { + return opt.output_style; + } + + // PROXY METHODS FOR SOURCE MAPS + + void Emitter::add_source_index(size_t idx) + { wbuf.smap.source_index.push_back(idx); } + + std::string Emitter::render_srcmap(Context &ctx) + { return wbuf.smap.render_srcmap(ctx); } + + void Emitter::set_filename(const std::string& str) + { wbuf.smap.file = str; } + + void Emitter::schedule_mapping(const AST_Node_Ptr node) + { scheduled_mapping = node; } + void Emitter::add_open_mapping(const AST_Node_Ptr node) + { wbuf.smap.add_open_mapping(node); } + void Emitter::add_close_mapping(const AST_Node_Ptr node) + { wbuf.smap.add_close_mapping(node); } + ParserState Emitter::remap(const ParserState& pstate) + { return wbuf.smap.remap(pstate); } + + // MAIN BUFFER MANIPULATION + + // add outstanding delimiter + void Emitter::finalize(bool final) + { + scheduled_space = 0; + if (output_style() == SASS_STYLE_COMPRESSED) + if (final) scheduled_delimiter = false; + if (scheduled_linefeed) + scheduled_linefeed = 1; + flush_schedules(); + } + + // flush scheduled space/linefeed + void Emitter::flush_schedules(void) + { + // check the schedule + if (scheduled_linefeed) { + std::string linefeeds = ""; + + for (size_t i = 0; i < scheduled_linefeed; i++) + linefeeds += opt.linefeed; + scheduled_space = 0; + scheduled_linefeed = 0; + append_string(linefeeds); + + } else if (scheduled_space) { + std::string spaces(scheduled_space, ' '); + scheduled_space = 0; + append_string(spaces); + } + if (scheduled_delimiter) { + scheduled_delimiter = false; + append_string(";"); + } + } + + // prepend some text or token to the buffer + void Emitter::prepend_output(const OutputBuffer& output) + { + wbuf.smap.prepend(output); + wbuf.buffer = output.buffer + wbuf.buffer; + } + + // prepend some text or token to the buffer + void Emitter::prepend_string(const std::string& text) + { + // do not adjust mappings for utf8 bom + // seems they are not counted in any UA + if (text.compare("\xEF\xBB\xBF") != 0) { + wbuf.smap.prepend(Offset(text)); + } + wbuf.buffer = text + wbuf.buffer; + } + + char Emitter::last_char() + { + return wbuf.buffer.back(); + } + + // append a single char to the buffer + void Emitter::append_char(const char chr) + { + // write space/lf + flush_schedules(); + // add to buffer + wbuf.buffer += chr; + // account for data in source-maps + wbuf.smap.append(Offset(chr)); + } + + // append some text or token to the buffer + void Emitter::append_string(const std::string& text) + { + + // write space/lf + flush_schedules(); + + if (in_comment && output_style() == COMPACT) { + // unescape comment nodes + std::string out = comment_to_string(text); + // add to buffer + wbuf.buffer += out; + // account for data in source-maps + wbuf.smap.append(Offset(out)); + } else { + // add to buffer + wbuf.buffer += text; + // account for data in source-maps + wbuf.smap.append(Offset(text)); + } + } + + // append some white-space only text + void Emitter::append_wspace(const std::string& text) + { + if (text.empty()) return; + if (peek_linefeed(text.c_str())) { + scheduled_space = 0; + append_mandatory_linefeed(); + } + } + + // append some text or token to the buffer + // this adds source-mappings for node start and end + void Emitter::append_token(const std::string& text, const AST_Node_Ptr node) + { + flush_schedules(); + add_open_mapping(node); + // hotfix for browser issues + // this is pretty ugly indeed + if (scheduled_crutch) { + add_open_mapping(scheduled_crutch); + scheduled_crutch = 0; + } + append_string(text); + add_close_mapping(node); + } + + // HELPER METHODS + + void Emitter::append_indentation() + { + if (output_style() == COMPRESSED) return; + if (output_style() == COMPACT) return; + if (in_declaration && in_comma_array) return; + if (scheduled_linefeed && indentation) + scheduled_linefeed = 1; + std::string indent = ""; + for (size_t i = 0; i < indentation; i++) + indent += opt.indent; + append_string(indent); + } + + void Emitter::append_delimiter() + { + scheduled_delimiter = true; + if (output_style() == COMPACT) { + if (indentation == 0) { + append_mandatory_linefeed(); + } else { + append_mandatory_space(); + } + } else if (output_style() != COMPRESSED) { + append_optional_linefeed(); + } + } + + void Emitter::append_comma_separator() + { + // scheduled_space = 0; + append_string(","); + append_optional_space(); + } + + void Emitter::append_colon_separator() + { + scheduled_space = 0; + append_string(":"); + if (!in_custom_property) append_optional_space(); + } + + void Emitter::append_mandatory_space() + { + scheduled_space = 1; + } + + void Emitter::append_optional_space() + { + if ((output_style() != COMPRESSED) && buffer().size()) { + unsigned char lst = buffer().at(buffer().length() - 1); + if (!isspace(lst) || scheduled_delimiter) { + if (last_char() != '(') { + append_mandatory_space(); + } + } + } + } + + void Emitter::append_special_linefeed() + { + if (output_style() == COMPACT) { + append_mandatory_linefeed(); + for (size_t p = 0; p < indentation; p++) + append_string(opt.indent); + } + } + + void Emitter::append_optional_linefeed() + { + if (in_declaration && in_comma_array) return; + if (output_style() == COMPACT) { + append_mandatory_space(); + } else { + append_mandatory_linefeed(); + } + } + + void Emitter::append_mandatory_linefeed() + { + if (output_style() != COMPRESSED) { + scheduled_linefeed = 1; + scheduled_space = 0; + // flush_schedules(); + } + } + + void Emitter::append_scope_opener(AST_Node_Ptr node) + { + scheduled_linefeed = 0; + append_optional_space(); + flush_schedules(); + if (node) add_open_mapping(node); + append_string("{"); + append_optional_linefeed(); + // append_optional_space(); + ++ indentation; + } + void Emitter::append_scope_closer(AST_Node_Ptr node) + { + -- indentation; + scheduled_linefeed = 0; + if (output_style() == COMPRESSED) + scheduled_delimiter = false; + if (output_style() == EXPANDED) { + append_optional_linefeed(); + append_indentation(); + } else { + append_optional_space(); + } + append_string("}"); + if (node) add_close_mapping(node); + append_optional_linefeed(); + if (indentation != 0) return; + if (output_style() != COMPRESSED) + scheduled_linefeed = 2; + } + +} diff --git a/src/emitter.hpp b/src/emitter.hpp new file mode 100644 index 000000000..3bf8f60da --- /dev/null +++ b/src/emitter.hpp @@ -0,0 +1,99 @@ +#ifndef SASS_EMITTER_H +#define SASS_EMITTER_H + +#include +#include "sass.hpp" +#include "sass/base.h" +#include "source_map.hpp" +#include "ast_fwd_decl.hpp" + +namespace Sass { + class Context; + + class Emitter { + + public: + Emitter(struct Sass_Output_Options& opt); + virtual ~Emitter() { } + + protected: + OutputBuffer wbuf; + public: + const std::string& buffer(void) { return wbuf.buffer; } + const SourceMap smap(void) { return wbuf.smap; } + const OutputBuffer output(void) { return wbuf; } + // proxy methods for source maps + void add_source_index(size_t idx); + void set_filename(const std::string& str); + void add_open_mapping(const AST_Node_Ptr node); + void add_close_mapping(const AST_Node_Ptr node); + void schedule_mapping(const AST_Node_Ptr node); + std::string render_srcmap(Context &ctx); + ParserState remap(const ParserState& pstate); + + public: + struct Sass_Output_Options& opt; + size_t indentation; + size_t scheduled_space; + size_t scheduled_linefeed; + bool scheduled_delimiter; + AST_Node_Ptr scheduled_crutch; + AST_Node_Ptr scheduled_mapping; + + public: + // output strings different in custom css properties + bool in_custom_property; + // output strings different in comments + bool in_comment; + // selector list does not get linefeeds + bool in_wrapped; + // lists always get a space after delimiter + bool in_media_block; + // nested list must not have parentheses + bool in_declaration; + // nested lists need parentheses + bool in_space_array; + bool in_comma_array; + + public: + // return buffer as std::string + std::string get_buffer(void); + // flush scheduled space/linefeed + Sass_Output_Style output_style(void) const; + // add outstanding linefeed + void finalize(bool final = true); + // flush scheduled space/linefeed + void flush_schedules(void); + // prepend some text or token to the buffer + void prepend_string(const std::string& text); + void prepend_output(const OutputBuffer& out); + // append some text or token to the buffer + void append_string(const std::string& text); + // append a single character to buffer + void append_char(const char chr); + // append some white-space only text + void append_wspace(const std::string& text); + // append some text or token to the buffer + // this adds source-mappings for node start and end + void append_token(const std::string& text, const AST_Node_Ptr node); + // query last appended character + char last_char(); + + public: // syntax sugar + void append_indentation(); + void append_optional_space(void); + void append_mandatory_space(void); + void append_special_linefeed(void); + void append_optional_linefeed(void); + void append_mandatory_linefeed(void); + void append_scope_opener(AST_Node_Ptr node = 0); + void append_scope_closer(AST_Node_Ptr node = 0); + void append_comma_separator(void); + void append_colon_separator(void); + void append_delimiter(void); + + }; + +} + +#endif diff --git a/src/environment.cpp b/src/environment.cpp new file mode 100644 index 000000000..e382e7e05 --- /dev/null +++ b/src/environment.cpp @@ -0,0 +1,246 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "environment.hpp" + +namespace Sass { + + template + Environment::Environment(bool is_shadow) + : local_frame_(environment_map()), + parent_(0), is_shadow_(false) + { } + template + Environment::Environment(Environment* env, bool is_shadow) + : local_frame_(environment_map()), + parent_(env), is_shadow_(is_shadow) + { } + template + Environment::Environment(Environment& env, bool is_shadow) + : local_frame_(environment_map()), + parent_(&env), is_shadow_(is_shadow) + { } + + // link parent to create a stack + template + void Environment::link(Environment& env) { parent_ = &env; } + template + void Environment::link(Environment* env) { parent_ = env; } + + // this is used to find the global frame + // which is the second last on the stack + template + bool Environment::is_lexical() const + { + return !! parent_ && parent_->parent_; + } + + // only match the real root scope + // there is still a parent around + // not sure what it is actually use for + // I guess we store functions etc. there + template + bool Environment::is_global() const + { + return parent_ && ! parent_->parent_; + } + + template + environment_map& Environment::local_frame() { + return local_frame_; + } + + template + bool Environment::has_local(const std::string& key) const + { return local_frame_.find(key) != local_frame_.end(); } + + template EnvResult + Environment::find_local(const std::string& key) + { + auto end = local_frame_.end(); + auto it = local_frame_.find(key); + return EnvResult(it, it != end); + } + + template + T& Environment::get_local(const std::string& key) + { return local_frame_[key]; } + + template + void Environment::set_local(const std::string& key, const T& val) + { + local_frame_[key] = val; + } + template + void Environment::set_local(const std::string& key, T&& val) + { + local_frame_[key] = val; + } + + template + void Environment::del_local(const std::string& key) + { local_frame_.erase(key); } + + template + Environment* Environment::global_env() + { + Environment* cur = this; + while (cur->is_lexical()) { + cur = cur->parent_; + } + return cur; + } + + template + bool Environment::has_global(const std::string& key) + { return global_env()->has(key); } + + template + T& Environment::get_global(const std::string& key) + { return (*global_env())[key]; } + + template + void Environment::set_global(const std::string& key, const T& val) + { + global_env()->local_frame_[key] = val; + } + template + void Environment::set_global(const std::string& key, T&& val) + { + global_env()->local_frame_[key] = val; + } + + template + void Environment::del_global(const std::string& key) + { global_env()->local_frame_.erase(key); } + + template + Environment* Environment::lexical_env(const std::string& key) + { + Environment* cur = this; + while (cur) { + if (cur->has_local(key)) { + return cur; + } + cur = cur->parent_; + } + return this; + } + + // see if we have a lexical variable + // move down the stack but stop before we + // reach the global frame (is not included) + template + bool Environment::has_lexical(const std::string& key) const + { + auto cur = this; + while (cur->is_lexical()) { + if (cur->has_local(key)) return true; + cur = cur->parent_; + } + return false; + } + + // see if we have a lexical we could update + // either update already existing lexical value + // or if flag is set, we create one if no lexical found + template + void Environment::set_lexical(const std::string& key, const T& val) + { + Environment* cur = this; + bool shadow = false; + while ((cur && cur->is_lexical()) || shadow) { + EnvResult rv(cur->find_local(key)); + if (rv.found) { + rv.it->second = val; + return; + } + shadow = cur->is_shadow(); + cur = cur->parent_; + } + set_local(key, val); + } + // this one moves the value + template + void Environment::set_lexical(const std::string& key, T&& val) + { + Environment* cur = this; + bool shadow = false; + while ((cur && cur->is_lexical()) || shadow) { + EnvResult rv(cur->find_local(key)); + if (rv.found) { + rv.it->second = val; + return; + } + shadow = cur->is_shadow(); + cur = cur->parent_; + } + set_local(key, val); + } + + // look on the full stack for key + // include all scopes available + template + bool Environment::has(const std::string& key) const + { + auto cur = this; + while (cur) { + if (cur->has_local(key)) { + return true; + } + cur = cur->parent_; + } + return false; + } + + // look on the full stack for key + // include all scopes available + template EnvResult + Environment::find(const std::string& key) + { + auto cur = this; + while (true) { + EnvResult rv(cur->find_local(key)); + if (rv.found) return rv; + cur = cur->parent_; + if (!cur) return rv; + } + }; + + // use array access for getter and setter functions + template + T& Environment::operator[](const std::string& key) + { + auto cur = this; + while (cur) { + if (cur->has_local(key)) { + return cur->get_local(key); + } + cur = cur->parent_; + } + return get_local(key); + } +/* + #ifdef DEBUG + template + size_t Environment::print(std::string prefix) + { + size_t indent = 0; + if (parent_) indent = parent_->print(prefix) + 1; + std::cerr << prefix << std::string(indent, ' ') << "== " << this << std::endl; + for (typename environment_map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { + if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { + std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second; + if (Value_Ptr val = Cast(i->second)) + { std::cerr << " : " << val->to_string(); } + std::cerr << std::endl; + } + } + return indent ; + } + #endif +*/ + // compile implementation for AST_Node + template class Environment; + +} + diff --git a/src/environment.hpp b/src/environment.hpp new file mode 100644 index 000000000..a6939be23 --- /dev/null +++ b/src/environment.hpp @@ -0,0 +1,113 @@ +#ifndef SASS_ENVIRONMENT_H +#define SASS_ENVIRONMENT_H + +#include +#include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" + +namespace Sass { + + typedef environment_map::iterator EnvIter; + + class EnvResult { + public: + EnvIter it; + bool found; + public: + EnvResult(EnvIter it, bool found) + : it(it), found(found) {} + }; + + template + class Environment { + // TODO: test with map + environment_map local_frame_; + ADD_PROPERTY(Environment*, parent) + ADD_PROPERTY(bool, is_shadow) + + public: + Environment(bool is_shadow = false); + Environment(Environment* env, bool is_shadow = false); + Environment(Environment& env, bool is_shadow = false); + + // link parent to create a stack + void link(Environment& env); + void link(Environment* env); + + // this is used to find the global frame + // which is the second last on the stack + bool is_lexical() const; + + // only match the real root scope + // there is still a parent around + // not sure what it is actually use for + // I guess we store functions etc. there + bool is_global() const; + + // scope operates on the current frame + + environment_map& local_frame(); + + bool has_local(const std::string& key) const; + + EnvResult find_local(const std::string& key); + + T& get_local(const std::string& key); + + // set variable on the current frame + void set_local(const std::string& key, const T& val); + void set_local(const std::string& key, T&& val); + + void del_local(const std::string& key); + + // global operates on the global frame + // which is the second last on the stack + Environment* global_env(); + // get the env where the variable already exists + // if it does not yet exist, we return current env + Environment* lexical_env(const std::string& key); + + bool has_global(const std::string& key); + + T& get_global(const std::string& key); + + // set a variable on the global frame + void set_global(const std::string& key, const T& val); + void set_global(const std::string& key, T&& val); + + void del_global(const std::string& key); + + // see if we have a lexical variable + // move down the stack but stop before we + // reach the global frame (is not included) + bool has_lexical(const std::string& key) const; + + // see if we have a lexical we could update + // either update already existing lexical value + // or we create a new one on the current frame + void set_lexical(const std::string& key, T&& val); + void set_lexical(const std::string& key, const T& val); + + // look on the full stack for key + // include all scopes available + bool has(const std::string& key) const; + + // look on the full stack for key + // include all scopes available + EnvResult find(const std::string& key); + + // use array access for getter and setter functions + T& operator[](const std::string& key); + + #ifdef DEBUG + size_t print(std::string prefix = ""); + #endif + + }; + + // define typedef for our use case + typedef Environment Env; + +} + +#endif diff --git a/src/error_handling.cpp b/src/error_handling.cpp new file mode 100644 index 000000000..745f65508 --- /dev/null +++ b/src/error_handling.cpp @@ -0,0 +1,235 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "prelexer.hpp" +#include "backtrace.hpp" +#include "error_handling.hpp" + +#include + +namespace Sass { + + namespace Exception { + + Base::Base(ParserState pstate, std::string msg, Backtraces traces) + : std::runtime_error(msg), msg(msg), + prefix("Error"), pstate(pstate), traces(traces) + { } + + InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) + { } + + + InvalidParent::InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector) + : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) + { + msg = "Invalid parent selector for \""; + msg += selector->to_string(Sass_Inspect_Options()); + msg += "\": \""; + msg += parent->to_string(Sass_Inspect_Options()); + msg += "\""; + } + + InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg) + : Base(pstate, def_msg, traces), name(name), arg(arg) + { + msg = "Variable keyword argument map must have string keys.\n"; + msg += name + " is not a string in " + arg->to_string() + "."; + } + + InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value) + : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) + { + msg = arg + ": \""; + if (value) msg += value->to_string(Sass_Inspect_Options()); + msg += "\" is not a " + type; + msg += " for `" + fn + "'"; + } + + MissingArgument::MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype) + : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) + { + msg = fntype + " " + fn; + msg += " is missing argument "; + msg += arg + "."; + } + + InvalidSyntax::InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) + { } + + NestingLimitError::NestingLimitError(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) + { } + + DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) + : Base(org.pstate(), def_msg, traces), dup(dup), org(org) + { + msg = "Duplicate key "; + msg += dup.get_duplicate_key()->inspect(); + msg += " in map ("; + msg += org.inspect(); + msg += ")."; + } + + TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const std::string type) + : Base(var.pstate(), def_msg, traces), var(var), type(type) + { + msg = var.to_string(); + msg += " is not an "; + msg += type; + msg += "."; + } + + InvalidValue::InvalidValue(Backtraces traces, const Expression& val) + : Base(val.pstate(), def_msg, traces), val(val) + { + msg = val.to_string(); + msg += " isn't a valid CSS value."; + } + + StackError::StackError(Backtraces traces, const AST_Node& node) + : Base(node.pstate(), def_msg, traces), node(node) + { + msg = "stack level too deep"; + } + + IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) + { + msg = "Incompatible units: '"; + msg += rhs.unit(); + msg += "' and '"; + msg += lhs.unit(); + msg += "'."; + } + + IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs) + { + msg = "Incompatible units: '"; + msg += unit_to_string(rhs); + msg += "' and '"; + msg += unit_to_string(lhs); + msg += "'."; + } + + AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : OperationError(), lhs(lhs), rhs(rhs), op(op) + { + msg = "Alpha channels must be equal: "; + msg += lhs->to_string({ NESTED, 5 }); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->to_string({ NESTED, 5 }); + msg += "."; + } + + ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) + : OperationError(), lhs(lhs), rhs(rhs) + { + msg = "divided by 0"; + } + + UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : OperationError(), lhs(lhs), rhs(rhs), op(op) + { + msg = def_op_msg + ": \""; + msg += lhs->to_string({ NESTED, 5 }); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->to_string({ TO_SASS, 5 }); + msg += "\"."; + } + + InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : UndefinedOperation(lhs, rhs, op) + { + msg = def_op_null_msg + ": \""; + msg += lhs->inspect(); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->inspect(); + msg += "\"."; + } + + SassValueError::SassValueError(Backtraces traces, ParserState pstate, OperationError& err) + : Base(pstate, err.what(), traces) + { + msg = err.what(); + prefix = err.errtype(); + } + + } + + + void warn(std::string msg, ParserState pstate) + { + std::cerr << "Warning: " << msg << std::endl; + } + + void warning(std::string msg, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); + + std::cerr << "WARNING on line " << pstate.line+1 << ", column " << pstate.column+1 << " of " << output_path << ":" << std::endl; + std::cerr << msg << std::endl << std::endl; + } + + void warn(std::string msg, ParserState pstate, Backtrace* bt) + { + warn(msg, pstate); + } + + void deprecated_function(std::string msg, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); + + std::cerr << "DEPRECATION WARNING: " << msg << std::endl; + std::cerr << "will be an error in future versions of Sass." << std::endl; + std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; + } + + void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, pstate.path, pstate.path)); + + std::cerr << "DEPRECATION WARNING on line " << pstate.line + 1; + if (with_column) std::cerr << ", column " << pstate.column + pstate.offset.column + 1; + if (output_path.length()) std::cerr << " of " << output_path; + std::cerr << ":" << std::endl; + std::cerr << msg << std::endl; + if (msg2.length()) std::cerr << msg2 << std::endl; + std::cerr << std::endl; + } + + void deprecated_bind(std::string msg, ParserState pstate) + { + std::string cwd(Sass::File::get_cwd()); + std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path)); + + std::cerr << "WARNING: " << msg << std::endl; + std::cerr << " on line " << pstate.line+1 << " of " << output_path << std::endl; + std::cerr << "This will be an error in future versions of Sass." << std::endl; + } + + // should be replaced with error with backtraces + void coreError(std::string msg, ParserState pstate) + { + Backtraces traces; + throw Exception::InvalidSyntax(pstate, traces, msg); + } + + void error(std::string msg, ParserState pstate, Backtraces& traces) + { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSyntax(pstate, traces, msg); + } + +} diff --git a/src/error_handling.hpp b/src/error_handling.hpp new file mode 100644 index 000000000..f863792ea --- /dev/null +++ b/src/error_handling.hpp @@ -0,0 +1,216 @@ +#ifndef SASS_ERROR_HANDLING_H +#define SASS_ERROR_HANDLING_H + +#include +#include +#include +#include "position.hpp" +#include "backtrace.hpp" +#include "ast_fwd_decl.hpp" +#include "sass/functions.h" + +namespace Sass { + + struct Backtrace; + + namespace Exception { + + const std::string def_msg = "Invalid sass detected"; + const std::string def_op_msg = "Undefined operation"; + const std::string def_op_null_msg = "Invalid null operation"; + const std::string def_nesting_limit = "Code too deeply neested"; + + class Base : public std::runtime_error { + protected: + std::string msg; + std::string prefix; + public: + ParserState pstate; + Backtraces traces; + public: + Base(ParserState pstate, std::string msg, Backtraces traces); + virtual const char* errtype() const { return prefix.c_str(); } + virtual const char* what() const throw() { return msg.c_str(); } + virtual ~Base() throw() {}; + }; + + class InvalidSass : public Base { + public: + InvalidSass(ParserState pstate, Backtraces traces, std::string msg); + virtual ~InvalidSass() throw() {}; + }; + + class InvalidParent : public Base { + protected: + Selector_Ptr parent; + Selector_Ptr selector; + public: + InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector); + virtual ~InvalidParent() throw() {}; + }; + + class MissingArgument : public Base { + protected: + std::string fn; + std::string arg; + std::string fntype; + public: + MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype); + virtual ~MissingArgument() throw() {}; + }; + + class InvalidArgumentType : public Base { + protected: + std::string fn; + std::string arg; + std::string type; + const Value_Ptr value; + public: + InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); + virtual ~InvalidArgumentType() throw() {}; + }; + + class InvalidVarKwdType : public Base { + protected: + std::string name; + const Argument_Ptr arg; + public: + InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg = 0); + virtual ~InvalidVarKwdType() throw() {}; + }; + + class InvalidSyntax : public Base { + public: + InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg); + virtual ~InvalidSyntax() throw() {}; + }; + + class NestingLimitError : public Base { + public: + NestingLimitError(ParserState pstate, Backtraces traces, std::string msg = def_nesting_limit); + virtual ~NestingLimitError() throw() {}; + }; + + class DuplicateKeyError : public Base { + protected: + const Map& dup; + const Expression& org; + public: + DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org); + virtual const char* errtype() const { return "Error"; } + virtual ~DuplicateKeyError() throw() {}; + }; + + class TypeMismatch : public Base { + protected: + const Expression& var; + const std::string type; + public: + TypeMismatch(Backtraces traces, const Expression& var, const std::string type); + virtual const char* errtype() const { return "Error"; } + virtual ~TypeMismatch() throw() {}; + }; + + class InvalidValue : public Base { + protected: + const Expression& val; + public: + InvalidValue(Backtraces traces, const Expression& val); + virtual const char* errtype() const { return "Error"; } + virtual ~InvalidValue() throw() {}; + }; + + class StackError : public Base { + protected: + const AST_Node& node; + public: + StackError(Backtraces traces, const AST_Node& node); + virtual const char* errtype() const { return "SystemStackError"; } + virtual ~StackError() throw() {}; + }; + + /* common virtual base class (has no pstate or trace) */ + class OperationError : public std::runtime_error { + protected: + std::string msg; + public: + OperationError(std::string msg = def_op_msg) + : std::runtime_error(msg), msg(msg) + {}; + public: + virtual const char* errtype() const { return "Error"; } + virtual const char* what() const throw() { return msg.c_str(); } + virtual ~OperationError() throw() {}; + }; + + class ZeroDivisionError : public OperationError { + protected: + const Expression& lhs; + const Expression& rhs; + public: + ZeroDivisionError(const Expression& lhs, const Expression& rhs); + virtual const char* errtype() const { return "ZeroDivisionError"; } + virtual ~ZeroDivisionError() throw() {}; + }; + + class IncompatibleUnits : public OperationError { + protected: + // const Sass::UnitType lhs; + // const Sass::UnitType rhs; + public: + IncompatibleUnits(const Units& lhs, const Units& rhs); + IncompatibleUnits(const UnitType lhs, const UnitType rhs); + virtual ~IncompatibleUnits() throw() {}; + }; + + class UndefinedOperation : public OperationError { + protected: + Expression_Ptr_Const lhs; + Expression_Ptr_Const rhs; + const Sass_OP op; + public: + UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); + // virtual const char* errtype() const { return "Error"; } + virtual ~UndefinedOperation() throw() {}; + }; + + class InvalidNullOperation : public UndefinedOperation { + public: + InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); + virtual ~InvalidNullOperation() throw() {}; + }; + + class AlphaChannelsNotEqual : public OperationError { + protected: + Expression_Ptr_Const lhs; + Expression_Ptr_Const rhs; + const Sass_OP op; + public: + AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); + // virtual const char* errtype() const { return "Error"; } + virtual ~AlphaChannelsNotEqual() throw() {}; + }; + + class SassValueError : public Base { + public: + SassValueError(Backtraces traces, ParserState pstate, OperationError& err); + virtual ~SassValueError() throw() {}; + }; + + } + + void warn(std::string msg, ParserState pstate); + void warn(std::string msg, ParserState pstate, Backtrace* bt); + void warning(std::string msg, ParserState pstate); + + void deprecated_function(std::string msg, ParserState pstate); + void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate); + void deprecated_bind(std::string msg, ParserState pstate); + // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); + + void coreError(std::string msg, ParserState pstate); + void error(std::string msg, ParserState pstate, Backtraces& traces); + +} + +#endif diff --git a/src/eval.cpp b/src/eval.cpp new file mode 100644 index 000000000..841f7277b --- /dev/null +++ b/src/eval.cpp @@ -0,0 +1,1663 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "file.hpp" +#include "eval.hpp" +#include "ast.hpp" +#include "bind.hpp" +#include "util.hpp" +#include "inspect.hpp" +#include "operators.hpp" +#include "environment.hpp" +#include "position.hpp" +#include "sass/values.h" +#include "to_value.hpp" +#include "to_c.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "lexer.hpp" +#include "prelexer.hpp" +#include "parser.hpp" +#include "expand.hpp" +#include "color_maps.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + Eval::Eval(Expand& exp) + : exp(exp), + ctx(exp.ctx), + traces(exp.traces), + force(false), + is_in_comment(false), + is_in_selector_schema(false) + { + bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); + bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); + } + Eval::~Eval() { } + + Env* Eval::environment() + { + return exp.environment(); + } + + Selector_List_Obj Eval::selector() + { + return exp.selector(); + } + + Expression_Ptr Eval::operator()(Block_Ptr b) + { + Expression_Ptr val = 0; + for (size_t i = 0, L = b->length(); i < L; ++i) { + val = b->at(i)->perform(this); + if (val) return val; + } + return val; + } + + Expression_Ptr Eval::operator()(Assignment_Ptr a) + { + Env* env = exp.environment(); + std::string var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Ptr e = Cast(env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Ptr e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(this)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Ptr e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(this)); + } + else { + env->set_local(var, a->value()->perform(this)); + } + } + else { + env->set_lexical(var, a->value()->perform(this)); + } + return 0; + } + + Expression_Ptr Eval::operator()(If_Ptr i) + { + Expression_Obj rv = 0; + Env env(exp.environment()); + exp.env_stack.push_back(&env); + Expression_Obj cond = i->predicate()->perform(this); + if (!cond->is_false()) { + rv = i->block()->perform(this); + } + else { + Block_Obj alt = i->alternative(); + if (alt) rv = alt->perform(this); + } + exp.env_stack.pop_back(); + return rv.detach(); + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(this); + if (low->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(this); + if (high->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); + } + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_end->unit() << "' and '" + << sass_start->unit() << "'."; + error(msg.str(), low->pstate(), traces); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + exp.env_stack.push_back(&env); + Block_Obj body = f->block(); + Expression_Ptr val = 0; + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(this); + Env env(environment(), true); + exp.env_stack.push_back(&env); + List_Obj list = 0; + Map_Ptr map = 0; + if (expr->concrete_type() == Expression::MAP) { + map = Cast(expr); + } + else if (Selector_List_Ptr ls = Cast(expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = Cast(rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = Cast(expr); + } + + Block_Obj body = e->block(); + Expression_Obj val = 0; + + if (map) { + for (Expression_Obj key : map->keys()) { + Expression_Obj value = map->at(key); + + if (variables.size() == 1) { + List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(key); + variable->append(value); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], key); + env.set_local(variables[1], value); + } + + val = body->perform(this); + if (val) break; + } + } + else { + if (list->length() == 1 && Cast(list)) { + list = Cast(list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Ptr item = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Ptr arg = Cast(item)) item = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Ptr scalars = Cast(item)) { + if (variables.size() == 1) { + Expression_Ptr var = scalars; + env.set_local(variables[0], var); + } else { + // XXX: this is never hit via spec tests + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Ptr res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : scalars->at(j); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + // XXX: this is never hit via spec tests + Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val.detach(); + } + + Expression_Ptr Eval::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Obj body = w->block(); + Env env(environment(), true); + exp.env_stack.push_back(&env); + Expression_Obj cond = pred->perform(this); + while (!cond->is_false()) { + Expression_Obj val = body->perform(this); + if (val) { + exp.env_stack.pop_back(); + return val.detach(); + } + cond = pred->perform(this); + } + exp.env_stack.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Return_Ptr r) + { + return r->value()->perform(this); + } + + Expression_Ptr Eval::operator()(Warning_Ptr w) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = w->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@warn[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@warn", + w->pstate().path, + w->pstate().line + 1, + w->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@warn[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + std::cerr << "WARNING: " << result << std::endl; + traces.push_back(Backtrace(w->pstate())); + std::cerr << traces_to_string(traces, " "); + std::cerr << std::endl; + ctx.c_options.output_style = outstyle; + traces.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Error_Ptr e) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = e->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@error[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@error", + e->pstate().path, + e->pstate().line + 1, + e->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@error[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + ctx.c_options.output_style = outstyle; + error(result, e->pstate(), traces); + return 0; + } + + Expression_Ptr Eval::operator()(Debug_Ptr d) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = d->value()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@debug[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@debug", + d->pstate().path, + d->pstate().line + 1, + d->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@debug[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string cwd(ctx.cwd()); + std::string result(unquote(message->to_sass())); + std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); + ctx.c_options.output_style = outstyle; + + std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; + std::cerr << std::endl; + return 0; + } + + Expression_Ptr Eval::operator()(List_Ptr l) + { + // special case for unevaluated map + if (l->separator() == SASS_HASH) { + Map_Obj lm = SASS_MEMORY_NEW(Map, + l->pstate(), + l->length() / 2); + for (size_t i = 0, L = l->length(); i < L; i += 2) + { + Expression_Obj key = (*l)[i+0]->perform(this); + Expression_Obj val = (*l)[i+1]->perform(this); + // make sure the color key never displays its real name + key->is_delayed(true); // verified + *lm << std::make_pair(key, val); + } + if (lm->has_duplicate_key()) { + traces.push_back(Backtrace(l->pstate())); + throw Exception::DuplicateKeyError(traces, *lm, *l); + } + + lm->is_interpolant(l->is_interpolant()); + return lm->perform(this); + } + // check if we should expand it + if (l->is_expanded()) return l; + // regular case for unevaluated lists + List_Obj ll = SASS_MEMORY_NEW(List, + l->pstate(), + l->length(), + l->separator(), + l->is_arglist(), + l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + ll->append((*l)[i]->perform(this)); + } + ll->is_interpolant(l->is_interpolant()); + ll->from_selector(l->from_selector()); + ll->is_expanded(true); + return ll.detach(); + } + + Expression_Ptr Eval::operator()(Map_Ptr m) + { + if (m->is_expanded()) return m; + + // make sure we're not starting with duplicate keys. + // the duplicate key state will have been set in the parser phase. + if (m->has_duplicate_key()) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *m, *m); + } + + Map_Obj mm = SASS_MEMORY_NEW(Map, + m->pstate(), + m->length()); + for (auto key : m->keys()) { + Expression_Ptr ex_key = key->perform(this); + Expression_Ptr ex_val = m->at(key); + if (ex_val == NULL) continue; + ex_val = ex_val->perform(this); + *mm << std::make_pair(ex_key, ex_val); + } + + // check the evaluated keys aren't duplicates. + if (mm->has_duplicate_key()) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *mm, *m); + } + + mm->is_expanded(true); + return mm.detach(); + } + + Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) + { + + Expression_Obj lhs = b_in->left(); + Expression_Obj rhs = b_in->right(); + enum Sass_OP op_type = b_in->optype(); + + if (op_type == Sass_OP::AND) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (!*lhs) return lhs.detach(); + return rhs->perform(this); + } + else if (op_type == Sass_OP::OR) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (*lhs) return lhs.detach(); + return rhs->perform(this); + } + + // Evaluate variables as early o + while (Variable_Ptr l_v = Cast(lhs)) { + lhs = operator()(l_v); + } + while (Variable_Ptr r_v = Cast(rhs)) { + rhs = operator()(r_v); + } + + Binary_Expression_Obj b = b_in; + + // Evaluate sub-expressions early on + while (Binary_Expression_Ptr l_b = Cast(lhs)) { + if (!force && l_b->is_delayed()) break; + lhs = operator()(l_b); + } + while (Binary_Expression_Ptr r_b = Cast(rhs)) { + if (!force && r_b->is_delayed()) break; + rhs = operator()(r_b); + } + + // don't eval delayed expressions (the '/' when used as a separator) + if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { + b->right(b->right()->perform(this)); + b->left(b->left()->perform(this)); + return b.detach(); + } + + // specific types we know are final + // handle them early to avoid overhead + if (Number_Ptr l_n = Cast(lhs)) { + // lhs is number and rhs is number + if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; + case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; + case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + // lhs is number and rhs is color + else if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + } + else if (Color_Ptr l_c = Cast(lhs)) { + // lhs is color and rhs is color + if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; + case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; + case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + // lhs is color and rhs is number + else if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + } + + String_Schema_Obj ret_schema; + + // only the last item will be used to eval the binary expression + if (String_Schema_Ptr s_l = Cast(b->left())) { + if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), s_l->last(), b->right()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified + for (size_t i = 0; i < s_l->length() - 1; ++i) { + ret_schema->append(s_l->at(i)->perform(this)); + } + ret_schema->append(bin_ex->perform(this)); + return ret_schema->perform(this); + } + } + if (String_Schema_Ptr s_r = Cast(b->right())) { + + if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), b->left(), s_r->first()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified + ret_schema->append(bin_ex->perform(this)); + for (size_t i = 1; i < s_r->length(); ++i) { + ret_schema->append(s_r->at(i)->perform(this)); + } + return ret_schema->perform(this); + } + } + + // fully evaluate their values + if (op_type == Sass_OP::EQ || + op_type == Sass_OP::NEQ || + op_type == Sass_OP::GT || + op_type == Sass_OP::GTE || + op_type == Sass_OP::LT || + op_type == Sass_OP::LTE) + { + LOCAL_FLAG(force, true); + lhs->is_expanded(false); + lhs->set_delayed(false); + lhs = lhs->perform(this); + rhs->is_expanded(false); + rhs->set_delayed(false); + rhs = rhs->perform(this); + } + else { + lhs = lhs->perform(this); + } + + // not a logical connective, so go ahead and eval the rhs + rhs = rhs->perform(this); + AST_Node_Obj lu = lhs; + AST_Node_Obj ru = rhs; + + Expression::Concrete_Type l_type; + Expression::Concrete_Type r_type; + + // Is one of the operands an interpolant? + String_Schema_Obj s1 = Cast(b->left()); + String_Schema_Obj s2 = Cast(b->right()); + Binary_Expression_Obj b1 = Cast(b->left()); + Binary_Expression_Obj b2 = Cast(b->right()); + + bool schema_op = false; + + bool force_delay = (s2 && s2->is_left_interpolant()) || + (s1 && s1->is_right_interpolant()) || + (b1 && b1->is_right_interpolant()) || + (b2 && b2->is_left_interpolant()); + + if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) + { + if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || + op_type == Sass_OP::EQ) { + // If possible upgrade LHS to a number (for number to string compare) + if (String_Constant_Ptr str = Cast(lhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { + lhs = Parser::lexed_dimension(b->pstate(), str->value()); + } + } + // If possible upgrade RHS to a number (for string to number compare) + if (String_Constant_Ptr str = Cast(rhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { + rhs = Parser::lexed_dimension(b->pstate(), str->value()); + } + } + } + + To_Value to_value(ctx); + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); + + if (force_delay) { + std::string str(""); + str += v_l->to_string(ctx.c_options); + if (b->op().ws_before) str += " "; + str += b->separator(); + if (b->op().ws_after) str += " "; + str += v_r->to_string(ctx.c_options); + String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); + val->is_interpolant(b->left()->has_interpolant()); + return val; + } + } + + // see if it's a relational expression + try { + switch(op_type) { + case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); + case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); + case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); + default: break; + } + } + catch (Exception::OperationError& err) + { + // throw Exception::Base(b->pstate(), err.what()); + traces.push_back(Backtrace(b->pstate())); + throw Exception::SassValueError(traces, b->pstate(), err); + } + + l_type = lhs->concrete_type(); + r_type = rhs->concrete_type(); + + // ToDo: throw error in op functions + // ToDo: then catch and re-throw them + Expression_Obj rv; + try { + ParserState pstate(b->pstate()); + if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { + Number_Ptr l_n = Cast(lhs); + Number_Ptr r_n = Cast(rhs); + l_n->reduce(); r_n->reduce(); + rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); + } + else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { + Number_Ptr l_n = Cast(lhs); + Color_Ptr r_c = Cast(rhs); + rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { + Color_Ptr l_c = Cast(lhs); + Number_Ptr r_n = Cast(rhs); + rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { + Color_Ptr l_c = Cast(lhs); + Color_Ptr r_c = Cast(rhs); + rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); + } + else { + To_Value to_value(ctx); + // this will leak if perform does not return a value! + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); + bool interpolant = b->is_right_interpolant() || + b->is_left_interpolant() || + b->is_interpolant(); + if (op_type == Sass_OP::SUB) interpolant = false; + // if (op_type == Sass_OP::DIV) interpolant = true; + // check for type violations + if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { + traces.push_back(Backtrace(v_l->pstate())); + throw Exception::InvalidValue(traces, *v_l); + } + if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { + traces.push_back(Backtrace(v_r->pstate())); + throw Exception::InvalidValue(traces, *v_r); + } + Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress + if (String_Constant_Ptr str = Cast(ex)) + { + if (str->concrete_type() == Expression::STRING) + { + String_Constant_Ptr lstr = Cast(lhs); + String_Constant_Ptr rstr = Cast(rhs); + if (op_type != Sass_OP::SUB) { + if (String_Constant_Ptr org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } + } + } + ex->is_interpolant(b->is_interpolant()); + rv = ex; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b->pstate())); + // throw Exception::Base(b->pstate(), err.what()); + throw Exception::SassValueError(traces, b->pstate(), err); + } + + if (rv) { + if (schema_op) { + // XXX: this is never hit via spec tests + (*s2)[0] = rv; + rv = s2->perform(this); + } + } + + return rv.detach(); + + } + + Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) + { + Expression_Obj operand = u->operand()->perform(this); + if (u->optype() == Unary_Expression::NOT) { + Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); + result->value(!result->value()); + return result; + } + else if (Number_Obj nr = Cast(operand)) { + // negate value for minus unary expression + if (u->optype() == Unary_Expression::MINUS) { + Number_Obj cpy = SASS_MEMORY_COPY(nr); + cpy->value( - cpy->value() ); // negate value + return cpy.detach(); // return the copy + } + else if (u->optype() == Unary_Expression::SLASH) { + std::string str = '/' + nr->to_string(ctx.c_options); + return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); + } + // nothing for positive + return nr.detach(); + } + else { + // Special cases: +/- variables which evaluate to null ouput just +/-, + // but +/- null itself outputs the string + if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { + u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); + } + // Never apply unary opertions on colors @see #2140 + else if (Color_Ptr color = Cast(operand)) { + // Use the color name if this was eval with one + if (color->disp().length() > 0) { + operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); + u->operand(operand); + } + } + else { + u->operand(operand); + } + + return SASS_MEMORY_NEW(String_Quoted, + u->pstate(), + u->inspect()); + } + // unreachable + return u; + } + + Expression_Ptr Eval::operator()(Function_Call_Ptr c) + { + if (traces.size() > Constants::MaxCallStack) { + // XXX: this is never hit via spec tests + std::ostringstream stm; + stm << "Stack depth exceeded max of " << Constants::MaxCallStack; + error(stm.str(), c->pstate(), traces); + } + std::string name(Util::normalize_underscores(c->name())); + std::string full_name(name + "[f]"); + // we make a clone here, need to implement that further + Arguments_Obj args = c->arguments(); + + Env* env = environment(); + if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { + if (!env->has("*[f]")) { + for (Argument_Obj arg : args->elements()) { + if (List_Obj ls = Cast(arg->value())) { + if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); + } + } + args = Cast(args->perform(this)); + Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, + c->pstate(), + c->name(), + args); + if (args->has_named_arguments()) { + error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); + } + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, + c->pstate(), + lit->to_string(ctx.c_options)); + str->is_interpolant(c->is_interpolant()); + return str; + } else { + // call generic function + full_name = "*[f]"; + } + } + + // further delay for calls + if (full_name != "call[f]") { + args->set_delayed(false); // verified + } + if (full_name != "if[f]") { + args = Cast(args->perform(this)); + } + Definition_Ptr def = Cast((*env)[full_name]); + + if (c->func()) def = c->func()->definition(); + + if (def->is_overload_stub()) { + std::stringstream ss; + size_t L = args->length(); + // account for rest arguments + if (args->has_rest_argument() && args->length() > 0) { + // get the rest arguments list + List_Ptr rest = Cast(args->last()->value()); + // arguments before rest argument plus rest + if (rest) L += rest->length() - 1; + } + ss << full_name << L; + full_name = ss.str(); + std::string resolved_name(full_name); + if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); + def = Cast((*env)[resolved_name]); + } + + Expression_Obj result = c; + Block_Obj body = def->block(); + Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + + if (c->is_css()) return result.detach(); + + Parameters_Obj params = def->parameters(); + Env fn_env(def->environment()); + exp.env_stack.push_back(&fn_env); + + if (func || body) { + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + // eval the body if user-defined or special, invoke underlying CPP function if native + if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { + result = body->perform(this); + } + else if (func) { + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); + } + if (!result) { + error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); + } + ctx.callee_stack.pop_back(); + traces.pop_back(); + } + + // else if it's a user-defined c function + // convert call into C-API compatible form + else if (c_function) { + Sass_Function_Fn c_func = sass_function_get_function(c_function); + if (full_name == "*[f]") { + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); + Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); + new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); + new_args->concat(args); + args = new_args; + } + + // populates env with default values for params + std::string ff(c->name()); + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_C_FUNCTION, + { env } + }); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); + for(size_t i = 0; i < params->length(); i++) { + Parameter_Obj param = params->at(i); + std::string key = param->name(); + AST_Node_Obj node = fn_env.get_local(key); + Expression_Obj arg = Cast(node); + sass_list_set_value(c_args, i, arg->perform(&to_c)); + } + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + if (sass_value_get_tag(c_val) == SASS_ERROR) { + error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); + } else if (sass_value_get_tag(c_val) == SASS_WARNING) { + error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); + } + result = cval_to_astnode(c_val, traces, c->pstate()); + + ctx.callee_stack.pop_back(); + traces.pop_back(); + sass_delete_value(c_args); + if (c_val != c_args) + sass_delete_value(c_val); + } + + // link back to function definition + // only do this for custom functions + if (result->pstate().file == std::string::npos) + result->pstate(c->pstate()); + + result = result->perform(this); + result->is_interpolant(c->is_interpolant()); + exp.env_stack.pop_back(); + return result.detach(); + } + + Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) + { + Expression_Ptr evaluated_name = s->name()->perform(this); + Expression_Ptr evaluated_args = s->arguments()->perform(this); + String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); + ss->append(evaluated_name); + ss->append(evaluated_args); + return ss->perform(this); + } + + Expression_Ptr Eval::operator()(Variable_Ptr v) + { + Expression_Obj value = 0; + Env* env = environment(); + const std::string& name(v->name()); + EnvResult rv(env->find(name)); + if (rv.found) value = static_cast(rv.it->second.ptr()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); + if (Argument_Ptr arg = Cast(value)) value = arg->value(); + if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag + value->is_interpolant(v->is_interpolant()); + if (force) value->is_expanded(false); + value->set_delayed(false); // verified + value = value->perform(this); + if(!force) rv.it->second = value; + return value.detach(); + } + + Expression_Ptr Eval::operator()(Color_Ptr c) + { + return c; + } + + Expression_Ptr Eval::operator()(Number_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Boolean_Ptr b) + { + return b; + } + + void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { + + bool needs_closing_brace = false; + + if (Arguments_Ptr args = Cast(ex)) { + List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); + for(auto arg : args->elements()) { + ll->append(arg->value()); + } + ll->is_interpolant(args->is_interpolant()); + needs_closing_brace = true; + res += "("; + ex = ll; + } + if (Number_Ptr nr = Cast(ex)) { + Number reduced(nr); + reduced.reduce(); + if (!reduced.is_valid_css_unit()) { + traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue(traces, *nr); + } + } + if (Argument_Ptr arg = Cast(ex)) { + ex = arg->value(); + } + if (String_Quoted_Ptr sq = Cast(ex)) { + if (was_itpl) { + bool was_interpolant = ex->is_interpolant(); + ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); + ex->is_interpolant(was_interpolant); + } + } + + if (Cast(ex)) { return; } + + // parent selector needs another go + if (Cast(ex)) { + // XXX: this is never hit via spec tests + ex = ex->perform(this); + } + + if (List_Ptr l = Cast(ex)) { + List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); + // this fixes an issue with bourbon sample, not really sure why + // if (l->size() && Cast((*l)[0])) { res += ""; } + for(Expression_Obj item : *l) { + item->is_interpolant(l->is_interpolant()); + std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); + bool is_null = Cast(item) != 0; // rl != "" + if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); + } + // Check indicates that we probably should not get a list + // here. Normally single list items are already unwrapped. + if (l->size() > 1) { + // string_to_output would fail "#{'_\a' '_\a'}"; + std::string str(ll->to_string(ctx.c_options)); + str = read_hex_escapes(str); // read escapes + newline_to_space(str); // replace directly + res += str; // append to result string + } else { + res += (ll->to_string(ctx.c_options)); + } + ll->is_interpolant(l->is_interpolant()); + } + + // Value + // Function_Call + // Selector_List + // String_Quoted + // String_Constant + // Parent_Selector + // Binary_Expression + else { + // ex = ex->perform(this); + if (into_quotes && ex->is_interpolant()) { + res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); + } else { + std::string str(ex ? ex->to_string(ctx.c_options) : ""); + if (into_quotes) str = read_hex_escapes(str); + res += str; // append to result string + } + } + + if (needs_closing_brace) res += ")"; + + } + + Expression_Ptr Eval::operator()(String_Schema_Ptr s) + { + size_t L = s->length(); + bool into_quotes = false; + if (L > 1) { + if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { + if (String_Constant_Ptr l = Cast((*s)[0])) { + if (String_Constant_Ptr r = Cast((*s)[L - 1])) { + if (r->value().size() > 0) { + if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; + if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; + } + } + } + } + } + bool was_quoted = false; + bool was_interpolant = false; + std::string res(""); + for (size_t i = 0; i < L; ++i) { + bool is_quoted = Cast((*s)[i]) != NULL; + if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + Expression_Obj ex = (*s)[i]->perform(this); + interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); + was_quoted = Cast((*s)[i]) != NULL; + was_interpolant = (*s)[i]->is_interpolant(); + + } + if (!s->is_interpolant()) { + if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); + return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); + } + // string schema seems to have a special unquoting behavior (also handles "nested" quotes) + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); + // if (s->is_interpolant()) str->quote_mark(0); + // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + if (str->quote_mark()) str->quote_mark('*'); + else if (!is_in_comment) str->value(string_to_output(str->value())); + str->is_interpolant(s->is_interpolant()); + return str.detach(); + } + + + Expression_Ptr Eval::operator()(String_Constant_Ptr s) + { + return s; + } + + Expression_Ptr Eval::operator()(String_Quoted_Ptr s) + { + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); + str->value(s->value()); + str->quote_mark(s->quote_mark()); + str->is_interpolant(s->is_interpolant()); + return str; + } + + Expression_Ptr Eval::operator()(Supports_Operator_Ptr c) + { + Expression_Ptr left = c->left()->perform(this); + Expression_Ptr right = c->right()->perform(this); + Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, + c->pstate(), + Cast(left), + Cast(right), + c->operand()); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) + { + Expression_Ptr condition = c->condition()->perform(this); + Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, + c->pstate(), + Cast(condition)); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) + { + Expression_Ptr feature = c->feature()->perform(this); + Expression_Ptr value = c->value()->perform(this); + Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, + c->pstate(), + feature, + value); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) + { + Expression_Ptr value = c->value()->perform(this); + Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, + c->pstate(), + value); + return cc; + } + + Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, + e->pstate(), + Cast(feature), + value); + return ee; + } + + Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) + { + String_Obj t = q->media_type(); + t = static_cast(t.isNull() ? 0 : t->perform(this)); + Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, + q->pstate(), + t, + q->length(), + q->is_negated(), + q->is_restricted()); + for (size_t i = 0, L = q->length(); i < L; ++i) { + qq->append(static_cast((*q)[i]->perform(this))); + } + return qq.detach(); + } + + Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + if (feature && Cast(feature)) { + feature = SASS_MEMORY_NEW(String_Quoted, + feature->pstate(), + Cast(feature)->value()); + } + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + if (value && Cast(value)) { + // XXX: this is never hit via spec tests + value = SASS_MEMORY_NEW(String_Quoted, + value->pstate(), + Cast(value)->value()); + } + return SASS_MEMORY_NEW(Media_Query_Expression, + e->pstate(), + feature, + value, + e->is_interpolated()); + } + + Expression_Ptr Eval::operator()(Null_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Argument_Ptr a) + { + Expression_Obj val = a->value()->perform(this); + bool is_rest_argument = a->is_rest_argument(); + bool is_keyword_argument = a->is_keyword_argument(); + + if (a->is_rest_argument()) { + if (val->concrete_type() == Expression::MAP) { + is_rest_argument = false; + is_keyword_argument = true; + } + else if(val->concrete_type() != Expression::LIST) { + List_Obj wrapper = SASS_MEMORY_NEW(List, + val->pstate(), + 0, + SASS_COMMA, + true); + wrapper->append(val); + val = wrapper; + } + } + return SASS_MEMORY_NEW(Argument, + a->pstate(), + val, + a->name(), + is_rest_argument, + is_keyword_argument); + } + + Expression_Ptr Eval::operator()(Arguments_Ptr a) + { + Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); + if (a->length() == 0) return aa.detach(); + for (size_t i = 0, L = a->length(); i < L; ++i) { + Expression_Obj rv = (*a)[i]->perform(this); + Argument_Ptr arg = Cast(rv); + if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { + aa->append(arg); + } + } + + if (a->has_rest_argument()) { + Expression_Obj rest = a->get_rest_argument()->perform(this); + Expression_Obj splat = Cast(rest)->value()->perform(this); + + Sass_Separator separator = SASS_COMMA; + List_Ptr ls = Cast(splat); + Map_Ptr ms = Cast(splat); + + List_Obj arglist = SASS_MEMORY_NEW(List, + splat->pstate(), + 0, + ls ? ls->separator() : separator, + true); + + if (ls && ls->is_arglist()) { + arglist->concat(ls); + } else if (ms) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); + } else if (ls) { + arglist->concat(ls); + } else { + arglist->append(splat); + } + if (arglist->length()) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); + } + } + + if (a->has_keyword_argument()) { + Expression_Obj rv = a->get_keyword_argument()->perform(this); + Argument_Ptr rvarg = Cast(rv); + Expression_Obj kwarg = rvarg->value()->perform(this); + + aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); + } + return aa.detach(); + } + + Expression_Ptr Eval::operator()(Comment_Ptr c) + { + return 0; + } + + inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + // All the binary helpers. + + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) + { + using std::strlen; + using std::strcpy; + Expression_Ptr e = NULL; + switch (sass_value_get_tag(v)) { + case SASS_BOOLEAN: { + e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); + } break; + case SASS_NUMBER: { + e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); + } break; + case SASS_COLOR: { + e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); + } break; + case SASS_STRING: { + if (sass_string_is_quoted(v)) + e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); + else { + e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); + } + } break; + case SASS_LIST: { + List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); + for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { + l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); + } + l->is_bracketed(sass_list_get_is_bracketed(v)); + e = l; + } break; + case SASS_MAP: { + Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); + for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { + *m << std::make_pair( + cval_to_astnode(sass_map_get_key(v, i), traces, pstate), + cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); + } + e = m; + } break; + case SASS_NULL: { + e = SASS_MEMORY_NEW(Null, pstate); + } break; + case SASS_ERROR: { + error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); + } break; + case SASS_WARNING: { + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); + } break; + default: break; + } + return e; + } + + Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) + { + std::vector rv; + Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); + sl->is_optional(s->is_optional()); + sl->media_block(s->media_block()); + sl->is_optional(s->is_optional()); + for (size_t i = 0, iL = s->length(); i < iL; ++i) { + rv.push_back(operator()((*s)[i])); + } + + // we should actually permutate parent first + // but here we have permutated the selector first + size_t round = 0; + while (round != std::string::npos) { + bool abort = true; + for (size_t i = 0, iL = rv.size(); i < iL; ++i) { + if (rv[i]->length() > round) { + sl->append((*rv[i])[round]); + abort = false; + } + } + if (abort) { + round = std::string::npos; + } else { + ++ round; + } + + } + return sl.detach(); + } + + + Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) + { + bool implicit_parent = !exp.old_at_root_without_rule; + if (is_in_selector_schema) exp.selector_stack.push_back(0); + Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); + if (is_in_selector_schema) exp.selector_stack.pop_back(); + for (size_t i = 0; i < resolved->length(); i++) { + Complex_Selector_Ptr is = resolved->at(i)->first(); + while (is) { + if (is->head()) { + is->head(operator()(is->head())); + } + is = is->tail(); + } + } + return resolved.detach(); + } + + Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0; i < s->length(); i++) { + Simple_Selector_Ptr ss = s->at(i); + // skip parents here (called via resolve_parent_refs) + if (ss == NULL || Cast(ss)) continue; + s->at(i) = Cast(ss->perform(this)); + } + return s; + } + + Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) + { + LOCAL_FLAG(is_in_selector_schema, true); + // the parser will look for a brace to end the selector + ctx.c_options.in_selector = true; // do not compress colors + Expression_Obj sel = s->contents()->perform(this); + std::string result_str(sel->to_string(ctx.c_options)); + ctx.c_options.in_selector = false; // flag temporary only + result_str = unquote(Util::rtrim(result_str)); + char* temp_cstr = sass_copy_c_string(result_str.c_str()); + ctx.strings.push_back(temp_cstr); // attach to context + Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); + p.last_media_block = s->media_block(); + // a selector schema may or may not connect to parent? + bool chroot = s->connect_parent() == false; + Selector_List_Obj sl = p.parse_selector_list(chroot); + auto vec_str_rend = ctx.strings.rend(); + auto vec_str_rbegin = ctx.strings.rbegin(); + // remove the first item searching from the back + // we cannot assume our item is still the last one + // order is not important, so we can optimize this + auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); + // undefined behavior if not found! + if (it != vec_str_rend) { + // overwrite with last item + *it = ctx.strings.back(); + // remove last one from vector + ctx.strings.pop_back(); + // free temporary copy + free(temp_cstr); + } + flag_is_in_selector_schema.reset(); + return operator()(sl); + } + + Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) + { + if (Selector_List_Obj pr = selector()) { + exp.selector_stack.pop_back(); + Selector_List_Obj rv = operator()(pr); + exp.selector_stack.push_back(rv); + return rv.detach(); + } else { + return SASS_MEMORY_NEW(Null, p->pstate()); + } + } + + Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) + { + return s; + } + + // hotfix to avoid invalid nested `:not` selectors + // probably the wrong place, but this should ultimately + // be fixed by implement superselector correctly for `:not` + // first use of "find" (ATM only implemented for selectors) + bool hasNotSelector(AST_Node_Obj obj) { + if (Wrapped_Selector_Ptr w = Cast(obj)) { + return w->name() == ":not"; + } + return false; + } + + Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) + { + + if (s->name() == ":not") { + if (exp.selector_stack.back()) { + if (s->selector()->find(hasNotSelector)) { + s->selector()->clear(); + s->name(" "); + } else if (s->selector()->length() == 1) { + Complex_Selector_Ptr cs = s->selector()->at(0); + if (cs->tail()) { + s->selector()->clear(); + s->name(" "); + } + } else if (s->selector()->length() > 1) { + s->selector()->clear(); + s->name(" "); + } + } + } + return s; + }; + +} diff --git a/src/eval.hpp b/src/eval.hpp new file mode 100644 index 000000000..aeaada87e --- /dev/null +++ b/src/eval.hpp @@ -0,0 +1,103 @@ +#ifndef SASS_EVAL_H +#define SASS_EVAL_H + +#include "ast.hpp" +#include "context.hpp" +#include "listize.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + class Expand; + class Context; + + class Eval : public Operation_CRTP { + + private: + Expression_Ptr fallback_impl(AST_Node_Ptr n); + + public: + Expand& exp; + Context& ctx; + Backtraces& traces; + Eval(Expand& exp); + ~Eval(); + + bool force; + bool is_in_comment; + bool is_in_selector_schema; + + Boolean_Obj bool_true; + Boolean_Obj bool_false; + + Env* environment(); + Selector_List_Obj selector(); + + // for evaluating function bodies + Expression_Ptr operator()(Block_Ptr); + Expression_Ptr operator()(Assignment_Ptr); + Expression_Ptr operator()(If_Ptr); + Expression_Ptr operator()(For_Ptr); + Expression_Ptr operator()(Each_Ptr); + Expression_Ptr operator()(While_Ptr); + Expression_Ptr operator()(Return_Ptr); + Expression_Ptr operator()(Warning_Ptr); + Expression_Ptr operator()(Error_Ptr); + Expression_Ptr operator()(Debug_Ptr); + + Expression_Ptr operator()(List_Ptr); + Expression_Ptr operator()(Map_Ptr); + Expression_Ptr operator()(Binary_Expression_Ptr); + Expression_Ptr operator()(Unary_Expression_Ptr); + Expression_Ptr operator()(Function_Call_Ptr); + Expression_Ptr operator()(Function_Call_Schema_Ptr); + Expression_Ptr operator()(Variable_Ptr); + Expression_Ptr operator()(Number_Ptr); + Expression_Ptr operator()(Color_Ptr); + Expression_Ptr operator()(Boolean_Ptr); + Expression_Ptr operator()(String_Schema_Ptr); + Expression_Ptr operator()(String_Quoted_Ptr); + Expression_Ptr operator()(String_Constant_Ptr); + // Expression_Ptr operator()(Selector_List_Ptr); + Media_Query_Ptr operator()(Media_Query_Ptr); + Expression_Ptr operator()(Media_Query_Expression_Ptr); + Expression_Ptr operator()(At_Root_Query_Ptr); + Expression_Ptr operator()(Supports_Operator_Ptr); + Expression_Ptr operator()(Supports_Negation_Ptr); + Expression_Ptr operator()(Supports_Declaration_Ptr); + Expression_Ptr operator()(Supports_Interpolation_Ptr); + Expression_Ptr operator()(Null_Ptr); + Expression_Ptr operator()(Argument_Ptr); + Expression_Ptr operator()(Arguments_Ptr); + Expression_Ptr operator()(Comment_Ptr); + + // these will return selectors + Selector_List_Ptr operator()(Selector_List_Ptr); + Selector_List_Ptr operator()(Complex_Selector_Ptr); + Compound_Selector_Ptr operator()(Compound_Selector_Ptr); + Simple_Selector_Ptr operator()(Simple_Selector_Ptr s); + Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s); + // they don't have any specific implementation (yet) + // Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; }; + // Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; }; + // Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; }; + // Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; }; + // Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; }; + // actual evaluated selectors + Selector_List_Ptr operator()(Selector_Schema_Ptr); + Expression_Ptr operator()(Parent_Selector_Ptr); + + template + Expression_Ptr fallback(U x) { return fallback_impl(x); } + + private: + void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); + + }; + + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate = ParserState("[AST]")); + +} + +#endif diff --git a/src/expand.cpp b/src/expand.cpp new file mode 100644 index 000000000..d8dc03f14 --- /dev/null +++ b/src/expand.cpp @@ -0,0 +1,817 @@ +#include "sass.hpp" +#include +#include + +#include "ast.hpp" +#include "expand.hpp" +#include "bind.hpp" +#include "eval.hpp" +#include "backtrace.hpp" +#include "context.hpp" +#include "parser.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // simple endless recursion protection + const size_t maxRecursion = 500; + + Expand::Expand(Context& ctx, Env* env, std::vector* stack) + : ctx(ctx), + traces(ctx.traces), + eval(Eval(*this)), + recursions(0), + in_keyframes(false), + at_root_without_rule(false), + old_at_root_without_rule(false), + env_stack(std::vector()), + block_stack(std::vector()), + call_stack(std::vector()), + selector_stack(std::vector()), + media_block_stack(std::vector()) + { + env_stack.push_back(0); + env_stack.push_back(env); + block_stack.push_back(0); + call_stack.push_back(0); + if (stack == NULL) { selector_stack.push_back(0); } + else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } + media_block_stack.push_back(0); + } + + Env* Expand::environment() + { + if (env_stack.size() > 0) + return env_stack.back(); + return 0; + } + + Selector_List_Obj Expand::selector() + { + if (selector_stack.size() > 0) + return selector_stack.back(); + return 0; + } + + // blocks create new variable scopes + Block_Ptr Expand::operator()(Block_Ptr b) + { + // create new local environment + // set the current env as parent + Env env(environment()); + // copy the block object (add items later) + Block_Obj bb = SASS_MEMORY_NEW(Block, + b->pstate(), + b->length(), + b->is_root()); + // setup block and env stack + this->block_stack.push_back(bb); + this->env_stack.push_back(&env); + // operate on block + // this may throw up! + this->append_block(b); + // revert block and env stack + this->block_stack.pop_back(); + this->env_stack.pop_back(); + // return copy + return bb.detach(); + } + + Statement_Ptr Expand::operator()(Ruleset_Ptr r) + { + LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); + + if (in_keyframes) { + Block_Ptr bb = operator()(r->block()); + Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); + if (r->selector()) { + if (Selector_List_Ptr s = r->selector()) { + selector_stack.push_back(0); + k->name(s->eval(eval)); + selector_stack.pop_back(); + } + } + return k.detach(); + } + + // reset when leaving scope + LOCAL_FLAG(at_root_without_rule, false); + + // `&` is allowed in `@at-root`! + bool has_parent_selector = false; + for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { + Selector_List_Obj ll = selector_stack.at(i); + has_parent_selector = ll != 0 && ll->length() > 0; + } + + Selector_List_Obj sel = r->selector(); + if (sel) sel = sel->eval(eval); + + // check for parent selectors in base level rules + if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { + if (Selector_List_Ptr selector_list = Cast(r->selector())) { + for (Complex_Selector_Obj complex_selector : selector_list->elements()) { + Complex_Selector_Ptr tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + Parent_Selector_Ptr ptr = Cast(header); + if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); + } + tail = tail->tail(); + } + } + } + } + else { + if (sel->length() == 0 || sel->has_parent_ref()) { + if (sel->has_real_parent_ref() && !has_parent_selector) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); + } + } + } + + // do not connect parent again + sel->remove_parent_selectors(); + selector_stack.push_back(sel); + Env env(environment()); + if (block_stack.back()->is_root()) { + env_stack.push_back(&env); + } + sel->set_media_block(media_block_stack.back()); + Block_Obj blk = 0; + if (r->block()) blk = operator()(r->block()); + Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + sel, + blk); + selector_stack.pop_back(); + if (block_stack.back()->is_root()) { + env_stack.pop_back(); + } + + rr->is_root(r->is_root()); + rr->tabs(r->tabs()); + + return rr; + } + + Statement_Ptr Expand::operator()(Supports_Block_Ptr f) + { + Expression_Obj condition = f->condition()->perform(&eval); + Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, + f->pstate(), + Cast(condition), + operator()(f->block())); + return ff.detach(); + } + + Statement_Ptr Expand::operator()(Media_Block_Ptr m) + { + Media_Block_Obj cpy = SASS_MEMORY_COPY(m); + // Media_Blocks are prone to have circular references + // Copy could leak memory if it does not get picked up + // Looks like we are able to reset block reference for copy + // Good as it will ensure a low memory overhead for this fix + // So this is a cheap solution with a minimal price + ctx.ast_gc.push_back(cpy); cpy->block(0); + Expression_Obj mq = eval(m->media_queries()); + std::string str_mq(mq->to_string(ctx.c_options)); + char* str = sass_copy_c_string(str_mq.c_str()); + ctx.strings.push_back(str); + Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); + mq = p.parse_media_queries(); // re-assign now + cpy->media_queries(mq); + media_block_stack.push_back(cpy); + Block_Obj blk = operator()(m->block()); + Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + mq, + blk); + media_block_stack.pop_back(); + mm->tabs(m->tabs()); + return mm; + } + + Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) + { + Block_Obj ab = a->block(); + Expression_Obj ae = a->expression(); + + if (ae) ae = ae->perform(&eval); + else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); + + LOCAL_FLAG(at_root_without_rule, true); + LOCAL_FLAG(in_keyframes, false); + + ; + + Block_Obj bb = ab ? operator()(ab) : NULL; + At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, + a->pstate(), + bb, + Cast(ae)); + return aa.detach(); + } + + Statement_Ptr Expand::operator()(Directive_Ptr a) + { + LOCAL_FLAG(in_keyframes, a->is_keyframes()); + Block_Ptr ab = a->block(); + Selector_List_Ptr as = a->selector(); + Expression_Ptr av = a->value(); + selector_stack.push_back(0); + if (av) av = av->perform(&eval); + if (as) as = eval(as); + selector_stack.pop_back(); + Block_Ptr bb = ab ? operator()(ab) : NULL; + Directive_Ptr aa = SASS_MEMORY_NEW(Directive, + a->pstate(), + a->keyword(), + as, + bb, + av); + return aa; + } + + Statement_Ptr Expand::operator()(Declaration_Ptr d) + { + Block_Obj ab = d->block(); + String_Obj old_p = d->property(); + Expression_Obj prop = old_p->perform(&eval); + String_Obj new_p = Cast(prop); + // we might get a color back + if (!new_p) { + std::string str(prop->to_string(ctx.c_options)); + new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); + } + Expression_Obj value = d->value(); + if (value) value = value->perform(&eval); + Block_Obj bb = ab ? operator()(ab) : NULL; + if (!bb) { + if (!value || (value->is_invisible() && !d->is_important())) return 0; + } + Declaration_Ptr decl = SASS_MEMORY_NEW(Declaration, + d->pstate(), + new_p, + value, + d->is_important(), + d->is_custom_property(), + bb); + decl->tabs(d->tabs()); + return decl; + } + + Statement_Ptr Expand::operator()(Assignment_Ptr a) + { + Env* env = environment(); + const std::string& var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Obj e = Cast(env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Obj e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(&eval)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Obj e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(&eval)); + } + else { + env->set_local(var, a->value()->perform(&eval)); + } + } + else { + env->set_lexical(var, a->value()->perform(&eval)); + } + return 0; + } + + Statement_Ptr Expand::operator()(Import_Ptr imp) + { + Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); + if (imp->import_queries() && imp->import_queries()->size()) { + Expression_Obj ex = imp->import_queries()->perform(&eval); + result->import_queries(Cast(ex)); + } + for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { + result->urls().push_back(imp->urls()[i]->perform(&eval)); + } + // all resources have been dropped for Input_Stubs + // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} + return result.detach(); + } + + Statement_Ptr Expand::operator()(Import_Stub_Ptr i) + { + traces.push_back(Backtrace(i->pstate())); + // get parent node from call stack + AST_Node_Obj parent = call_stack.back(); + if (Cast(parent) == NULL) { + error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); + } + // we don't seem to need that actually afterall + Sass_Import_Entry import = sass_make_import( + i->imp_path().c_str(), + i->abs_path().c_str(), + 0, 0 + ); + ctx.import_stack.push_back(import); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); + block_stack.back()->append(trace); + block_stack.push_back(trace_block); + + const std::string& abs_path(i->resource().abs_path); + append_block(ctx.sheets.at(abs_path).root); + sass_delete_import(ctx.import_stack.back()); + ctx.import_stack.pop_back(); + block_stack.pop_back(); + traces.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Warning_Ptr w) + { + // eval handles this too, because warnings may occur in functions + w->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Error_Ptr e) + { + // eval handles this too, because errors may occur in functions + e->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Debug_Ptr d) + { + // eval handles this too, because warnings may occur in functions + d->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Comment_Ptr c) + { + if (ctx.output_style() == COMPRESSED) { + // comments should not be evaluated in compact + // https://github.com/sass/libsass/issues/2359 + if (!c->is_important()) return NULL; + } + eval.is_in_comment = true; + Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); + eval.is_in_comment = false; + // TODO: eval the text, once we're parsing/storing it as a String_Schema + return rv; + } + + Statement_Ptr Expand::operator()(If_Ptr i) + { + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(i); + Expression_Obj rv = i->predicate()->perform(&eval); + if (*rv) { + append_block(i->block()); + } + else { + Block_Ptr alt = i->alternative(); + if (alt) append_block(alt); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(&eval); + if (low->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(&eval); + if (high->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); + } + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_start->unit() << "' and '" + << sass_end->unit() << "'."; + error(msg.str(), low->pstate(), traces); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(f); + Block_Ptr body = f->block(); + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + append_block(body); + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(&eval); + List_Obj list = 0; + Map_Obj map; + if (expr->concrete_type() == Expression::MAP) { + map = Cast(expr); + } + else if (Selector_List_Ptr ls = Cast(expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = Cast(rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = Cast(expr); + } + // remember variables and then reset them + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(e); + Block_Ptr body = e->block(); + + if (map) { + for (auto key : map->keys()) { + Expression_Obj k = key->perform(&eval); + Expression_Obj v = map->at(key)->perform(&eval); + + if (variables.size() == 1) { + List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(k); + variable->append(v); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], k); + env.set_local(variables[1], v); + } + append_block(body); + } + } + else { + // bool arglist = list->is_arglist(); + if (list->length() == 1 && Cast(list)) { + list = Cast(list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Obj item = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Obj arg = Cast(item)) item = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Obj scalars = Cast(item)) { + if (variables.size() == 1) { + List_Obj var = scalars; + // if (arglist) var = (*scalars)[0]; + env.set_local(variables[0], var); + } else { + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Obj res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : (*scalars)[j]->perform(&eval); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Ptr body = w->block(); + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(w); + Expression_Obj cond = pred->perform(&eval); + while (!cond->is_false()) { + append_block(body); + cond = pred->perform(&eval); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Return_Ptr r) + { + error("@return may only be used within a function", r->pstate(), traces); + return 0; + } + + + void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { + + if (Selector_List_Obj sl = Cast(s)) { + for (Complex_Selector_Obj complex_selector : sl->elements()) { + Complex_Selector_Obj tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + if (Cast(header) == NULL) continue; // skip all others + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); + } + tail = tail->tail(); + } + } + } + + + Selector_List_Obj contextualized = Cast(s->perform(&eval)); + if (contextualized == false) return; + for (auto complex_sel : contextualized->elements()) { + Complex_Selector_Obj c = complex_sel; + if (!c->head() || c->tail()) { + std::string sel_str(contextualized->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); + } + Compound_Selector_Obj target = c->head(); + if (contextualized->is_optional()) target->is_optional(true); + for (size_t i = 0, L = extender->length(); i < L; ++i) { + Complex_Selector_Obj sel = (*extender)[i]; + if (!(sel->head() && sel->head()->length() > 0 && + Cast((*sel->head())[0]))) + { + Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); + hh->media_block((*extender)[i]->media_block()); + Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); + ssel->media_block((*extender)[i]->media_block()); + if (sel->has_line_feed()) ssel->has_line_feed(true); + Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); + ps->media_block((*extender)[i]->media_block()); + hh->append(ps); + ssel->tail(sel); + ssel->head(hh); + sel = ssel; + } + // if (c->has_line_feed()) sel->has_line_feed(true); + ctx.subset_map.put(target, std::make_pair(sel, target)); + } + } + + } + + Statement* Expand::operator()(Extension_Ptr e) + { + if (Selector_List_Ptr extender = selector()) { + Selector_List_Ptr sl = e->selector(); + // abort on invalid selector + if (sl == NULL) return NULL; + if (Selector_Schema_Ptr schema = sl->schema()) { + if (schema->has_real_parent_ref()) { + // put root block on stack again (ignore parents) + // selector schema must not connect in eval! + block_stack.push_back(block_stack.at(1)); + sl = eval(sl->schema()); + block_stack.pop_back(); + } else { + selector_stack.push_back(0); + sl = eval(sl->schema()); + selector_stack.pop_back(); + } + } + for (Complex_Selector_Obj cs : sl->elements()) { + if (!cs.isNull() && !cs->head().isNull()) { + cs->head()->media_block(media_block_stack.back()); + } + } + selector_stack.push_back(0); + expand_selector_list(sl, extender); + selector_stack.pop_back(); + } + return 0; + } + + Statement_Ptr Expand::operator()(Definition_Ptr d) + { + Env* env = environment(); + Definition_Obj dd = SASS_MEMORY_COPY(d); + env->local_frame()[d->name() + + (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; + + if (d->type() == Definition::FUNCTION && ( + Prelexer::calc_fn_call(d->name().c_str()) || + d->name() == "element" || + d->name() == "expression" || + d->name() == "url" + )) { + deprecated( + "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", + "This name conflicts with an existing CSS function with special parse rules.", + false, d->pstate() + ); + } + + // set the static link so we can have lexical scoping + dd->environment(env); + return 0; + } + + Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) + { + if (recursions > maxRecursion) { + throw Exception::StackError(traces, *c); + } + + recursions ++; + + Env* env = environment(); + std::string full_name(c->name() + "[m]"); + if (!env->has(full_name)) { + error("no mixin named " + c->name(), c->pstate(), traces); + } + Definition_Obj def = Cast((*env)[full_name]); + Block_Obj body = def->block(); + Parameters_Obj params = def->parameters(); + + if (c->block() && c->name() != "@content" && !body->has_content()) { + error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); + } + Expression_Obj rv = c->arguments()->perform(&eval); + Arguments_Obj args = Cast(rv); + std::string msg(", in mixin `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_MIXIN, + { env } + }); + + Env new_env(def->environment()); + env_stack.push_back(&new_env); + if (c->block()) { + // represent mixin content blocks as thunks/closures + Definition_Obj thunk = SASS_MEMORY_NEW(Definition, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Parameters, c->pstate()), + c->block(), + Definition::MIXIN); + thunk->environment(env); + new_env.local_frame()["@content[m]"] = thunk; + } + + bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); + + env->set_global("is_in_mixin", bool_true); + if (Block_Ptr pr = block_stack.back()) { + trace_block->is_root(pr->is_root()); + } + block_stack.push_back(trace_block); + for (auto bb : body->elements()) { + if (Ruleset_Ptr r = Cast(bb)) { + r->is_root(trace_block->is_root()); + } + Statement_Obj ith = bb->perform(this); + if (ith) trace->block()->append(ith); + } + block_stack.pop_back(); + env->del_global("is_in_mixin"); + + ctx.callee_stack.pop_back(); + env_stack.pop_back(); + traces.pop_back(); + + recursions --; + return trace.detach(); + } + + Statement_Ptr Expand::operator()(Content_Ptr c) + { + Env* env = environment(); + // convert @content directives into mixin calls to the underlying thunk + if (!env->has("@content[m]")) return 0; + + if (block_stack.back()->is_root()) { + selector_stack.push_back(0); + } + + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Arguments, c->pstate())); + + Trace_Obj trace = Cast(call->perform(this)); + + if (block_stack.back()->is_root()) { + selector_stack.pop_back(); + } + + return trace.detach(); + } + + // produce an error if something is not implemented + inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) + { + std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); + String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); + error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); + return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); + } + + // process and add to last block on stack + inline void Expand::append_block(Block_Ptr b) + { + if (b->is_root()) call_stack.push_back(b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr stm = b->at(i); + Statement_Obj ith = stm->perform(this); + if (ith) block_stack.back()->append(ith); + } + if (b->is_root()) call_stack.pop_back(); + } + +} diff --git a/src/expand.hpp b/src/expand.hpp new file mode 100644 index 000000000..3464c98f6 --- /dev/null +++ b/src/expand.hpp @@ -0,0 +1,82 @@ +#ifndef SASS_EXPAND_H +#define SASS_EXPAND_H + +#include + +#include "ast.hpp" +#include "eval.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + class Listize; + class Context; + class Eval; + struct Backtrace; + + class Expand : public Operation_CRTP { + public: + + Env* environment(); + Selector_List_Obj selector(); + + Context& ctx; + Backtraces& traces; + Eval eval; + size_t recursions; + bool in_keyframes; + bool at_root_without_rule; + bool old_at_root_without_rule; + + // it's easier to work with vectors + std::vector env_stack; + std::vector block_stack; + std::vector call_stack; + std::vector selector_stack; + std::vector media_block_stack; + + Boolean_Obj bool_true; + + Statement_Ptr fallback_impl(AST_Node_Ptr n); + + private: + void expand_selector_list(Selector_Obj, Selector_List_Obj extender); + + public: + Expand(Context&, Env*, std::vector* stack = NULL); + ~Expand() { } + + Block_Ptr operator()(Block_Ptr); + Statement_Ptr operator()(Ruleset_Ptr); + Statement_Ptr operator()(Media_Block_Ptr); + Statement_Ptr operator()(Supports_Block_Ptr); + Statement_Ptr operator()(At_Root_Block_Ptr); + Statement_Ptr operator()(Directive_Ptr); + Statement_Ptr operator()(Declaration_Ptr); + Statement_Ptr operator()(Assignment_Ptr); + Statement_Ptr operator()(Import_Ptr); + Statement_Ptr operator()(Import_Stub_Ptr); + Statement_Ptr operator()(Warning_Ptr); + Statement_Ptr operator()(Error_Ptr); + Statement_Ptr operator()(Debug_Ptr); + Statement_Ptr operator()(Comment_Ptr); + Statement_Ptr operator()(If_Ptr); + Statement_Ptr operator()(For_Ptr); + Statement_Ptr operator()(Each_Ptr); + Statement_Ptr operator()(While_Ptr); + Statement_Ptr operator()(Return_Ptr); + Statement_Ptr operator()(Extension_Ptr); + Statement_Ptr operator()(Definition_Ptr); + Statement_Ptr operator()(Mixin_Call_Ptr); + Statement_Ptr operator()(Content_Ptr); + + template + Statement_Ptr fallback(U x) { return fallback_impl(x); } + + void append_block(Block_Ptr); + }; + +} + +#endif diff --git a/src/extend.cpp b/src/extend.cpp new file mode 100644 index 000000000..602269880 --- /dev/null +++ b/src/extend.cpp @@ -0,0 +1,2130 @@ +#include "sass.hpp" +#include "extend.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "paths.hpp" +#include "parser.hpp" +#include "expand.hpp" +#include "node.hpp" +#include "sass_util.hpp" +#include "remove_placeholders.hpp" +#include "debug.hpp" +#include +#include +#include + +/* + NOTES: + + - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which + is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what + ruby already outputs (to make comparisons easier). + + - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing. + Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass. + The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually + used for the porting. + + - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby + output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to + the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see + something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment. + + - Coding conventions in this file (these may need to be changed before merging back into master) + - Very basic hungarian notation: + p prefix for pointers (pSelector) + no prefix for value types and references (selector) + - Use STL iterators where possible + - prefer verbose naming over terse naming + - use typedefs for STL container types for make maintenance easier + + - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators + in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby + sass causes this to be necessary. + + + GLOBAL TODOS: + + - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode. + + - consider making the extend* functions member functions to avoid passing around ctx and subset_map map around. This has the + drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and + can cause additional compile time dependencies. + + - mark the helper methods in this file static to given them compilation unit linkage. + + - implement parent directive matching + + - fix compilation warnings for unused Extend members if we really don't need those references anymore. + */ + + +namespace Sass { + + + +#ifdef DEBUG + + // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp + std::ostream& operator<<(std::ostream& os, const Complex_Selector::Combinator combinator) { + switch (combinator) { + case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; + case Complex_Selector::PARENT_OF: os << "\">\""; break; + case Complex_Selector::PRECEDES: os << "\"~\""; break; + case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; + case Complex_Selector::REFERENCE: os << "\"/\""; break; + } + + return os; + } + + + std::ostream& operator<<(std::ostream& os, Compound_Selector& compoundSelector) { + for (size_t i = 0, L = compoundSelector.length(); i < L; ++i) { + if (i > 0) os << ", "; + os << compoundSelector[i]->to_string(); + } + return os; + } + + std::ostream& operator<<(std::ostream& os, Simple_Selector& simpleSelector) { + os << simpleSelector.to_string(); + return os; + } + + // Print a string representation of a Compound_Selector + static void printSimpleSelector(Simple_Selector* pSimpleSelector, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + if (pSimpleSelector) { + std::cerr << "[" << *pSimpleSelector << "]"; + } else { + std::cerr << "NULL"; + } + + if (newline) { + std::cerr << std::endl; + } + } + + // Print a string representation of a Compound_Selector + static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + if (pCompoundSelector) { + std::cerr << "[" << *pCompoundSelector << "]"; + } else { + std::cerr << "NULL"; + } + + if (newline) { + std::cerr << std::endl; + } + } + + + std::ostream& operator<<(std::ostream& os, Complex_Selector& complexSelector) { + + os << "["; + Complex_Selector_Ptr pIter = &complexSelector; + bool first = true; + while (pIter) { + if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) { + if (!first) { + os << ", "; + } + first = false; + os << pIter->combinator(); + } + + if (!first) { + os << ", "; + } + first = false; + + if (pIter->head()) { + os << pIter->head()->to_string(); + } else { + os << "NULL_HEAD"; + } + + pIter = pIter->tail(); + } + os << "]"; + + return os; + } + + + // Print a string representation of a Complex_Selector + static void printComplexSelector(Complex_Selector_Ptr pComplexSelector, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + if (pComplexSelector) { + std::cerr << *pComplexSelector; + } else { + std::cerr << "NULL"; + } + + if (newline) { + std::cerr << std::endl; + } + } + + static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + bool first = true; + std::cerr << "["; + for(SubSetMapLookup& pair : collection) { + if (first) { + first = false; + } else { + std::cerr << ", "; + } + std::cerr << "["; + Compound_Selector_Ptr pSels = pair.first; + Complex_Selector_Ptr pNewSelector = pair.second; + std::cerr << "[" << *pSels << "], "; + printComplexSelector(pNewSelector, NULL, false); + } + std::cerr << "]"; + + if (newline) { + std::cerr << std::endl; + } + } + + // Print a string representation of a ComplexSelectorSet + static void printSourcesSet(ComplexSelectorSet& sources, const char* message=NULL, bool newline=true) { + + if (message) { + std::cerr << message; + } + + // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on + // the differences we see when debug printing. + typedef std::deque SourceStrings; + SourceStrings sourceStrings; + for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { + Complex_Selector_Ptr pSource = *iterator; + std::stringstream sstream; + sstream << complexSelectorToNode(pSource); + sourceStrings.push_back(sstream.str()); + } + + // Sort to get consistent output + std::sort(sourceStrings.begin(), sourceStrings.end()); + + std::cerr << "ComplexSelectorSet["; + for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) { + std::string source = *iterator; + if (iterator != sourceStrings.begin()) { + std::cerr << ", "; + } + std::cerr << source; + } + std::cerr << "]"; + + if (newline) { + std::cerr << std::endl; + } + } + + + std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) { + os << "SUBSET_MAP_ENTRIES["; + + for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { + Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge) + Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend + + if (iterator != entries.begin()) { + os << ", "; + } + + os << "("; + + if (pExtComplexSelector) { + std::cerr << *pExtComplexSelector; + } else { + std::cerr << "NULL"; + } + + os << " -> "; + + if (pExtCompoundSelector) { + std::cerr << *pExtCompoundSelector; + } else { + std::cerr << "NULL"; + } + + os << ")"; + + } + + os << "]"; + + return os; + } +#endif + + static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo) { + // TODO: figure out a better way to create a Complex_Selector from scratch + // TODO: There's got to be a better way. This got ugly quick... + Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); + Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); + fakeHead->elements().push_back(fakeParent); + Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); + + pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); + pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); + + bool isSuperselector = pOne->is_superselector_of(pTwo); + + pOne->clear_innermost(); + pTwo->clear_innermost(); + + return isSuperselector; + } + + void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out) { + for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { + Node& child = *iter; + out.push_back(nodeToComplexSelector(child)); + } + } + + Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque) { + Node result = Node::createCollection(); + + for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { + Complex_Selector_Obj pChild = *iter; + result.collection()->push_back(complexSelectorToNode(pChild)); + } + + return result; + } + + class LcsCollectionComparator { + public: + LcsCollectionComparator() {} + + bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const { + /* + This code is based on the following block from ruby sass' subweave + do |s1, s2| + next s1 if s1 == s2 + next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) + next s2 if parent_superselector?(s1, s2) + next s1 if parent_superselector?(s2, s1) + end + */ + + if (*pOne == *pTwo) { + pOut = pOne; + return true; + } + + if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) { + return false; + } + + if (parentSuperselector(pOne, pTwo)) { + pOut = pTwo; + return true; + } + + if (parentSuperselector(pTwo, pOne)) { + pOut = pOne; + return true; + } + + return false; + } + }; + + + /* + This is the equivalent of ruby's Sass::Util.lcs_backtrace. + + # Computes a single longest common subsequence for arrays x and y. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS + */ + void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { + //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) + // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output + + if (i == 0 || j == 0) { + DEBUG_PRINTLN(LCS, "RETURNING EMPTY") + return; + } + + + Complex_Selector_Obj pCompareOut; + if (comparator(x[i], y[j], pCompareOut)) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") + lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out); + out.push_back(pCompareOut); + return; + } + + if (c[i][j - 1] > c[i - 1][j]) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") + lcs_backtrace(c, x, y, i, j - 1, comparator, out); + return; + } + + DEBUG_PRINTLN(LCS, "FINAL RETURN") + lcs_backtrace(c, x, y, i - 1, j, comparator, out); + return; + } + + /* + This is the equivalent of ruby's Sass::Util.lcs_table. + + # Calculates the memoization table for the Least Common Subsequence algorithm. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS + */ + void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) { + //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) + // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output + + LCSTable c(x.size(), std::vector(y.size())); + + // These shouldn't be necessary since the vector will be initialized to 0 already. + // x.size.times {|i| c[i][0] = 0} + // y.size.times {|j| c[0][j] = 0} + + for (size_t i = 1; i < x.size(); i++) { + for (size_t j = 1; j < y.size(); j++) { + Complex_Selector_Obj pCompareOut; + + if (comparator(x[i], y[j], pCompareOut)) { + c[i][j] = c[i - 1][j - 1] + 1; + } else { + c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); + } + } + } + + out = c; + } + + /* + This is the equivalent of ruby's Sass::Util.lcs. + + # Computes a single longest common subsequence for `x` and `y`. + # If there are more than one longest common subsequences, + # the one returned is that which starts first in `x`. + + # @param x [NodeCollection] + # @param y [NodeCollection] + # @comparator An equality check between elements of `x` and `y`. + # @return [NodeCollection] The LCS + + http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + */ + void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { + //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) + // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output + + x.push_front(NULL); + y.push_front(NULL); + + LCSTable table; + lcs_table(x, y, comparator, table); + + return lcs_backtrace(table, x, y, static_cast(x.size()) - 1, static_cast(y.size()) - 1, comparator, out); + } + + + /* + This is the equivalent of ruby's Sequence.trim. + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + # Avoid truly horrific quadratic behavior. TODO: I think there + # may be a way to get perfect trimming without going quadratic. + return seqses if seqses.size > 100 + + # Keep the results in a separate array so we can be sure we aren't + # comparing against an already-trimmed selector. This ensures that two + # identical selectors don't mutually trim one another. + result = seqses.dup + + # This is n^2 on the sequences, but only comparing between + # separate sequences should limit the quadratic behavior. + seqses.each_with_index do |seqs1, i| + tempResult = [] + + for seq1 in seqs1 do + max_spec = 0 + for seq in _sources(seq1) do + max_spec = [max_spec, seq.specificity].max + end + + + isMoreSpecificOuter = false + for seqs2 in result do + if seqs1.equal?(seqs2) then + next + end + + # Second Law of Extend: the specificity of a generated selector + # should never be less than the specificity of the extending + # selector. + # + # See https://github.com/nex3/sass/issues/324. + isMoreSpecificInner = false + for seq2 in seqs2 do + isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1) + if isMoreSpecificInner then + break + end + end + + if isMoreSpecificInner then + isMoreSpecificOuter = true + break + end + end + + if !isMoreSpecificOuter then + tempResult.push(seq1) + end + end + + result[i] = tempResult + + end + + result + */ + /* + - IMPROVEMENT: We could probably work directly in the output trimmed deque. + */ + Node Extend::trim(Node& seqses, bool isReplace) { + // See the comments in the above ruby code before embarking on understanding this function. + + // Avoid poor performance in extreme cases. + if (seqses.collection()->size() > 100) { + return seqses; + } + + + DEBUG_PRINTLN(TRIM, "TRIM: " << seqses) + + + Node result = Node::createCollection(); + result.plus(seqses); + + DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result) + + // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're + // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track + // of the index manually. + int toTrimIndex = 0; + + for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) { + Node& seqs1 = *seqsesIter; + + DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex) + + Node tempResult = Node::createCollection(); + tempResult.got_line_feed = seqs1.got_line_feed; + + for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) { + Node& seq1 = *seqs1Iter; + + Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1); + + // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code + // for a good description of sources. + // + // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test. + // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We + // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My + // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely + // a guess though. + unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0; + ComplexSelectorSet sources = pSeq1->sources(); + + DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) + DEBUG_EXEC(TRIM, printSourcesSet(sources, "TRIM SOURCES: ")) + + for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { + const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; + maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity()); + } + + DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity) + + bool isMoreSpecificOuter = false; + + int resultIndex = 0; + + for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) { + Node& seqs2 = *resultIter; + + DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1) + DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2) + + // Do not compare the same sequence to itself. The ruby call we're trying to + // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision. + // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is + // derived from seqses and seqs2 is derived from result. + if (seqs1.collection() == seqs2.collection()) { + DEBUG_PRINTLN(TRIM, "CONTINUE") + continue; + } + + bool isMoreSpecificInner = false; + + for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) { + Node& seq2 = *seqs2Iter; + + Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2); + + DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity()) + DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false")) + DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false")) + + isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1); + + if (isMoreSpecificInner) { + DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC") + break; + } + } + + // If we found something more specific, we're done. Let the outer loop know and stop iterating. + if (isMoreSpecificInner) { + isMoreSpecificOuter = true; + break; + } + + resultIndex++; + } + + if (!isMoreSpecificOuter) { + DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1) + tempResult.collection()->push_back(seq1); + } + + } + + DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result) + DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult) + (*result.collection())[toTrimIndex] = tempResult; + + toTrimIndex++; + + DEBUG_PRINTLN(TRIM, "RESULT: " << result) + } + + return result; + } + + + + static bool parentSuperselector(const Node& one, const Node& two) { + // TODO: figure out a better way to create a Complex_Selector from scratch + // TODO: There's got to be a better way. This got ugly quick... + Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); + Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); + fakeHead->elements().push_back(fakeParent); + Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); + + Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one); + pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); + Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two); + pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); + + return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); + } + + + class ParentSuperselectorChunker { + public: + ParentSuperselectorChunker(Node& lcs) : mLcs(lcs) {} + Node& mLcs; + + bool operator()(const Node& seq) const { + // {|s| parent_superselector?(s.first, lcs.first)} + if (seq.collection()->size() == 0) return false; + return parentSuperselector(seq.collection()->front(), mLcs.collection()->front()); + } + }; + + class SubweaveEmptyChunker { + public: + bool operator()(const Node& seq) const { + // {|s| s.empty?} + + return seq.collection()->empty(); + } + }; + + /* + # Takes initial subsequences of `seq1` and `seq2` and returns all + # orderings of those subsequences. The initial subsequences are determined + # by a block. + # + # Destructively removes the initial subsequences of `seq1` and `seq2`. + # + # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` + # denoting the boundary of the initial subsequence), this would return + # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and + # `(3 4 5)`. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @yield [a] Used to determine when to cut off the initial subsequences. + # Called repeatedly for each sequence until it returns true. + # @yieldparam a [Array] A final subsequence of one input sequence after + # cutting off some initial subsequence. + # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence + # here. + # @return [Array] All possible orderings of the initial subsequences. + def chunks(seq1, seq2) + chunk1 = [] + chunk1 << seq1.shift until yield seq1 + chunk2 = [] + chunk2 << seq2.shift until yield seq2 + return [] if chunk1.empty? && chunk2.empty? + return [chunk2] if chunk1.empty? + return [chunk1] if chunk2.empty? + [chunk1 + chunk2, chunk2 + chunk1] + end + */ + template + static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) { + Node chunk1 = Node::createCollection(); + while (seq1.collection()->size() && !chunker(seq1)) { + chunk1.collection()->push_back(seq1.collection()->front()); + seq1.collection()->pop_front(); + } + + Node chunk2 = Node::createCollection(); + while (!seq2.collection()->empty() && !chunker(seq2)) { + chunk2.collection()->push_back(seq2.collection()->front()); + seq2.collection()->pop_front(); + } + + if (chunk1.collection()->empty() && chunk2.collection()->empty()) { + DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY") + return Node::createCollection(); + } + + if (chunk1.collection()->empty()) { + Node chunk2Wrapper = Node::createCollection(); + chunk2Wrapper.collection()->push_back(chunk2); + DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY") + return chunk2Wrapper; + } + + if (chunk2.collection()->empty()) { + Node chunk1Wrapper = Node::createCollection(); + chunk1Wrapper.collection()->push_back(chunk1); + DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY") + return chunk1Wrapper; + } + + Node perms = Node::createCollection(); + + Node firstPermutation = Node::createCollection(); + firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); + firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); + perms.collection()->push_back(firstPermutation); + + Node secondPermutation = Node::createCollection(); + secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); + secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); + perms.collection()->push_back(secondPermutation); + + DEBUG_PRINTLN(CHUNKS, "RETURNING PERM") + + return perms; + } + + + static Node groupSelectors(Node& seq) { + Node newSeq = Node::createCollection(); + + Node tail = Node::createCollection(); + tail.plus(seq); + + while (!tail.collection()->empty()) { + Node head = Node::createCollection(); + + do { + head.collection()->push_back(tail.collection()->front()); + tail.collection()->pop_front(); + } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator())); + + newSeq.collection()->push_back(head); + } + + return newSeq; + } + + + static void getAndRemoveInitialOps(Node& seq, Node& ops) { + NodeDeque& seqCollection = *(seq.collection()); + NodeDeque& opsCollection = *(ops.collection()); + + while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) { + opsCollection.push_back(seqCollection.front()); + seqCollection.pop_front(); + } + } + + + static void getAndRemoveFinalOps(Node& seq, Node& ops) { + NodeDeque& seqCollection = *(seq.collection()); + NodeDeque& opsCollection = *(ops.collection()); + + while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) { + opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code + seqCollection.pop_back(); + } + } + + + /* + def merge_initial_ops(seq1, seq2) + ops1, ops2 = [], [] + ops1 << seq1.shift while seq1.first.is_a?(String) + ops2 << seq2.shift while seq2.first.is_a?(String) + + newline = false + newline ||= !!ops1.shift if ops1.first == "\n" + newline ||= !!ops2.shift if ops2.first == "\n" + + # If neither sequence is a subsequence of the other, they cannot be + # merged successfully + lcs = Sass::Util.lcs(ops1, ops2) + return unless lcs == ops1 || lcs == ops2 + return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) + end + */ + static Node mergeInitialOps(Node& seq1, Node& seq2) { + Node ops1 = Node::createCollection(); + Node ops2 = Node::createCollection(); + + getAndRemoveInitialOps(seq1, ops1); + getAndRemoveInitialOps(seq2, ops2); + + // TODO: Do we have this information available to us? + // newline = false + // newline ||= !!ops1.shift if ops1.first == "\n" + // newline ||= !!ops2.shift if ops2.first == "\n" + + // If neither sequence is a subsequence of the other, they cannot be merged successfully + DefaultLcsComparator lcsDefaultComparator; + Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); + + if (!(opsLcs == ops1 || opsLcs == ops2)) { + return Node::createNil(); + } + + // TODO: more newline logic + // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) + + return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2); + } + + + /* + def merge_final_ops(seq1, seq2, res = []) + + + # This code looks complicated, but it's actually just a bunch of special + # cases for interactions between different combinators. + op1, op2 = ops1.first, ops2.first + if op1 && op2 + sel1 = seq1.pop + sel2 = seq2.pop + if op1 == '~' && op2 == '~' + if sel1.superselector?(sel2) + res.unshift sel2, '~' + elsif sel2.superselector?(sel1) + res.unshift sel1, '~' + else + merged = sel1.unify(sel2.members, sel2.subject?) + res.unshift [ + [sel1, '~', sel2, '~'], + [sel2, '~', sel1, '~'], + ([merged, '~'] if merged) + ].compact + end + elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~') + if op1 == '~' + tilde_sel, plus_sel = sel1, sel2 + else + tilde_sel, plus_sel = sel2, sel1 + end + + if tilde_sel.superselector?(plus_sel) + res.unshift plus_sel, '+' + else + merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) + res.unshift [ + [tilde_sel, '~', plus_sel, '+'], + ([merged, '+'] if merged) + ].compact + end + elsif op1 == '>' && %w[~ +].include?(op2) + res.unshift sel2, op2 + seq1.push sel1, op1 + elsif op2 == '>' && %w[~ +].include?(op1) + res.unshift sel1, op1 + seq2.push sel2, op2 + elsif op1 == op2 + return unless merged = sel1.unify(sel2.members, sel2.subject?) + res.unshift merged, op1 + else + # Unknown selector combinators can't be unified + return + end + return merge_final_ops(seq1, seq2, res) + elsif op1 + seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last) + res.unshift seq1.pop, op1 + return merge_final_ops(seq1, seq2, res) + else # op2 + seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last) + res.unshift seq2.pop, op2 + return merge_final_ops(seq1, seq2, res) + end + end + */ + static Node mergeFinalOps(Node& seq1, Node& seq2, Node& res) { + + Node ops1 = Node::createCollection(); + Node ops2 = Node::createCollection(); + + getAndRemoveFinalOps(seq1, ops1); + getAndRemoveFinalOps(seq2, ops2); + + // TODO: do we have newlines to remove? + // ops1.reject! {|o| o == "\n"} + // ops2.reject! {|o| o == "\n"} + + if (ops1.collection()->empty() && ops2.collection()->empty()) { + return res; + } + + if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) { + DefaultLcsComparator lcsDefaultComparator; + Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); + + // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. + + if (!(opsLcs == ops1 || opsLcs == ops2)) { + return Node::createNil(); + } + + if (ops1.collection()->size() > ops2.collection()->size()) { + res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend()); + } else { + res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend()); + } + + return res; + } + + if (!ops1.collection()->empty() && !ops2.collection()->empty()) { + + Node op1 = ops1.collection()->front(); + Node op2 = ops2.collection()->front(); + + Node sel1 = seq1.collection()->back(); + seq1.collection()->pop_back(); + + Node sel2 = seq2.collection()->back(); + seq2.collection()->pop_back(); + + if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) { + + if (sel1.selector()->is_superselector_of(sel2.selector())) { + + res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); + res.collection()->push_front(sel2); + + } else if (sel2.selector()->is_superselector_of(sel1.selector())) { + + res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); + res.collection()->push_front(sel1); + + } else { + + DEBUG_PRINTLN(ALL, "sel1: " << sel1) + DEBUG_PRINTLN(ALL, "sel2: " << sel2) + + Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result + // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); + pMergedWrapper->head(pMerged); + + DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) + + Node newRes = Node::createCollection(); + + Node firstPerm = Node::createCollection(); + firstPerm.collection()->push_back(sel1); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + firstPerm.collection()->push_back(sel2); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + newRes.collection()->push_back(firstPerm); + + Node secondPerm = Node::createCollection(); + secondPerm.collection()->push_back(sel2); + secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + secondPerm.collection()->push_back(sel1); + secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + newRes.collection()->push_back(secondPerm); + + if (pMerged) { + Node mergedPerm = Node::createCollection(); + mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); + mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + newRes.collection()->push_back(mergedPerm); + } + + res.collection()->push_front(newRes); + + DEBUG_PRINTLN(ALL, "RESULT: " << res) + + } + + } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) { + + Node tildeSel = sel1; + Node plusSel = sel2; + Node plusOp = op2; + if (op1.combinator() != Complex_Selector::PRECEDES) { + tildeSel = sel2; + plusSel = sel1; + plusOp = op1; + } + + if (tildeSel.selector()->is_superselector_of(plusSel.selector())) { + + res.collection()->push_front(plusOp); + res.collection()->push_front(plusSel); + + } else { + + DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel) + DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel) + + Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result + // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) + Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head()); + pMergedWrapper->head(pMerged); + + DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) + + Node newRes = Node::createCollection(); + + Node firstPerm = Node::createCollection(); + firstPerm.collection()->push_back(tildeSel); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); + firstPerm.collection()->push_back(plusSel); + firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); + newRes.collection()->push_back(firstPerm); + + if (pMerged) { + Node mergedPerm = Node::createCollection(); + mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); + mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); + newRes.collection()->push_back(mergedPerm); + } + + res.collection()->push_front(newRes); + + DEBUG_PRINTLN(ALL, "RESULT: " << res) + + } + } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) { + + res.collection()->push_front(op2); + res.collection()->push_front(sel2); + + seq1.collection()->push_back(sel1); + seq1.collection()->push_back(op1); + + } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) { + + res.collection()->push_front(op1); + res.collection()->push_front(sel1); + + seq2.collection()->push_back(sel2); + seq2.collection()->push_back(op2); + + } else if (op1.combinator() == op2.combinator()) { + + DEBUG_PRINTLN(ALL, "sel1: " << sel1) + DEBUG_PRINTLN(ALL, "sel2: " << sel2) + + Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result + // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) + Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); + pMergedWrapper->head(pMerged); + + DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) + + if (!pMerged) { + return Node::createNil(); + } + + res.collection()->push_front(op1); + res.collection()->push_front(Node::createSelector(pMergedWrapper)); + + DEBUG_PRINTLN(ALL, "RESULT: " << res) + + } else { + return Node::createNil(); + } + + return mergeFinalOps(seq1, seq2, res); + + } else if (!ops1.collection()->empty()) { + + Node op1 = ops1.collection()->front(); + + if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) { + seq2.collection()->pop_back(); + } + + // TODO: consider unshift(NodeCollection, Node) + res.collection()->push_front(op1); + res.collection()->push_front(seq1.collection()->back()); + seq1.collection()->pop_back(); + + return mergeFinalOps(seq1, seq2, res); + + } else { // !ops2.collection()->empty() + + Node op2 = ops2.collection()->front(); + + if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) { + seq1.collection()->pop_back(); + } + + res.collection()->push_front(op2); + res.collection()->push_front(seq2.collection()->back()); + seq2.collection()->pop_back(); + + return mergeFinalOps(seq1, seq2, res); + + } + + } + + + /* + This is the equivalent of ruby's Sequence.subweave. + + Here is the original subweave code for reference during porting. + + def subweave(seq1, seq2) + return [seq2] if seq1.empty? + return [seq1] if seq2.empty? + + seq1, seq2 = seq1.dup, seq2.dup + return unless init = merge_initial_ops(seq1, seq2) + return unless fin = merge_final_ops(seq1, seq2) + seq1 = group_selectors(seq1) + seq2 = group_selectors(seq2) + lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2| + next s1 if s1 == s2 + next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) + next s2 if parent_superselector?(s1, s2) + next s1 if parent_superselector?(s2, s1) + end + + diff = [[init]] + until lcs.empty? + diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift] + seq1.shift + seq2.shift + end + diff << chunks(seq1, seq2) {|s| s.empty?} + diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} + diff.reject! {|c| c.empty?} + + result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)} + + result + end + */ + Node subweave(Node& one, Node& two) { + // Check for the simple cases + if (one.collection()->size() == 0) { + Node out = Node::createCollection(); + out.collection()->push_back(two); + return out; + } + if (two.collection()->size() == 0) { + Node out = Node::createCollection(); + out.collection()->push_back(one); + return out; + } + + Node seq1 = Node::createCollection(); + seq1.plus(one); + Node seq2 = Node::createCollection(); + seq2.plus(two); + + DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1) + DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) + + Node init = mergeInitialOps(seq1, seq2); + if (init.isNil()) { + return Node::createNil(); + } + + DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init) + + Node res = Node::createCollection(); + Node fin = mergeFinalOps(seq1, seq2, res); + if (fin.isNil()) { + return Node::createNil(); + } + + DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin) + + + // Moving this line up since fin isn't modified between now and when it happened before + // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} + + for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end(); + finIter != finEndIter; ++finIter) { + + Node& childNode = *finIter; + + if (!childNode.isCollection()) { + Node wrapper = Node::createCollection(); + wrapper.collection()->push_back(childNode); + childNode = wrapper; + } + + } + + DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin) + + + + Node groupSeq1 = groupSelectors(seq1); + DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) + + Node groupSeq2 = groupSelectors(seq2); + DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2) + + + ComplexSelectorDeque groupSeq1Converted; + nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted); + + ComplexSelectorDeque groupSeq2Converted; + nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted); + + ComplexSelectorDeque out; + LcsCollectionComparator collectionComparator; + lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, out); + Node seqLcs = complexSelectorDequeToNode(out); + + DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) + + + Node initWrapper = Node::createCollection(); + initWrapper.collection()->push_back(init); + Node diff = Node::createCollection(); + diff.collection()->push_back(initWrapper); + + DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff) + + + while (!seqLcs.collection()->empty()) { + ParentSuperselectorChunker superselectorChunker(seqLcs); + Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker); + diff.collection()->push_back(chunksResult); + + Node lcsWrapper = Node::createCollection(); + lcsWrapper.collection()->push_back(seqLcs.collection()->front()); + seqLcs.collection()->pop_front(); + diff.collection()->push_back(lcsWrapper); + + if (groupSeq1.collection()->size()) groupSeq1.collection()->pop_front(); + if (groupSeq2.collection()->size()) groupSeq2.collection()->pop_front(); + } + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff) + + + DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2) + + + SubweaveEmptyChunker emptyChunker; + Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker); + diff.collection()->push_back(chunksResult); + + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff) + + + diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end()); + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff) + + // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection) + Node diffFiltered = Node::createCollection(); + for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end(); + diffIter != diffEndIter; ++diffIter) { + Node& node = *diffIter; + if (node.collection() && !node.collection()->empty()) { + diffFiltered.collection()->push_back(node); + } + } + diff = diffFiltered; + + DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff) + + + Node pathsResult = paths(diff); + + DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) + + + // We're flattening in place + for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end(); + pathsIter != pathsEndIter; ++pathsIter) { + + Node& child = *pathsIter; + child = flatten(child); + } + + DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult) + + + /* + TODO: implement + rejected = mapped.reject {|p| path_has_two_subjects?(p)} + $stderr.puts "REJECTED: #{rejected}" + */ + + + return pathsResult; + + } + /* + // disabled to avoid clang warning [-Wunused-function] + static Node subweaveNaive(const Node& one, const Node& two) { + Node out = Node::createCollection(); + + // Check for the simple cases + if (one.isNil()) { + out.collection()->push_back(two.klone()); + } else if (two.isNil()) { + out.collection()->push_back(one.klone()); + } else { + // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B + // See https://gist.github.com/nex3/7609394 for details. + + Node firstPerm = one.klone(); + Node twoCloned = two.klone(); + firstPerm.plus(twoCloned); + out.collection()->push_back(firstPerm); + + Node secondPerm = two.klone(); + Node oneCloned = one.klone(); + secondPerm.plus(oneCloned ); + out.collection()->push_back(secondPerm); + } + + return out; + } + */ + + + /* + This is the equivalent of ruby's Sequence.weave. + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def weave(path) + # This function works by moving through the selector path left-to-right, + # building all possible prefixes simultaneously. These prefixes are + # `befores`, while the remaining parenthesized suffixes is `afters`. + befores = [[]] + afters = path.dup + + until afters.empty? + current = afters.shift.dup + last_current = [current.pop] + + tempResult = [] + + for before in befores do + sub = subweave(before, current) + if sub.nil? + next + end + + for seqs in sub do + tempResult.push(seqs + last_current) + end + end + + befores = tempResult + + end + + return befores + end + */ + /* + def weave(path) + befores = [[]] + afters = path.dup + + until afters.empty? + current = afters.shift.dup + + last_current = [current.pop] + + + tempResult = [] + + for before in befores do + sub = subweave(before, current) + + if sub.nil? + next [] + end + + + for seqs in sub do + toPush = seqs + last_current + + tempResult.push(seqs + last_current) + end + + end + + befores = tempResult + + end + + return befores + end + */ + Node Extend::weave(Node& path) { + + DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) + + Node befores = Node::createCollection(); + befores.collection()->push_back(Node::createCollection()); + + Node afters = Node::createCollection(); + afters.plus(path); + + while (!afters.collection()->empty()) { + Node current = afters.collection()->front().klone(); + afters.collection()->pop_front(); + DEBUG_PRINTLN(WEAVE, "CURRENT: " << current) + if (current.collection()->size() == 0) continue; + + Node last_current = Node::createCollection(); + last_current.collection()->push_back(current.collection()->back()); + current.collection()->pop_back(); + DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current) + DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current) + + Node tempResult = Node::createCollection(); + + for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { + Node& before = *beforesIter; + + Node sub = subweave(before, current); + + DEBUG_PRINTLN(WEAVE, "SUB: " << sub) + + if (sub.isNil()) { + return Node::createCollection(); + } + + for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) { + Node& seqs = *subIter; + + Node toPush = Node::createCollection(); + toPush.plus(seqs); + toPush.plus(last_current); + + // move line feed from inner to outer selector (very hacky indeed) + if (last_current.collection() && last_current.collection()->front().selector()) { + toPush.got_line_feed = last_current.collection()->front().got_line_feed; + last_current.collection()->front().selector()->has_line_feed(false); + last_current.collection()->front().got_line_feed = false; + } + + tempResult.collection()->push_back(toPush); + + } + } + + befores = tempResult; + + } + + return befores; + } + + + + /* + This is the equivalent of ruby's SimpleSequence.do_extend. + + // TODO: I think I have some modified ruby code to put here. Check. + */ + /* + ISSUES: + - Previous TODO: Do we need to group the results by extender? + - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?) + - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything... + - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing... + */ + template + class GroupByToAFunctor { + public: + KeyType operator()(SubSetMapPair& extPair) const { + Complex_Selector_Obj pSelector = extPair.first; + return pSelector; + } + }; + Node Extend::extendCompoundSelector(Compound_Selector_Ptr pSelector, CompoundSelectorSet& seen, bool isReplace) { + + /* this turned out to be too much overhead + probably due to holding a "Node" object + // check if we already extended this selector + // we can do this since subset_map is "static" + auto memoized = memoizeCompound.find(pSelector); + if (memoized != memoizeCompound.end()) { + return memoized->second.klone(); + } + */ + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: ")) + // TODO: Ruby has another loop here to skip certain members? + + // let RESULTS be an empty list of complex selectors + Node results = Node::createCollection(); + // extendedSelectors.got_line_feed = true; + + SubSetMapPairs entries = subset_map.get_v(pSelector); + + GroupByToAFunctor extPairKeyFunctor; + SubSetMapResults arr; + group_by_to_a(entries, extPairKeyFunctor, arr); + + SubSetMapLookups holder; + + // for each (EXTENDER, TARGET) in MAP.get(COMPOUND): + for (SubSetMapResult& groupedPair : arr) { + + Complex_Selector_Obj seq = groupedPair.first; + SubSetMapPairs& group = groupedPair.second; + + DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) + + Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); + for (SubSetMapPair& pair : group) { + pair.second->extended(true); + pSels->concat(pair.second); + } + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) + + // The selector up to where the @extend is (ie, the thing to merge) + Complex_Selector_Ptr pExtComplexSelector = seq; + + // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? + // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) + Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels); + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) + + Compound_Selector_Obj pInnermostCompoundSelector = pExtComplexSelector->last()->head(); + + if (!pInnermostCompoundSelector) { + pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); + } + Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors); + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: ")) + + // RUBY: next unless unified + if (!pUnifiedSelector || pUnifiedSelector->length() == 0) { + continue; + } + + // TODO: implement the parent directive match (if necessary based on test failures) + // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none? + + // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just + // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more + // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered + // out and aren't operated on. + Complex_Selector_Obj pNewSelector = SASS_MEMORY_CLONE(pExtComplexSelector); // ->first(); + + Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); + + Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); + pNewSelector->set_innermost(pNewInnerMost, combinator); + +#ifdef DEBUG + ComplexSelectorSet debugSet; + debugSet = pNewSelector->sources(); + if (debugSet.size() > 0) { + throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this."); + } + debugSet = pExtComplexSelector->sources(); + if (debugSet.size() > 0) { + throw std::runtime_error("The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this."); + } +#endif + + + // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true); + // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. + DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector)) + + DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, "SOURCES NEW SEQ BEGIN: ")) + + // I actually want to create a copy here (performance!) + ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES THIS EXTEND: ")) + + newSourcesSet.insert(pExtComplexSelector); + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES WITH NEW SOURCE: ")) + + // RUBY: new_seq.add_sources!(sources + [seq]) + pNewSelector->addSources(newSourcesSet); + + DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, "SOURCES ON NEW SELECTOR AFTER ADD: ")) + DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) + + + if (pSels->has_line_feed()) pNewSelector->has_line_feed(true); + + holder.push_back(std::make_pair(pSels, pNewSelector)); + } + + + for (SubSetMapLookup& pair : holder) { + + Compound_Selector_Obj pSels = pair.first; + Complex_Selector_Obj pNewSelector = pair.second; + + + // RUBY??: next [] if seen.include?(sels) + if (seen.find(pSels) != seen.end()) { + continue; + } + + + CompoundSelectorSet recurseSeen(seen); + recurseSeen.insert(pSels); + + + DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector)) + Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, recurseSeen, isReplace, false); // !:isOriginal + + DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) + + for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end(); + iterator != endIterator; ++iterator) { + Node newSelector = *iterator; + +// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << results) +// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << results.contains(newSelector, false /*simpleSelectorOrderDependent*/)); + + if (!results.contains(newSelector)) { +// DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR") + results.collection()->push_back(newSelector); + } + } + } + + DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) + + // this turned out to be too much overhead + // memory results in a map table - since extending is very expensive + // memoizeCompound.insert(std::pair(pSelector, results)); + + return results; + } + + + // check if selector has something to be extended by subset_map + bool Extend::complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen) { + + bool hasExtension = false; + + Complex_Selector_Obj pIter = selector; + + while (!hasExtension && pIter) { + Compound_Selector_Obj pHead = pIter->head(); + + if (pHead) { + SubSetMapPairs entries = subset_map.get_v(pHead); + for (SubSetMapPair ext : entries) { + // check if both selectors have the same media block parent + // if (ext.first->media_block() == pComplexSelector->media_block()) continue; + if (ext.second->media_block() == 0) continue; + if (pHead->media_block() && + ext.second->media_block()->media_queries() && + pHead->media_block()->media_queries() + ) { + std::string query_left(ext.second->media_block()->media_queries()->to_string()); + std::string query_right(pHead->media_block()->media_queries()->to_string()); + if (query_left == query_right) continue; + } + + // fail if one goes across media block boundaries + std::stringstream err; + std::string cwd(Sass::File::get_cwd()); + ParserState pstate(ext.second->pstate()); + std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); + err << "You may not @extend an outer selector from within @media.\n"; + err << "You may only @extend selectors within the same directive.\n"; + err << "From \"@extend " << ext.second->to_string() << "\""; + err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; + error(err.str(), selector->pstate(), eval->exp.traces); + } + if (entries.size() > 0) hasExtension = true; + } + + pIter = pIter->tail(); + } + + return hasExtension; + } + + + /* + This is the equivalent of ruby's Sequence.do_extend. + + // TODO: I think I have some modified ruby code to put here. Check. + */ + /* + ISSUES: + - check to automatically include combinators doesn't transfer over to libsass' data model where + the combinator and compound selector are one unit + next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) + */ + Node Extend::extendComplexSelector(Complex_Selector_Ptr selector, CompoundSelectorSet& seen, bool isReplace, bool isOriginal) { + + // check if we already extended this selector + // we can do this since subset_map is "static" + auto memoized = memoizeComplex.find(selector); + if (memoized != memoizeComplex.end()) { + return memoized->second; + } + + // convert the input selector to extend node format + Node complexSelector = complexSelectorToNode(selector); + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) + + // let CHOICES be an empty list of selector-lists + // create new collection to hold the results + Node choices = Node::createCollection(); + + // for each compound selector COMPOUND in COMPLEX: + for (Node& sseqOrOp : *complexSelector.collection()) { + + DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) + + // If it's not a selector (meaning it's a combinator), just include it automatically + // RUBY: next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) + if (!sseqOrOp.isSelector()) { + // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node + // with one collection child. The collection child represents a Complex_Selector that is only a combinator. + Node outer = Node::createCollection(); + Node inner = Node::createCollection(); + outer.collection()->push_back(inner); + inner.collection()->push_back(sseqOrOp); + choices.collection()->push_back(outer); + continue; + } + + // verified now that node is a valid selector + Complex_Selector_Obj sseqSel = sseqOrOp.selector(); + Compound_Selector_Obj sseqHead = sseqSel->head(); + + // let EXTENDED be extend_compound(COMPOUND, SEEN) + // extend the compound selector against the given subset_map + // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) + Node extended = extendCompoundSelector(sseqHead, seen, isReplace); // slow(17%)! + if (sseqOrOp.got_line_feed) extended.got_line_feed = true; + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) + + // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with a ruby + // Array instead of a Sequence due to the member mapping: choices = extended.map {|seq| seq.members} + // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? + if (isOriginal && !selector->has_placeholder()) { + ComplexSelectorSet srcset; + srcset.insert(selector); + sseqSel->addSources(srcset); + // DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) + } + + bool isSuperselector = false; + // if no complex selector in EXTENDED is a superselector of COMPOUND: + for (Node& childNode : *extended.collection()) { + Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode); + if (pExtensionSelector->is_superselector_of(sseqSel)) { + isSuperselector = true; + break; + } + } + + if (!isSuperselector) { + // add a complex selector composed only of COMPOUND to EXTENDED + if (sseqOrOp.got_line_feed) sseqSel->has_line_feed(sseqOrOp.got_line_feed); + extended.collection()->push_front(complexSelectorToNode(sseqSel)); + } + + DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) + + // add EXTENDED to CHOICES + // Aggregate our current extensions + choices.collection()->push_back(extended); + } + + + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << choices) + + + + // Ruby Equivalent: paths + Node paths = Sass::paths(choices); + + DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) + + // let WEAVES be an empty list of selector lists + Node weaves = Node::createCollection(); + + // for each list of complex selectors PATH in paths(CHOICES): + for (Node& path : *paths.collection()) { + // add weave(PATH) to WEAVES + Node weaved = weave(path); // slow(12%)! + weaved.got_line_feed = path.got_line_feed; + weaves.collection()->push_back(weaved); + } + + DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) + + // Ruby Equivalent: trim + Node trimmed(trim(weaves, isReplace)); // slow(19%)! + + DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) + + // Ruby Equivalent: flatten + Node flattened(flatten(trimmed, 1)); + + DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors) + DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) + + // memory results in a map table - since extending is very expensive + memoizeComplex.insert(std::pair(selector, flattened)); + + // return trim(WEAVES) + return flattened; + } + + + + /* + This is the equivalent of ruby's CommaSequence.do_extend. + */ + // We get a selector list with has something to extend and a subset_map with + // all extenders. Pick the ones that match our selectors in the list. + Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen) { + + Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); + + // check if we already extended this selector + // we can do this since subset_map is "static" + auto memoized = memoizeList.find(pSelectorList); + if (memoized != memoizeList.end()) { + extendedSomething = true; + return memoized->second; + } + + extendedSomething = false; + // process each comlplex selector in the selector list. + // Find the ones that can be extended by given subset_map. + for (size_t index = 0, length = pSelectorList->length(); index < length; index++) { + Complex_Selector_Obj pSelector = (*pSelectorList)[index]; + + // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that. + // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to + // run through the extend code (which does a data model transformation), check if there is anything to extend before doing + // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps + // when debugging). + if (!complexSelectorHasExtension(pSelector, seen)) { + pNewSelectors->append(pSelector); + continue; + } + + // complexSelectorHasExtension was true! + extendedSomething = true; + + // now do the actual extension of the complex selector + Node extendedSelectors = extendComplexSelector(pSelector, seen, isReplace, true); + + if (!pSelector->has_placeholder()) { + Node nSelector(complexSelectorToNode(pSelector)); + if (!extendedSelectors.contains(nSelector)) { + pNewSelectors->append(pSelector); + continue; + } + } + + bool doReplace = isReplace; + for (Node& childNode : *extendedSelectors.collection()) { + // When it is a replace, skip the first one, unless there is only one + if(doReplace && extendedSelectors.collection()->size() > 1 ) { + doReplace = false; + continue; + } + pNewSelectors->append(nodeToComplexSelector(childNode)); + } + } + + Remove_Placeholders remove_placeholders; + // it seems that we have to remove the place holders early here + // normally we do this as the very last step (compare to ruby sass) + pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors); + + // unwrap all wrapped selectors with inner lists + for (Complex_Selector_Obj cur : pNewSelectors->elements()) { + // process tails + while (cur) { + // process header + if (cur->head() && seen.find(cur->head()) == seen.end()) { + CompoundSelectorSet recseen(seen); + recseen.insert(cur->head()); + // create a copy since we add multiple items if stuff get unwrapped + Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); + for (Simple_Selector_Obj hs : *cur->head()) { + if (Wrapped_Selector_Obj ws = Cast(hs)) { + ws->selector(SASS_MEMORY_CLONE(ws->selector())); + if (Selector_List_Obj sl = Cast(ws->selector())) { + // special case for ruby ass + if (sl->empty()) { + // this seems inconsistent but it is how ruby sass seems to remove parentheses + cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name())); + } + // has wrapped not selectors + else if (ws->name() == ":not") { + // extend the inner list of wrapped selector + bool extended = false; + Selector_List_Obj ext_sl = extendSelectorList(sl, false, extended, recseen); + for (size_t i = 0; i < ext_sl->length(); i += 1) { + if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { + // create clones for wrapped selector and the inner list + Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); + Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); + // remove parent selectors from inner selector + Compound_Selector_Obj ext_head = NULL; + if (ext_cs->first()) ext_head = ext_cs->first()->head(); + if (ext_head && ext_head && ext_head->length() > 0) { + cpy_ws_sl->append(ext_cs->first()); + } + // assign list to clone + cpy_ws->selector(cpy_ws_sl); + // append the clone + cpy_head->append(cpy_ws); + } + } + if (eval && extended) { + eval->exp.selector_stack.push_back(pNewSelectors); + cpy_head->perform(eval); + eval->exp.selector_stack.pop_back(); + } + } + // has wrapped selectors + else { + Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); + Selector_List_Obj ext_sl = extendSelectorList(sl, recseen); + cpy_ws->selector(ext_sl); + cpy_head->append(cpy_ws); + } + } else { + cpy_head->append(hs); + } + } else { + cpy_head->append(hs); + } + } + // replace header + cur->head(cpy_head); + } + // process tail + cur = cur->tail(); + } + } + + // memory results in a map table - since extending is very expensive + memoizeList.insert(std::pair(pSelectorList, pNewSelectors)); + + return pNewSelectors.detach(); + + } + + + bool shouldExtendBlock(Block_Obj b) { + + // If a block is empty, there's no reason to extend it since any rules placed on this block + // won't have any output. The main benefit of this is for structures like: + // + // .a { + // .b { + // x: y; + // } + // } + // + // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b). + // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since + // there are no child statements. However .a .b should have extensions applied. + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + + if (Cast(stm)) { + // Do nothing. This doesn't count as a statement that causes extension since we'll + // iterate over this rule set in a future visit and try to extend it. + } + else { + return true; + } + } + + return false; + + } + + + // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. + // Every Ruleset in the whole tree is calling this function. We decide if there + // was is @extend that matches our selector. If we find one, we will go further + // and call the extend magic for our selector. The subset_map contains all blocks + // where @extend was found. Pick the ones that match our selector! + void Extend::extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject) { + + DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string()) + + // Ruby sass seems to filter nodes that don't have any content well before we get here. + // I'm not sure the repercussions of doing so, so for now, let's just not extend things + // that won't be output later. Profiling shows this may us 0.2% or so. + if (!shouldExtendBlock(pObject->block())) { + DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT") + return; + } + + bool extendedSomething = false; + + CompoundSelectorSet seen; + Selector_List_Obj pNewSelectorList = extendSelectorList(pObject->selector(), false, extendedSomething, seen); + + if (extendedSomething && pNewSelectorList) { + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << pObject->selector()->to_string()) + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string()) + pNewSelectorList->remove_parent_selectors(); + pObject->selector(pNewSelectorList); + } else { + DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING") + } + } + + Extend::Extend(Subset_Map& ssm) + : subset_map(ssm), eval(NULL) + { } + + void Extend::setEval(Eval& e) { + eval = &e; + } + + void Extend::operator()(Block_Ptr b) + { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + } + // do final check if everything was extended + // we set `extended` flag on extended selectors + if (b->is_root()) { + // debug_subset_map(subset_map); + for(auto const &it : subset_map.values()) { + Complex_Selector_Ptr sel = NULL; + Compound_Selector_Ptr ext = NULL; + if (it.first) sel = it.first->first(); + if (it.second) ext = it.second; + if (ext && (ext->extended() || ext->is_optional())) continue; + std::string str_sel(sel ? sel->to_string({ NESTED, 5 }) : "NULL"); + std::string str_ext(ext ? ext->to_string({ NESTED, 5 }) : "NULL"); + // debug_ast(sel, "sel: "); + // debug_ast(ext, "ext: "); + error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" + "The selector \"" + str_ext + "\" was not found.\n" + "Use \"@extend " + str_ext + " !optional\" if the" + " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces); + } + } + + } + + void Extend::operator()(Ruleset_Ptr pRuleset) + { + extendObjectWithSelectorAndBlock( pRuleset ); + pRuleset->block()->perform(this); + } + + void Extend::operator()(Supports_Block_Ptr pFeatureBlock) + { + pFeatureBlock->block()->perform(this); + } + + void Extend::operator()(Media_Block_Ptr pMediaBlock) + { + pMediaBlock->block()->perform(this); + } + + void Extend::operator()(Directive_Ptr a) + { + // Selector_List_Ptr ls = Cast(a->selector()); + // selector_stack.push_back(ls); + if (a->block()) a->block()->perform(this); + // exp.selector_stack.pop_back(); + } +} diff --git a/src/extend.hpp b/src/extend.hpp new file mode 100644 index 000000000..03042f3e2 --- /dev/null +++ b/src/extend.hpp @@ -0,0 +1,86 @@ +#ifndef SASS_EXTEND_H +#define SASS_EXTEND_H + +#include +#include + +#include "ast.hpp" +#include "node.hpp" +#include "eval.hpp" +#include "operation.hpp" +#include "subset_map.hpp" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + Node subweave(Node& one, Node& two); + + class Extend : public Operation_CRTP { + + Subset_Map& subset_map; + Eval* eval; + + void fallback_impl(AST_Node_Ptr n) { } + + private: + + std::unordered_map< + Selector_List_Obj, // key + Selector_List_Obj, // value + HashNodes, // hasher + CompareNodes // compare + > memoizeList; + + std::unordered_map< + Complex_Selector_Obj, // key + Node, // value + HashNodes, // hasher + CompareNodes // compare + > memoizeComplex; + + /* this turned out to be too much overhead + re-evaluate once we store an ast selector + std::unordered_map< + Compound_Selector_Obj, // key + Node, // value + HashNodes, // hasher + CompareNodes // compare + > memoizeCompound; + */ + + void extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject); + Node extendComplexSelector(Complex_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace, bool isOriginal); + Node extendCompoundSelector(Compound_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace); + bool complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen); + Node trim(Node& seqses, bool isReplace); + Node weave(Node& path); + + public: + void setEval(Eval& eval); + Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen); + Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace = false) { + bool extendedSomething = false; + CompoundSelectorSet seen; + return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); + } + Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, CompoundSelectorSet& seen) { + bool isReplace = false; + bool extendedSomething = false; + return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); + } + Extend(Subset_Map&); + ~Extend() { } + + void operator()(Block_Ptr); + void operator()(Ruleset_Ptr); + void operator()(Supports_Block_Ptr); + void operator()(Media_Block_Ptr); + void operator()(Directive_Ptr); + + template + void fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 000000000..32d4a7c63 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,485 @@ +#include "sass.hpp" +#ifdef _WIN32 +# ifdef __MINGW32__ +# ifndef off64_t +# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ +# endif +# endif +# include +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#else +# include +#endif +#include +#include +#include +#include +#include +#include +#include "file.hpp" +#include "context.hpp" +#include "prelexer.hpp" +#include "utf8_string.hpp" +#include "sass_functions.hpp" +#include "sass2scss.h" + +#ifdef _WIN32 +# include + +# ifdef _MSC_VER +# include +inline static std::string wstring_to_string(const std::wstring& wstr) +{ + std::wstring_convert, wchar_t> wchar_converter; + return wchar_converter.to_bytes(wstr); +} +# else // mingw(/gcc) does not support C++11's codecvt yet. +inline static std::string wstring_to_string(const std::wstring &wstr) +{ + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} +# endif +#endif + +namespace Sass { + namespace File { + + // return the current directory + // always with forward slashes + // always with trailing slash + std::string get_cwd() + { + const size_t wd_len = 4096; + #ifndef _WIN32 + char wd[wd_len]; + char* pwd = getcwd(wd, wd_len); + // we should check error for more detailed info (e.g. ENOENT) + // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = pwd; + #else + wchar_t wd[wd_len]; + wchar_t* pwd = _wgetcwd(wd, wd_len); + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = wstring_to_string(pwd); + //convert backslashes to forward slashes + replace(cwd.begin(), cwd.end(), '\\', '/'); + #endif + if (cwd[cwd.length() - 1] != '/') cwd += '/'; + return cwd; + } + + // test if path exists and is a file + bool file_exists(const std::string& path) + { + #ifdef _WIN32 + wchar_t resolved[32768]; + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + DWORD dwAttrib = GetFileAttributesW(resolved); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + #else + struct stat st_buf; + return (stat (path.c_str(), &st_buf) == 0) && + (!S_ISDIR (st_buf.st_mode)); + #endif + } + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path) + { + #ifdef _WIN32 + if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; + #endif + size_t i = 0; + // check if we have a protocol + if (path[i] && Prelexer::is_alpha(path[i])) { + // skip over all alphanumeric characters + while (path[i] && Prelexer::is_alnum(path[i])) ++i; + i = i && path[i] == ':' ? i + 1 : 0; + } + return path[i] == '/'; + } + + // helper function to find the last directory seperator + inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) + { + size_t pos; + size_t pos_p = path.find_last_of('/', limit); + #ifdef _WIN32 + size_t pos_w = path.find_last_of('\\', limit); + #else + size_t pos_w = std::string::npos; + #endif + if (pos_p != std::string::npos && pos_w != std::string::npos) { + pos = std::max(pos_p, pos_w); + } + else if (pos_p != std::string::npos) { + pos = pos_p; + } + else { + pos = pos_w; + } + return pos; + } + + // return only the directory part of path + std::string dir_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return ""; + else return path.substr(0, pos+1); + } + + // return only the filename part of path + std::string base_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return path; + else return path.substr(pos+1); + } + + // do a logical clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path) + { + + // declarations + size_t pos; + + #ifdef _WIN32 + //convert backslashes to forward slashes + replace(path.begin(), path.end(), '\\', '/'); + #endif + + pos = 0; // remove all self references inside the path string + while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); + + // remove all leading and trailing self references + while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); + while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); + + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // then skip over start slashes + while (path[proto++] == '/') {} + + pos = proto; // collapse multiple delimiters into a single one + while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1); + + return path; + + } + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string l, std::string r) + { + + #ifdef _WIN32 + // convert Windows backslashes to URL forward slashes + replace(l.begin(), l.end(), '\\', '/'); + replace(r.begin(), r.end(), '\\', '/'); + #endif + + if (l.empty()) return r; + if (r.empty()) return l; + + if (is_absolute_path(r)) return r; + if (l[l.length()-1] != '/') l += '/'; + + // this does a logical cleanup of the right hand path + // Note that this does collapse x/../y sections into y. + // This is by design. If /foo on your system is a symlink + // to /bar/baz, then /foo/../cd is actually /bar/cd, + // not /cd as a naive ../ removal would give you. + // will only work on leading double dot dirs on rhs + // therefore it is safe if lhs is already resolved cwd + while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { + size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); + bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); + bool is_self = pos + 3 == L && (l[pos+1] == '.'); + if (!is_self && !is_slash) r = r.substr(3); + else if (pos == std::string::npos) break; + l = l.substr(0, pos == std::string::npos ? pos : pos + 1); + } + + return l + r; + } + + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path) + { + // magic algorith goes here!! + + // if the file is outside this directory show the absolute path + if (rel_path.substr(0, 3) == "../") { + return orig_path; + } + // this seems to work most of the time + return abs_path == orig_path ? abs_path : rel_path; + } + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd) + { + return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); + } + + // create a path that is relative to the given base directory + // path and base will first be resolved against cwd to make them absolute + std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd) + { + + std::string abs_path = rel2abs(path, cwd); + std::string abs_base = rel2abs(base, cwd); + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // distinguish between windows absolute paths and valid protocols + // we assume that protocols must at least have two chars to be valid + if (proto && path[proto++] == '/' && proto > 3) return path; + + #ifdef _WIN32 + // absolute link must have a drive letter, and we know that we + // can only create relative links if both are on the same drive + if (abs_base[0] != abs_path[0]) return abs_path; + #endif + + std::string stripped_uri = ""; + std::string stripped_base = ""; + + size_t index = 0; + size_t minSize = std::min(abs_path.size(), abs_base.size()); + for (size_t i = 0; i < minSize; ++i) { + #ifdef FS_CASE_SENSITIVE + if (abs_path[i] != abs_base[i]) break; + #else + // compare the charactes in a case insensitive manner + // windows fs is only case insensitive in ascii ranges + if (tolower(abs_path[i]) != tolower(abs_base[i])) break; + #endif + if (abs_path[i] == '/') index = i + 1; + } + for (size_t i = index; i < abs_path.size(); ++i) { + stripped_uri += abs_path[i]; + } + for (size_t i = index; i < abs_base.size(); ++i) { + stripped_base += abs_base[i]; + } + + size_t left = 0; + size_t directories = 0; + for (size_t right = 0; right < stripped_base.size(); ++right) { + if (stripped_base[right] == '/') { + if (stripped_base.substr(left, 2) != "..") { + ++directories; + } + else if (directories > 1) { + --directories; + } + else { + directories = 0; + } + left = right + 1; + } + } + + std::string result = ""; + for (size_t i = 0; i < directories; ++i) { + result += "../"; + } + result += stripped_uri; + + return result; + } + + // Resolution order for ambiguous imports: + // (1) filename as given + // (2) underscore + given + // (3) underscore + given + extension + // (4) given + extension + std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) + { + std::string filename = join_paths(root, file); + // split the filename + std::string base(dir_name(file)); + std::string name(base_name(file)); + std::vector includes; + // create full path (maybe relative) + std::string rel_path(join_paths(base, name)); + std::string abs_path(join_paths(root, rel_path)); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test variation with underscore + rel_path = join_paths(base, "_" + name); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test exts plus underscore + for(auto ext : exts) { + rel_path = join_paths(base, "_" + name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + } + // next test plain name with exts + for(auto ext : exts) { + rel_path = join_paths(base, name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + } + // nothing found + return includes; + } + + std::vector find_files(const std::string& file, const std::vector paths) + { + std::vector includes; + for (std::string path : paths) { + std::string abs_path(join_paths(path, file)); + if (file_exists(abs_path)) includes.push_back(abs_path); + } + return includes; + } + + std::vector find_files(const std::string& file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + // struct Sass_Options* options = sass_compiler_get_options(compiler); + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(dir_name(import->abs_path)); + paths.insert(paths.end(), incs.begin(), incs.end()); + // dispatch to find files in paths + return find_files(file, paths); + } + + // helper function to search one file in all include paths + // this is normally not used internally by libsass (C-API sugar) + std::string find_file(const std::string& file, const std::vector paths) + { + if (file.empty()) return file; + auto res = find_files(file, paths); + return res.empty() ? "" : res.front(); + } + + // helper function to resolve a filename + std::string find_include(const std::string& file, const std::vector paths) + { + // search in every include path for a match + for (size_t i = 0, S = paths.size(); i < S; ++i) + { + std::vector resolved(resolve_includes(paths[i], file)); + if (resolved.size()) return resolved[0].abs_path; + } + // nothing found + return std::string(""); + } + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& path) + { + #ifdef _WIN32 + BYTE* pBuffer; + DWORD dwBytes; + wchar_t resolved[32768]; + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + DWORD dwFileLength = GetFileSize(hFile, NULL); + if (dwFileLength == INVALID_FILE_SIZE) return 0; + // allocate an extra byte for the null char + // and another one for edge-cases in lexer + pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); + ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); + pBuffer[dwFileLength+0] = '\0'; + pBuffer[dwFileLength+1] = '\0'; + CloseHandle(hFile); + // just convert from unsigned char* + char* contents = (char*) pBuffer; + #else + struct stat st; + if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0; + std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + char* contents = 0; + if (file.is_open()) { + size_t size = file.tellg(); + // allocate an extra byte for the null char + // and another one for edge-cases in lexer + contents = (char*) malloc((size+2)*sizeof(char)); + file.seekg(0, std::ios::beg); + file.read(contents, size); + contents[size+0] = '\0'; + contents[size+1] = '\0'; + file.close(); + } + #endif + std::string extension; + if (path.length() > 5) { + extension = path.substr(path.length() - 5, 5); + } + for(size_t i=0; i split_path_list(const char* str) + { + std::vector paths; + if (str == NULL) return paths; + // find delimiter via prelexer (return zero at end) + const char* end = Prelexer::find_first(str); + // search until null delimiter + while (end) { + // add path from current position to delimiter + paths.push_back(std::string(str, end - str)); + str = end + 1; // skip delimiter + end = Prelexer::find_first(str); + } + // add path from current position to end + paths.push_back(std::string(str)); + // return back + return paths; + } + + } +} diff --git a/src/file.hpp b/src/file.hpp new file mode 100644 index 000000000..279b9e9f6 --- /dev/null +++ b/src/file.hpp @@ -0,0 +1,133 @@ +#ifndef SASS_FILE_H +#define SASS_FILE_H + +#include +#include + +#include "sass/context.h" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + namespace File { + + // return the current directory + // always with forward slashes + std::string get_cwd(); + + // test if path exists and is a file + bool file_exists(const std::string& file); + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path); + + // return only the directory part of path + std::string dir_name(const std::string& path); + + // return only the filename part of path + std::string base_name(const std::string&); + + // do a locigal clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path); + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string root, std::string name); + + // if the relative path is outside of the cwd we want want to + // show the absolute path in console messages + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path); + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // create a path that is relative to the given base directory + // path and base will first be resolved against cwd to make them absolute + std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // helper function to resolve a filename + // searching without variations in all paths + std::string find_file(const std::string& file, struct Sass_Compiler* options); + std::string find_file(const std::string& file, const std::vector paths); + + // helper function to resolve a include filename + // this has the original resolve logic for sass include + std::string find_include(const std::string& file, const std::vector paths); + + // split a path string delimited by semicolons or colons (OS dependent) + std::vector split_path_list(const char* paths); + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& file); + + } + + // requested import + class Importer { + public: + // requested import path + std::string imp_path; + // parent context path + std::string ctx_path; + // base derived from context path + // this really just acts as a cache + std::string base_path; + public: + Importer(std::string imp_path, std::string ctx_path) + : imp_path(File::make_canonical_path(imp_path)), + ctx_path(File::make_canonical_path(ctx_path)), + base_path(File::dir_name(ctx_path)) + { } + }; + + // a resolved include (final import) + class Include : public Importer { + public: + // resolved absolute path + std::string abs_path; + public: + Include(const Importer& imp, std::string abs_path) + : Importer(imp), abs_path(abs_path) + { } + }; + + // a loaded resource + class Resource { + public: + // the file contents + char* contents; + // conected sourcemap + char* srcmap; + public: + Resource(char* contents, char* srcmap) + : contents(contents), srcmap(srcmap) + { } + }; + + // parsed stylesheet from loaded resource + class StyleSheet : public Resource { + public: + // parsed root block + Block_Obj root; + public: + StyleSheet(const Resource& res, Block_Obj root) + : Resource(res), root(root) + { } + }; + + namespace File { + + static std::vector defaultExtensions = { ".scss", ".sass", ".css" }; + + std::vector resolve_includes(const std::string& root, const std::string& file, + const std::vector& exts = defaultExtensions); + + } + +} + +#endif diff --git a/src/functions.cpp b/src/functions.cpp new file mode 100644 index 000000000..c9999fc3a --- /dev/null +++ b/src/functions.cpp @@ -0,0 +1,2234 @@ +#include "sass.hpp" +#include "functions.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "parser.hpp" +#include "constants.hpp" +#include "inspect.hpp" +#include "extend.hpp" +#include "eval.hpp" +#include "util.hpp" +#include "expand.hpp" +#include "operators.hpp" +#include "utf8_string.hpp" +#include "sass/base.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) +#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) + +// return a number object (copied since we want to have reduced units) +#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy + +// special function for weird hsla percent (10px == 10% == 10 != 0.1) +#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double + +// macros for common ranges (u mean unsigned or upper, r for full range) +#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double +#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double +#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double +#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double +#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double +#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double + +// macros for color related inputs (rbg and alpha/opacity values) +#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double +#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double + +namespace Sass { + using std::stringstream; + using std::endl; + + Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) + { + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); + sig_parser.lex(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + sig, + name, + params, + func, + false); + } + + Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) + { + using namespace Prelexer; + + const char* sig = sass_function_get_signature(c_func); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); + // allow to overload generic callback plus @warn, @error and @debug with custom functions + sig_parser.lex < alternatives < identifier, exactly <'*'>, + exactly < Constants::warn_kwd >, + exactly < Constants::error_kwd >, + exactly < Constants::debug_kwd > + > >(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[c function]"), + sig, + name, + params, + c_func, + false, true); + } + + std::string function_name(Signature sig) + { + std::string str(sig); + return str.substr(0, str.find('(')); + } + + namespace Functions { + + inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) + { + try { + throw; + } + catch (utf8::invalid_code_point) { + std::string msg("utf8::invalid_code_point"); + error(msg, pstate, traces); + } + catch (utf8::not_enough_room) { + std::string msg("utf8::not_enough_room"); + error(msg, pstate, traces); + } + catch (utf8::invalid_utf8) { + std::string msg("utf8::invalid_utf8"); + error(msg, pstate, traces); + } + catch (...) { throw; } + } + + template + T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + T* val = Cast(env[argname]); + if (!val) { + std::string msg("argument `"); + msg += argname; + msg += "` of `"; + msg += sig; + msg += "` must be a "; + msg += T::type_name(); + error(msg, pstate, traces); + } + return val; + } + + Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Map_Ptr val = Cast(env[argname]); + if (val) return val; + + List_Ptr lval = Cast(env[argname]); + if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); + + // fallback on get_arg for error handling + val = get_arg(argname, env, sig, pstate, traces); + return val; + } + + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + double v = tmpnr.value(); + if (!(lo <= v && v <= hi)) { + std::stringstream msg; + msg << "argument `" << argname << "` of `" << sig << "` must be between "; + msg << lo << " and " << hi; + error(msg.str(), pstate, traces); + } + return v; + } + + Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + val = SASS_MEMORY_COPY(val); + val->reduce(); + return val; + } + + double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + /* + if (tmpnr.unit() == "%") { + tmpnr.value(tmpnr.value() / 100); + tmpnr.numerators.clear(); + } else { + if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); + } + */ + return tmpnr.value(); + } + + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + return tmpnr.value(); + } + + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 255.0); + } + } + + + inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value(), 0.0), 100.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 1.0); + } + } + + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) + + template + T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); + + template <> + Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + return Parser::parse_selector(exp_src.c_str(), ctx, traces); + } + + template <> + Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a string for `" << function_name(sig) << "'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); + if (sel_list->length() == 0) return NULL; + Complex_Selector_Obj first = sel_list->first(); + if (!first->tail()) return first->head(); + return first->tail()->head(); + } + + #ifdef __MINGW32__ + uint64_t GetSeed() + { + HCRYPTPROV hp = 0; + BYTE rb[8]; + CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hp, sizeof(rb), rb); + CryptReleaseContext(hp, 0); + + uint64_t seed; + memcpy(&seed, &rb[0], sizeof(seed)); + + return seed; + } + #else + uint64_t GetSeed() + { + std::random_device rd; + return rd(); + } + #endif + + // note: the performance of many implementations of + // random_device degrades sharply once the entropy pool + // is exhausted. For practical use, random_device is + // generally only used to seed a PRNG such as mt19937. + static std::mt19937 rand(static_cast(GetSeed())); + + // features + static std::set features { + "global-variable-shadowing", + "extend-selector-pseudoclass", + "at-error", + "units-level-3", + "custom-property" + }; + + //////////////// + // RGB FUNCTIONS + //////////////// + + inline bool special_number(String_Constant_Ptr s) { + if (s) { + std::string calc("calc("); + std::string var("var("); + std::string ss(s->value()); + return std::equal(calc.begin(), calc.end(), ss.begin()) || + std::equal(var.begin(), var.end(), ss.begin()); + } + return false; + } + + Signature rgb_sig = "rgb($red, $green, $blue)"; + BUILT_IN(rgb) + { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ")" + ); + } + + return SASS_MEMORY_NEW(Color, + pstate, + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue")); + } + + Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; + BUILT_IN(rgba_4) + { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return SASS_MEMORY_NEW(Color, + pstate, + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue"), + ALPHA_NUM("$alpha")); + } + + Signature rgba_2_sig = "rgba($color, $alpha)"; + BUILT_IN(rgba_2) + { + if ( + special_number(Cast(env["$color"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$color"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + Color_Ptr c_arg = ARG("$color", Color); + + if ( + special_number(Cast(env["$alpha"])) + ) { + std::stringstream strm; + strm << "rgba(" + << (int)c_arg->r() << ", " + << (int)c_arg->g() << ", " + << (int)c_arg->b() << ", " + << env["$alpha"]->to_string() + << ")"; + return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); + } + + Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); + new_c->a(ALPHA_NUM("$alpha")); + new_c->disp(""); + return new_c; + } + + Signature red_sig = "red($color)"; + BUILT_IN(red) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } + + Signature green_sig = "green($color)"; + BUILT_IN(green) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } + + Signature blue_sig = "blue($color)"; + BUILT_IN(blue) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } + + Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { + double p = weight/100; + double w = 2*p - 1; + double a = color1->a() - color2->a(); + + double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; + double w2 = 1 - w1; + + return SASS_MEMORY_NEW(Color, + pstate, + Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), + Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), + Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), + color1->a()*p + color2->a()*(1-p)); + } + + Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; + BUILT_IN(mix) + { + Color_Obj color1 = ARG("$color-1", Color); + Color_Obj color2 = ARG("$color-2", Color); + double weight = DARG_U_PRCT("$weight"); + return colormix(ctx, pstate, color1, color2, weight); + + } + + //////////////// + // HSL FUNCTIONS + //////////////// + + // RGB to HSL helper function + struct HSL { double h; double s; double l; }; + HSL rgb_to_hsl(double r, double g, double b) + { + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + r /= 255.0; g /= 255.0; b /= 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0; + double s; + double l = (max + min) / 2.0; + + if (NEAR_EQUAL(max, min)) { + h = s = 0; // achromatic + } + else { + if (l < 0.5) s = delta / (max + min); + else s = delta / (2.0 - max - min); + + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; + } + + HSL hsl_struct; + hsl_struct.h = h / 6 * 360; + hsl_struct.s = s * 100; + hsl_struct.l = l * 100; + + return hsl_struct; + } + + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) { + while (h < 0) h += 1; + while (h > 1) h -= 1; + if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; + if (h*2.0 < 1) return m2; + if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; + return m1; + } + + Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) + { + h /= 360.0; + s /= 100.0; + l /= 100.0; + + if (l < 0) l = 0; + if (s < 0) s = 0; + if (l > 1) l = 1; + if (s > 1) s = 1; + while (h < 0) h += 1; + while (h > 1) h -= 1; + + // if saturation is exacly zero, we loose + // information for hue, since it will evaluate + // to zero if converted back from rgb. Setting + // saturation to a very tiny number solves this. + if (s == 0) s = 1e-10; + + // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + double m2; + if (l <= 0.5) m2 = l*(s+1.0); + else m2 = (l+s)-(l*s); + double m1 = (l*2.0)-m2; + // round the results -- consider moving this into the Color constructor + double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); + double g = (h_to_rgb(m1, m2, h) * 255.0); + double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); + + return SASS_MEMORY_NEW(Color, pstate, r, g, b, a); + } + + Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; + BUILT_IN(hsl) + { + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + 1.0, + ctx, + pstate); + } + + Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; + BUILT_IN(hsla) + { + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + ARGVAL("$alpha"), + ctx, + pstate); + } + + Signature hue_sig = "hue($color)"; + BUILT_IN(hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); + } + + Signature saturation_sig = "saturation($color)"; + BUILT_IN(saturation) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); + } + + Signature lightness_sig = "lightness($color)"; + BUILT_IN(lightness) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); + } + + Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; + BUILT_IN(adjust_hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + double degrees = ARGVAL("$degrees"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h + degrees, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature lighten_sig = "lighten($color, $amount)"; + BUILT_IN(lighten) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + //Check lightness is not negative before lighten it + double hslcolorL = hsl_color.l; + if (hslcolorL < 0) { + hslcolorL = 0; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL + amount, + rgb_color->a(), + ctx, + pstate); + } + + Signature darken_sig = "darken($color, $amount)"; + BUILT_IN(darken) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + //Check lightness if not over 100, before darken it + double hslcolorL = hsl_color.l; + if (hslcolorL > 100) { + hslcolorL = 100; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL - amount, + rgb_color->a(), + ctx, + pstate); + } + + Signature saturate_sig = "saturate($color, $amount: false)"; + BUILT_IN(saturate) + { + // CSS3 filter function overload: pass literal through directly + if (!Cast(env["$amount"])) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); + } + + double amount = DARG_U_PRCT("$amount"); + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s + amount; + + // Saturation cannot be below 0 or above 100 + if (hslcolorS < 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature desaturate_sig = "desaturate($color, $amount)"; + BUILT_IN(desaturate) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s - amount; + + // Saturation cannot be below 0 or above 100 + if (hslcolorS <= 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature grayscale_sig = "grayscale($color)"; + BUILT_IN(grayscale) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); + } + + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h, + 0.0, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature complement_sig = "complement($color)"; + BUILT_IN(complement) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h - 180.0, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature invert_sig = "invert($color, $weight: 100%)"; + BUILT_IN(invert) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); + } + + double weight = DARG_U_PRCT("$weight"); + Color_Ptr rgb_color = ARG("$color", Color); + Color_Obj inv = SASS_MEMORY_NEW(Color, + pstate, + 255 - rgb_color->r(), + 255 - rgb_color->g(), + 255 - rgb_color->b(), + rgb_color->a()); + return colormix(ctx, pstate, inv, rgb_color, weight); + } + + //////////////////// + // OPACITY FUNCTIONS + //////////////////// + Signature alpha_sig = "alpha($color)"; + Signature opacity_sig = "opacity($color)"; + BUILT_IN(alpha) + { + String_Constant_Ptr ie_kwd = Cast(env["$color"]); + if (ie_kwd) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); + } + + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); + } + + return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); + } + + Signature opacify_sig = "opacify($color, $amount)"; + Signature fade_in_sig = "fade-in($color, $amount)"; + BUILT_IN(opacify) + { + Color_Ptr color = ARG("$color", Color); + double amount = DARG_U_FACT("$amount"); + double alpha = std::min(color->a() + amount, 1.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + Signature transparentize_sig = "transparentize($color, $amount)"; + Signature fade_out_sig = "fade-out($color, $amount)"; + BUILT_IN(transparentize) + { + Color_Ptr color = ARG("$color", Color); + double amount = DARG_U_FACT("$amount"); + double alpha = std::max(color->a() - amount, 0.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + //////////////////////// + // OTHER COLOR FUNCTIONS + //////////////////////// + + Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(adjust_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); + } + if (rgb) { + double rr = r ? DARG_R_BYTE("$red") : 0; + double gg = g ? DARG_R_BYTE("$green") : 0; + double bb = b ? DARG_R_BYTE("$blue") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rr, + color->g() + gg, + color->b() + bb, + color->a() + aa); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + double ss = s ? DARG_R_PRCT("$saturation") : 0; + double ll = l ? DARG_R_PRCT("$lightness") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; + return hsla_impl(hsl_struct.h + (h ? h->value() : 0), + hsl_struct.s + ss, + hsl_struct.l + ll, + color->a() + aa, + ctx, + pstate); + } + if (a) { + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + (a ? a->value() : 0)); + } + error("not enough arguments for `adjust-color'", pstate, traces); + // unreachable + return color; + } + + Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(scale_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); + } + if (rgb) { + double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; + double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; + double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), + color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), + color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + if (hsl) { + double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; + double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; + double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); + hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); + hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); + double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double ascale = (DARG_R_PRCT("$alpha")) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + error("not enough arguments for `scale-color'", pstate, traces); + // unreachable + return color; + } + + Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(change_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); + } + if (rgb) { + return SASS_MEMORY_NEW(Color, + pstate, + r ? DARG_U_BYTE("$red") : color->r(), + g ? DARG_U_BYTE("$green") : color->g(), + b ? DARG_U_BYTE("$blue") : color->b(), + a ? DARG_U_BYTE("$alpha") : color->a()); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + if (h) hsl_struct.h = std::fmod(h->value(), 360.0); + if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); + if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); + double alpha = a ? DARG_U_FACT("$alpha") : color->a(); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double alpha = DARG_U_FACT("$alpha"); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + error("not enough arguments for `change-color'", pstate, traces); + // unreachable + return color; + } + + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + Signature ie_hex_str_sig = "ie-hex-str($color)"; + BUILT_IN(ie_hex_str) + { + Color_Ptr c = ARG("$color", Color); + double r = cap_channel<0xff>(c->r()); + double g = cap_channel<0xff>(c->g()); + double b = cap_channel<0xff>(c->b()); + double a = cap_channel<1> (c->a()) * 255; + + std::stringstream ss; + ss << '#' << std::setw(2) << std::setfill('0'); + ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); + + std::string result(ss.str()); + for (size_t i = 0, L = result.length(); i < L; ++i) { + result[i] = std::toupper(result[i]); + } + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + /////////////////// + // STRING FUNCTIONS + /////////////////// + + Signature unquote_sig = "unquote($string)"; + BUILT_IN(sass_unquote) + { + AST_Node_Obj arg = env["$string"]; + if (String_Quoted_Ptr string_quoted = Cast(arg)) { + String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); + // remember if the string was quoted (color tokens) + result->is_delayed(true); // delay colors + return result; + } + else if (String_Constant_Ptr str = Cast(arg)) { + return str; + } + else if (Expression_Ptr ex = Cast(arg)) { + Sass_Output_Style oldstyle = ctx.c_options.output_style; + ctx.c_options.output_style = SASS_STYLE_NESTED; + std::string val(arg->to_string(ctx.c_options)); + val = Cast(arg) ? "null" : val; + ctx.c_options.output_style = oldstyle; + + deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); + return ex; + } + throw std::runtime_error("Invalid Data Type for unquote"); + } + + Signature quote_sig = "quote($string)"; + BUILT_IN(sass_quote) + { + AST_Node_Obj arg = env["$string"]; + // only set quote mark to true if already a string + if (String_Quoted_Ptr qstr = Cast(arg)) { + qstr->quote_mark('*'); + return qstr; + } + // all other nodes must be converted to a string node + std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); + String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); + result->quote_mark('*'); + return result; + } + + + Signature str_length_sig = "str-length($string)"; + BUILT_IN(str_length) + { + size_t len = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + len = UTF_8::code_point_count(s->value(), 0, s->value().size()); + + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)len); + } + + Signature str_insert_sig = "str-insert($string, $insert, $index)"; + BUILT_IN(str_insert) + { + std::string str; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + str = s->value(); + str = unquote(str); + String_Constant_Ptr i = ARG("$insert", String_Constant); + std::string ins = i->value(); + ins = unquote(ins); + double index = ARGVAL("$index"); + size_t len = UTF_8::code_point_count(str, 0, str.size()); + + if (index > 0 && index <= len) { + // positive and within string length + str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); + } + else if (index > len) { + // positive and past string length + str += ins; + } + else if (index == 0) { + str = ins + str; + } + else if (std::abs(index) <= len) { + // negative and within string length + index += len + 1; + str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); + } + else { + // negative and past string length + str = ins + str; + } + + if (String_Quoted_Ptr ss = Cast(s)) { + if (ss->quote_mark()) str = quote(str); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature str_index_sig = "str-index($string, $substring)"; + BUILT_IN(str_index) + { + size_t index = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + String_Constant_Ptr t = ARG("$substring", String_Constant); + std::string str = s->value(); + str = unquote(str); + std::string substr = t->value(); + substr = unquote(substr); + + size_t c_index = str.find(substr); + if(c_index == std::string::npos) { + return SASS_MEMORY_NEW(Null, pstate); + } + index = UTF_8::code_point_count(str, 0, c_index) + 1; + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)index); + } + + Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; + BUILT_IN(str_slice) + { + std::string newstr; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + double start_at = ARGVAL("$start-at"); + double end_at = ARGVAL("$end-at"); + String_Quoted_Ptr ss = Cast(s); + + std::string str = unquote(s->value()); + + size_t size = utf8::distance(str.begin(), str.end()); + + if (!Cast(env["$end-at"])) { + end_at = -1; + } + + if (end_at == 0 || (end_at + size) < 0) { + if (ss && ss->quote_mark()) newstr = quote(""); + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + if (end_at < 0) { + end_at += size + 1; + if (end_at == 0) end_at = 1; + } + if (end_at > size) { end_at = (double)size; } + if (start_at < 0) { + start_at += size + 1; + if (start_at < 0) start_at = 0; + } + else if (start_at == 0) { ++ start_at; } + + if (start_at <= end_at) + { + std::string::iterator start = str.begin(); + utf8::advance(start, start_at - 1, str.end()); + std::string::iterator end = start; + utf8::advance(end, end_at - start_at + 1, str.end()); + newstr = std::string(start, end); + } + if (ss) { + if(ss->quote_mark()) newstr = quote(newstr); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + Signature to_upper_case_sig = "to-upper-case($string)"; + BUILT_IN(to_upper_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::toupper(str[i]); + } + } + + if (String_Quoted_Ptr ss = Cast(s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + Signature to_lower_case_sig = "to-lower-case($string)"; + BUILT_IN(to_lower_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::tolower(str[i]); + } + } + + if (String_Quoted_Ptr ss = Cast(s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + /////////////////// + // NUMBER FUNCTIONS + /////////////////// + + Signature percentage_sig = "percentage($number)"; + BUILT_IN(percentage) + { + Number_Obj n = ARGN("$number"); + if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); + return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); + } + + Signature round_sig = "round($number)"; + BUILT_IN(round) + { + Number_Obj r = ARGN("$number"); + r->value(Sass::round(r->value(), ctx.c_options.precision)); + r->pstate(pstate); + return r.detach(); + } + + Signature ceil_sig = "ceil($number)"; + BUILT_IN(ceil) + { + Number_Obj r = ARGN("$number"); + r->value(std::ceil(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature floor_sig = "floor($number)"; + BUILT_IN(floor) + { + Number_Obj r = ARGN("$number"); + r->value(std::floor(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature abs_sig = "abs($number)"; + BUILT_IN(abs) + { + Number_Obj r = ARGN("$number"); + r->value(std::abs(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature min_sig = "min($numbers...)"; + BUILT_IN(min) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj least = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = Cast(val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); + } + if (least) { + if (*xi < *least) least = xi; + } else least = xi; + } + return least.detach(); + } + + Signature max_sig = "max($numbers...)"; + BUILT_IN(max) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj greatest = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = Cast(val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); + } + if (greatest) { + if (*greatest < *xi) greatest = xi; + } else greatest = xi; + } + return greatest.detach(); + } + + Signature random_sig = "random($limit:false)"; + BUILT_IN(random) + { + AST_Node_Obj arg = env["$limit"]; + Value_Ptr v = Cast(arg); + Number_Ptr l = Cast(arg); + Boolean_Ptr b = Cast(arg); + if (l) { + double lv = l->value(); + if (lv < 1) { + stringstream err; + err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; + error(err.str(), pstate, traces); + } + bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; + if (!eq_int) { + stringstream err; + err << "Expected $limit to be an integer but got " << lv << " for `random'"; + error(err.str(), pstate, traces); + } + std::uniform_real_distribution<> distributor(1, lv + 1); + uint_fast32_t distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, (double)distributed); + } + else if (b) { + std::uniform_real_distribution<> distributor(0, 1); + double distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, distributed); + } else if (v) { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); + } else { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); + } + } + + ///////////////// + // LIST FUNCTIONS + ///////////////// + + Signature length_sig = "length($list)"; + BUILT_IN(length) + { + if (Selector_List_Ptr sl = Cast(env["$list"])) { + return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); + } + Expression_Ptr v = ARG("$list", Expression); + if (v->concrete_type() == Expression::MAP) { + Map_Ptr map = Cast(env["$list"]); + return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); + } + if (v->concrete_type() == Expression::SELECTOR) { + if (Compound_Selector_Ptr h = Cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); + } else if (Selector_List_Ptr ls = Cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); + } else { + return SASS_MEMORY_NEW(Number, pstate, 1); + } + } + + List_Ptr list = Cast(env["$list"]); + return SASS_MEMORY_NEW(Number, + pstate, + (double)(list ? list->size() : 1)); + } + + Signature nth_sig = "nth($list, $n)"; + BUILT_IN(nth) + { + double nr = ARGVAL("$n"); + Map_Ptr m = Cast(env["$list"]); + if (Selector_List_Ptr sl = Cast(env["$list"])) { + size_t len = m ? m->length() : sl->length(); + bool empty = m ? m->empty() : sl->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + // return (*sl)[static_cast(index)]; + Listize listize; + return (*sl)[static_cast(index)]->perform(&listize); + } + List_Obj l = Cast(env["$list"]); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); + // if the argument isn't a list, then wrap it in a singleton list + if (!m && !l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + size_t len = m ? m->length() : l->length(); + bool empty = m ? m->empty() : l->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + + if (m) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(m->keys()[static_cast(index)]); + l->append(m->at(m->keys()[static_cast(index)])); + return l.detach(); + } + else { + Expression_Obj rv = l->value_at_index(static_cast(index)); + rv->set_delayed(false); + return rv.detach(); + } + } + + Signature set_nth_sig = "set-nth($list, $n, $value)"; + BUILT_IN(set_nth) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Number_Obj n = ARG("$n", Number); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); + if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + result->append(((i == index) ? v : (*l)[i])); + } + return result; + } + + Signature index_sig = "index($list, $value)"; + BUILT_IN(index) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + for (size_t i = 0, L = l->length(); i < L; ++i) { + if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); + } + return SASS_MEMORY_NEW(Null, pstate); + } + + Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; + BUILT_IN(join) + { + Map_Obj m1 = Cast(env["$list1"]); + Map_Obj m2 = Cast(env["$list2"]); + List_Obj l1 = Cast(env["$list1"]); + List_Obj l2 = Cast(env["$list2"]); + String_Constant_Obj sep = ARG("$separator", String_Constant); + enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); + Value* bracketed = ARG("$bracketed", Value); + bool is_bracketed = (l1 ? l1->is_bracketed() : false); + if (!l1) { + l1 = SASS_MEMORY_NEW(List, pstate, 1); + l1->append(ARG("$list1", Expression)); + sep_val = (l2 ? l2->separator() : SASS_SPACE); + is_bracketed = (l2 ? l2->is_bracketed() : false); + } + if (!l2) { + l2 = SASS_MEMORY_NEW(List, pstate, 1); + l2->append(ARG("$list2", Expression)); + } + if (m1) { + l1 = m1->to_list(pstate); + sep_val = SASS_COMMA; + } + if (m2) { + l2 = m2->to_list(pstate); + } + size_t len = l1->length() + l2->length(); + std::string sep_str = unquote(sep->value()); + if (sep_str == "space") sep_val = SASS_SPACE; + else if (sep_str == "comma") sep_val = SASS_COMMA; + else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + String_Constant_Obj bracketed_as_str = Cast(bracketed); + bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; + if (!bracketed_is_auto) { + is_bracketed = !bracketed->is_false(); + } + List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); + result->concat(l1); + result->concat(l2); + return result.detach(); + } + + Signature append_sig = "append($list, $val, $separator: auto)"; + BUILT_IN(append) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj v = ARG("$val", Expression); + if (Selector_List_Ptr sl = Cast(env["$list"])) { + Listize listize; + l = Cast(sl->perform(&listize)); + } + String_Constant_Obj sep = ARG("$separator", String_Constant); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + List_Ptr result = SASS_MEMORY_COPY(l); + std::string sep_str(unquote(sep->value())); + if (sep_str != "auto") { // check default first + if (sep_str == "space") result->separator(SASS_SPACE); + else if (sep_str == "comma") result->separator(SASS_COMMA); + else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + } + if (l->is_arglist()) { + result->append(SASS_MEMORY_NEW(Argument, + v->pstate(), + v, + "", + false, + false)); + + } else { + result->append(v); + } + return result; + } + + Signature zip_sig = "zip($lists...)"; + BUILT_IN(zip) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); + size_t shortest = 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + List_Obj ith = Cast(arglist->value_at_index(i)); + Map_Obj mith = Cast(arglist->value_at_index(i)); + if (!ith) { + if (mith) { + ith = mith->to_list(pstate); + } else { + ith = SASS_MEMORY_NEW(List, pstate, 1); + ith->append(arglist->value_at_index(i)); + } + if (arglist->is_arglist()) { + Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX + arg->value(ith); + } else { + (*arglist)[i] = ith; + } + } + shortest = (i ? std::min(shortest, ith->length()) : ith->length()); + } + List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); + size_t L = arglist->length(); + for (size_t i = 0; i < shortest; ++i) { + List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); + for (size_t j = 0; j < L; ++j) { + zipper->append(Cast(arglist->value_at_index(j))->at(i)); + } + zippers->append(zipper); + } + return zippers; + } + + Signature list_separator_sig = "list_separator($list)"; + BUILT_IN(list_separator) + { + List_Obj l = Cast(env["$list"]); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + return SASS_MEMORY_NEW(String_Quoted, + pstate, + l->separator() == SASS_COMMA ? "comma" : "space"); + } + + ///////////////// + // MAP FUNCTIONS + ///////////////// + + Signature map_get_sig = "map-get($map, $key)"; + BUILT_IN(map_get) + { + // leaks for "map-get((), foo)" if not Obj + // investigate why this is (unexpected) + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + try { + Expression_Obj val = m->at(v); + if (!val) return SASS_MEMORY_NEW(Null, pstate); + val->set_delayed(false); + return val.detach(); + } catch (const std::out_of_range&) { + return SASS_MEMORY_NEW(Null, pstate); + } + catch (...) { throw; } + } + + Signature map_has_key_sig = "map-has-key($map, $key)"; + BUILT_IN(map_has_key) + { + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); + } + + Signature map_keys_sig = "map-keys($map)"; + BUILT_IN(map_keys) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(key); + } + return result; + } + + Signature map_values_sig = "map-values($map)"; + BUILT_IN(map_values) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(m->at(key)); + } + return result; + } + + Signature map_merge_sig = "map-merge($map1, $map2)"; + BUILT_IN(map_merge) + { + Map_Obj m1 = ARGM("$map1", Map, ctx); + Map_Obj m2 = ARGM("$map2", Map, ctx); + + size_t len = m1->length() + m2->length(); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); + // concat not implemented for maps + *result += m1; + *result += m2; + return result; + } + + Signature map_remove_sig = "map-remove($map, $keys...)"; + BUILT_IN(map_remove) + { + bool remove; + Map_Obj m = ARGM("$map", Map, ctx); + List_Obj arglist = ARG("$keys", List); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, 1); + for (auto key : m->keys()) { + remove = false; + for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { + remove = Operators::eq(key, arglist->value_at_index(j)); + } + if (!remove) *result << std::make_pair(key, m->at(key)); + } + return result; + } + + Signature keywords_sig = "keywords($args)"; + BUILT_IN(keywords) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy + Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); + for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX + std::string name = std::string(arg->name()); + name = name.erase(0, 1); // sanitize name (remove dollar sign) + *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, + pstate, name), + arg->value()); + } + return result.detach(); + } + + ////////////////////////// + // INTROSPECTION FUNCTIONS + ////////////////////////// + + Signature type_of_sig = "type-of($value)"; + BUILT_IN(type_of) + { + Expression_Ptr v = ARG("$value", Expression); + return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); + } + + Signature unit_sig = "unit($number)"; + BUILT_IN(unit) + { + Number_Obj arg = ARGN("$number"); + std::string str(quote(arg->unit(), '"')); + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature unitless_sig = "unitless($number)"; + BUILT_IN(unitless) + { + Number_Obj arg = ARGN("$number"); + bool unitless = arg->is_unitless(); + return SASS_MEMORY_NEW(Boolean, pstate, unitless); + } + + Signature comparable_sig = "comparable($number-1, $number-2)"; + BUILT_IN(comparable) + { + Number_Obj n1 = ARGN("$number-1"); + Number_Obj n2 = ARGN("$number-2"); + if (n1->is_unitless() || n2->is_unitless()) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + // normalize into main units + n1->normalize(); n2->normalize(); + Units &lhs_unit = *n1, &rhs_unit = *n2; + bool is_comparable = (lhs_unit == rhs_unit); + return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); + } + + Signature variable_exists_sig = "variable-exists($name)"; + BUILT_IN(variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature global_variable_exists_sig = "global-variable-exists($name)"; + BUILT_IN(global_variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature function_exists_sig = "function-exists($name)"; + BUILT_IN(function_exists) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + + if(d_env.has_global(name+"[f]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature mixin_exists_sig = "mixin-exists($name)"; + BUILT_IN(mixin_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global(s+"[m]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature feature_exists_sig = "feature-exists($name)"; + BUILT_IN(feature_exists) + { + std::string s = unquote(ARG("$name", String_Constant)->value()); + + if(features.find(s) == features.end()) { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + } + + Signature call_sig = "call($name, $args...)"; + BUILT_IN(call) + { + std::string name; + Function_Ptr ff = Cast(env["$name"]); + String_Constant_Ptr ss = Cast(env["$name"]); + + if (ss) { + name = Util::normalize_underscores(unquote(ss->value())); + std::cerr << "DEPRECATION WARNING: "; + std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; + std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl; + std::cerr << std::endl; + } else if (ff) { + name = ff->name(); + } + + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); + + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + // std::string full_name(name + "[f]"); + // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; + // Parameters_Ptr params = def ? def->parameters() : 0; + // size_t param_size = params ? params->length() : 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj expr = arglist->value_at_index(i); + // if (params && params->has_rest_parameter()) { + // Parameter_Obj p = param_size > i ? (*params)[i] : 0; + // List_Ptr list = Cast(expr); + // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; + // } + if (arglist->is_arglist()) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX + args->append(SASS_MEMORY_NEW(Argument, + pstate, + expr, + arg ? arg->name() : "", + arg ? arg->is_rest_argument() : false, + arg ? arg->is_keyword_argument() : false)); + } else { + args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); + } + } + Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); + Expand expand(ctx, &d_env, &selector_stack); + func->via_call(true); // calc invoke is allowed + if (ff) func->func(ff); + return func->perform(&expand.eval); + } + + //////////////////// + // BOOLEAN FUNCTIONS + //////////////////// + + Signature not_sig = "not($value)"; + BUILT_IN(sass_not) + { + return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); + } + + Signature if_sig = "if($condition, $if-true, $if-false)"; + // BUILT_IN(sass_if) + // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } + BUILT_IN(sass_if) + { + Expand expand(ctx, &d_env, &selector_stack); + Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); + bool is_true = !cond->is_false(); + Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); + res = res->perform(&expand.eval); + res->set_delayed(false); // clone? + return res.detach(); + } + + ////////////////////////// + // MISCELLANEOUS FUNCTIONS + ////////////////////////// + + // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) + // unquoted_string(value.to_sass) + + Signature inspect_sig = "inspect($value)"; + BUILT_IN(inspect) + { + Expression_Ptr v = ARG("$value", Expression); + if (v->concrete_type() == Expression::NULL_VAL) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); + } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); + } else if (v->concrete_type() == Expression::STRING) { + return v; + } else { + // ToDo: fix to_sass for nested parentheses + Sass_Output_Style old_style; + old_style = ctx.c_options.output_style; + ctx.c_options.output_style = TO_SASS; + Emitter emitter(ctx.c_options); + Inspect i(emitter); + i.in_declaration = false; + v->perform(&i); + ctx.c_options.output_style = old_style; + return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); + } + // return v; + } + Signature selector_nest_sig = "selector-nest($selectors...)"; + BUILT_IN(selector_nest) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Obj str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector exploded; + selector_stack.push_back(result); + Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); + selector_stack.pop_back(); + for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { + exploded.push_back((*rv)[m]); + } + result->elements(exploded); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_append_sig = "selector-append($selectors...)"; + BUILT_IN(selector_append) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-append'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector newElements; + + // For every COMPLEX_SELECTOR in `result` + // For every COMPLEX_SELECTOR in `child` + // let parentSeqClone equal a copy of result->elements[i] + // let childSeq equal child->elements[j] + // Append all of childSeq head elements into parentSeqClone + // Set the innermost tail of parentSeqClone, to childSeq's tail + // Replace result->elements with newElements + for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { + for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { + Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); + Complex_Selector_Obj childSeq = (*child)[j]; + Complex_Selector_Obj base = childSeq->tail(); + + // Must be a simple sequence + if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, traces); + } + + // Cannot be a Universal selector + Element_Selector_Obj pType = Cast(childSeq->head()->first()); + if(pType && pType->name() == "*") { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, traces); + } + + // TODO: Add check for namespace stuff + + // append any selectors in childSeq's head + parentSeqClone->innermost()->head()->concat(base->head()); + + // Set parentSeqClone new tail + parentSeqClone->innermost()->tail( base->tail() ); + + newElements.push_back(parentSeqClone); + } + } + + result->elements(newElements); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; + BUILT_IN(selector_unify) + { + Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); + Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); + + Selector_List_Obj result = selector1->unify_with(selector2); + Listize listize; + return result->perform(&listize); + } + + Signature simple_selectors_sig = "simple-selectors($selector)"; + BUILT_IN(simple_selectors) + { + Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); + + List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Simple_Selector_Obj ss = (*sel)[i]; + std::string ss_string = ss->to_string() ; + + l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + } + + return l; + } + + Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; + BUILT_IN(selector_extend) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); + Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); + + Subset_Map subset_map; + extender->populate_extends(extendee, subset_map); + Extend extend(subset_map); + + Selector_List_Obj result = extend.extendSelectorList(selector, false); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; + BUILT_IN(selector_replace) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); + Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); + Subset_Map subset_map; + replacement->populate_extends(original, subset_map); + Extend extend(subset_map); + + Selector_List_Obj result = extend.extendSelectorList(selector, true); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_parse_sig = "selector-parse($selector)"; + BUILT_IN(selector_parse) + { + Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + + Listize listize; + return sel->perform(&listize); + } + + Signature is_superselector_sig = "is-superselector($super, $sub)"; + BUILT_IN(is_superselector) + { + Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); + Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); + bool result = sel_sup->is_superselector_of(sel_sub); + return SASS_MEMORY_NEW(Boolean, pstate, result); + } + + Signature unique_id_sig = "unique-id()"; + BUILT_IN(unique_id) + { + std::stringstream ss; + std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 + uint_fast32_t distributed = static_cast(distributor(rand)); + ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; + return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); + } + + Signature is_bracketed_sig = "is-bracketed($list)"; + BUILT_IN(is_bracketed) + { + Value_Obj value = ARG("$list", Value); + List_Obj list = Cast(value); + return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); + } + + Signature content_exists_sig = "content-exists()"; + BUILT_IN(content_exists) + { + if (!d_env.has_global("is_in_mixin")) { + error("Cannot call content-exists() except within a mixin.", pstate, traces); + } + return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); + } + + Signature get_function_sig = "get-function($name, $css: false)"; + BUILT_IN(get_function) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + std::string full_name = name + "[f]"; + + Boolean_Obj css = ARG("$css", Boolean); + if (!css->is_false()) { + Definition_Ptr def = SASS_MEMORY_NEW(Definition, + pstate, + name, + SASS_MEMORY_NEW(Parameters, pstate), + SASS_MEMORY_NEW(Block, pstate, 0, false), + Definition::FUNCTION); + return SASS_MEMORY_NEW(Function, pstate, def, true); + } + + + if (!d_env.has_global(full_name)) { + error("Function not found: " + name, pstate, traces); + } + + Definition_Ptr def = Cast(d_env[full_name]); + return SASS_MEMORY_NEW(Function, pstate, def, false); + } + } +} diff --git a/src/functions.hpp b/src/functions.hpp new file mode 100644 index 000000000..7019be934 --- /dev/null +++ b/src/functions.hpp @@ -0,0 +1,198 @@ +#ifndef SASS_FUNCTIONS_H +#define SASS_FUNCTIONS_H + +#include "listize.hpp" +#include "position.hpp" +#include "environment.hpp" +#include "ast_fwd_decl.hpp" +#include "sass/functions.h" + +#define BUILT_IN(name) Expression_Ptr \ +name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtraces traces, std::vector selector_stack) + +namespace Sass { + struct Backtrace; + typedef const char* Signature; + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); + + Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx); + Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx); + + std::string function_name(Signature); + + namespace Functions { + + extern Signature rgb_sig; + extern Signature rgba_4_sig; + extern Signature rgba_2_sig; + extern Signature red_sig; + extern Signature green_sig; + extern Signature blue_sig; + extern Signature mix_sig; + extern Signature hsl_sig; + extern Signature hsla_sig; + extern Signature hue_sig; + extern Signature saturation_sig; + extern Signature lightness_sig; + extern Signature adjust_hue_sig; + extern Signature lighten_sig; + extern Signature darken_sig; + extern Signature saturate_sig; + extern Signature desaturate_sig; + extern Signature grayscale_sig; + extern Signature complement_sig; + extern Signature invert_sig; + extern Signature alpha_sig; + extern Signature opacity_sig; + extern Signature opacify_sig; + extern Signature fade_in_sig; + extern Signature transparentize_sig; + extern Signature fade_out_sig; + extern Signature adjust_color_sig; + extern Signature scale_color_sig; + extern Signature change_color_sig; + extern Signature ie_hex_str_sig; + extern Signature unquote_sig; + extern Signature quote_sig; + extern Signature str_length_sig; + extern Signature str_insert_sig; + extern Signature str_index_sig; + extern Signature str_slice_sig; + extern Signature to_upper_case_sig; + extern Signature to_lower_case_sig; + extern Signature percentage_sig; + extern Signature round_sig; + extern Signature ceil_sig; + extern Signature floor_sig; + extern Signature abs_sig; + extern Signature min_sig; + extern Signature max_sig; + extern Signature inspect_sig; + extern Signature random_sig; + extern Signature length_sig; + extern Signature nth_sig; + extern Signature index_sig; + extern Signature join_sig; + extern Signature append_sig; + extern Signature zip_sig; + extern Signature list_separator_sig; + extern Signature type_of_sig; + extern Signature unit_sig; + extern Signature unitless_sig; + extern Signature comparable_sig; + extern Signature variable_exists_sig; + extern Signature global_variable_exists_sig; + extern Signature function_exists_sig; + extern Signature mixin_exists_sig; + extern Signature feature_exists_sig; + extern Signature call_sig; + extern Signature not_sig; + extern Signature if_sig; + extern Signature map_get_sig; + extern Signature map_merge_sig; + extern Signature map_remove_sig; + extern Signature map_keys_sig; + extern Signature map_values_sig; + extern Signature map_has_key_sig; + extern Signature keywords_sig; + extern Signature set_nth_sig; + extern Signature unique_id_sig; + extern Signature selector_nest_sig; + extern Signature selector_append_sig; + extern Signature selector_extend_sig; + extern Signature selector_replace_sig; + extern Signature selector_unify_sig; + extern Signature is_superselector_sig; + extern Signature simple_selectors_sig; + extern Signature selector_parse_sig; + extern Signature is_bracketed_sig; + extern Signature content_exists_sig; + extern Signature get_function_sig; + + BUILT_IN(rgb); + BUILT_IN(rgba_4); + BUILT_IN(rgba_2); + BUILT_IN(red); + BUILT_IN(green); + BUILT_IN(blue); + BUILT_IN(mix); + BUILT_IN(hsl); + BUILT_IN(hsla); + BUILT_IN(hue); + BUILT_IN(saturation); + BUILT_IN(lightness); + BUILT_IN(adjust_hue); + BUILT_IN(lighten); + BUILT_IN(darken); + BUILT_IN(saturate); + BUILT_IN(desaturate); + BUILT_IN(grayscale); + BUILT_IN(complement); + BUILT_IN(invert); + BUILT_IN(alpha); + BUILT_IN(opacify); + BUILT_IN(transparentize); + BUILT_IN(adjust_color); + BUILT_IN(scale_color); + BUILT_IN(change_color); + BUILT_IN(ie_hex_str); + BUILT_IN(sass_unquote); + BUILT_IN(sass_quote); + BUILT_IN(str_length); + BUILT_IN(str_insert); + BUILT_IN(str_index); + BUILT_IN(str_slice); + BUILT_IN(to_upper_case); + BUILT_IN(to_lower_case); + BUILT_IN(percentage); + BUILT_IN(round); + BUILT_IN(ceil); + BUILT_IN(floor); + BUILT_IN(abs); + BUILT_IN(min); + BUILT_IN(max); + BUILT_IN(inspect); + BUILT_IN(random); + BUILT_IN(length); + BUILT_IN(nth); + BUILT_IN(index); + BUILT_IN(join); + BUILT_IN(append); + BUILT_IN(zip); + BUILT_IN(list_separator); + BUILT_IN(type_of); + BUILT_IN(unit); + BUILT_IN(unitless); + BUILT_IN(comparable); + BUILT_IN(variable_exists); + BUILT_IN(global_variable_exists); + BUILT_IN(function_exists); + BUILT_IN(mixin_exists); + BUILT_IN(feature_exists); + BUILT_IN(call); + BUILT_IN(sass_not); + BUILT_IN(sass_if); + BUILT_IN(map_get); + BUILT_IN(map_merge); + BUILT_IN(map_remove); + BUILT_IN(map_keys); + BUILT_IN(map_values); + BUILT_IN(map_has_key); + BUILT_IN(keywords); + BUILT_IN(set_nth); + BUILT_IN(unique_id); + BUILT_IN(selector_nest); + BUILT_IN(selector_append); + BUILT_IN(selector_extend); + BUILT_IN(selector_replace); + BUILT_IN(selector_unify); + BUILT_IN(is_superselector); + BUILT_IN(simple_selectors); + BUILT_IN(selector_parse); + BUILT_IN(is_bracketed); + BUILT_IN(content_exists); + BUILT_IN(get_function); + } +} + +#endif diff --git a/src/inspect.cpp b/src/inspect.cpp new file mode 100644 index 000000000..5cd8cc0c7 --- /dev/null +++ b/src/inspect.cpp @@ -0,0 +1,1138 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "inspect.hpp" +#include "context.hpp" +#include "listize.hpp" +#include "color_maps.hpp" +#include "utf8/checked.h" + +namespace Sass { + + Inspect::Inspect(const Emitter& emi) + : Emitter(emi) + { } + Inspect::~Inspect() { } + + // statements + void Inspect::operator()(Block_Ptr block) + { + if (!block->is_root()) { + add_open_mapping(block); + append_scope_opener(); + } + if (output_style() == NESTED) indentation += block->tabs(); + for (size_t i = 0, L = block->length(); i < L; ++i) { + (*block)[i]->perform(this); + } + if (output_style() == NESTED) indentation -= block->tabs(); + if (!block->is_root()) { + append_scope_closer(); + add_close_mapping(block); + } + + } + + void Inspect::operator()(Ruleset_Ptr ruleset) + { + if (ruleset->selector()) { + opt.in_selector = true; + ruleset->selector()->perform(this); + opt.in_selector = false; + } + if (ruleset->block()) { + ruleset->block()->perform(this); + } + } + + void Inspect::operator()(Keyframe_Rule_Ptr rule) + { + if (rule->name()) rule->name()->perform(this); + if (rule->block()) rule->block()->perform(this); + } + + void Inspect::operator()(Bubble_Ptr bubble) + { + append_indentation(); + append_token("::BUBBLE", bubble); + append_scope_opener(); + bubble->node()->perform(this); + append_scope_closer(); + } + + void Inspect::operator()(Media_Block_Ptr media_block) + { + append_indentation(); + append_token("@media", media_block); + append_mandatory_space(); + in_media_block = true; + media_block->media_queries()->perform(this); + in_media_block = false; + media_block->block()->perform(this); + } + + void Inspect::operator()(Supports_Block_Ptr feature_block) + { + append_indentation(); + append_token("@supports", feature_block); + append_mandatory_space(); + feature_block->condition()->perform(this); + feature_block->block()->perform(this); + } + + void Inspect::operator()(At_Root_Block_Ptr at_root_block) + { + append_indentation(); + append_token("@at-root ", at_root_block); + append_mandatory_space(); + if(at_root_block->expression()) at_root_block->expression()->perform(this); + if(at_root_block->block()) at_root_block->block()->perform(this); + } + + void Inspect::operator()(Directive_Ptr at_rule) + { + append_indentation(); + append_token(at_rule->keyword(), at_rule); + if (at_rule->selector()) { + append_mandatory_space(); + bool was_wrapped = in_wrapped; + in_wrapped = true; + at_rule->selector()->perform(this); + in_wrapped = was_wrapped; + } + if (at_rule->value()) { + append_mandatory_space(); + at_rule->value()->perform(this); + } + if (at_rule->block()) { + at_rule->block()->perform(this); + } + else { + append_delimiter(); + } + } + + void Inspect::operator()(Declaration_Ptr dec) + { + if (dec->value()->concrete_type() == Expression::NULL_VAL) return; + bool was_decl = in_declaration; + in_declaration = true; + LOCAL_FLAG(in_custom_property, dec->is_custom_property()); + + if (output_style() == NESTED) + indentation += dec->tabs(); + append_indentation(); + if (dec->property()) + dec->property()->perform(this); + append_colon_separator(); + + if (dec->value()->concrete_type() == Expression::SELECTOR) { + Listize listize; + Expression_Obj ls = dec->value()->perform(&listize); + ls->perform(this); + } else { + dec->value()->perform(this); + } + + if (dec->is_important()) { + append_optional_space(); + append_string("!important"); + } + append_delimiter(); + if (output_style() == NESTED) + indentation -= dec->tabs(); + in_declaration = was_decl; + } + + void Inspect::operator()(Assignment_Ptr assn) + { + append_token(assn->variable(), assn); + append_colon_separator(); + assn->value()->perform(this); + if (assn->is_default()) { + append_optional_space(); + append_string("!default"); + } + append_delimiter(); + } + + void Inspect::operator()(Import_Ptr import) + { + if (!import->urls().empty()) { + append_token("@import", import); + append_mandatory_space(); + + import->urls().front()->perform(this); + if (import->urls().size() == 1) { + if (import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + for (size_t i = 1, S = import->urls().size(); i < S; ++i) { + append_mandatory_linefeed(); + append_token("@import", import); + append_mandatory_space(); + + import->urls()[i]->perform(this); + if (import->urls().size() - 1 == i) { + if (import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + } + } + } + + void Inspect::operator()(Import_Stub_Ptr import) + { + append_indentation(); + append_token("@import", import); + append_mandatory_space(); + append_string(import->imp_path()); + append_delimiter(); + } + + void Inspect::operator()(Warning_Ptr warning) + { + append_indentation(); + append_token("@warn", warning); + append_mandatory_space(); + warning->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Error_Ptr error) + { + append_indentation(); + append_token("@error", error); + append_mandatory_space(); + error->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Debug_Ptr debug) + { + append_indentation(); + append_token("@debug", debug); + append_mandatory_space(); + debug->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Comment_Ptr comment) + { + in_comment = true; + comment->text()->perform(this); + in_comment = false; + } + + void Inspect::operator()(If_Ptr cond) + { + append_indentation(); + append_token("@if", cond); + append_mandatory_space(); + cond->predicate()->perform(this); + cond->block()->perform(this); + if (cond->alternative()) { + append_optional_linefeed(); + append_indentation(); + append_string("else"); + cond->alternative()->perform(this); + } + } + + void Inspect::operator()(For_Ptr loop) + { + append_indentation(); + append_token("@for", loop); + append_mandatory_space(); + append_string(loop->variable()); + append_string(" from "); + loop->lower_bound()->perform(this); + append_string(loop->is_inclusive() ? " through " : " to "); + loop->upper_bound()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Each_Ptr loop) + { + append_indentation(); + append_token("@each", loop); + append_mandatory_space(); + append_string(loop->variables()[0]); + for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { + append_comma_separator(); + append_string(loop->variables()[i]); + } + append_string(" in "); + loop->list()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(While_Ptr loop) + { + append_indentation(); + append_token("@while", loop); + append_mandatory_space(); + loop->predicate()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Return_Ptr ret) + { + append_indentation(); + append_token("@return", ret); + append_mandatory_space(); + ret->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Extension_Ptr extend) + { + append_indentation(); + append_token("@extend", extend); + append_mandatory_space(); + extend->selector()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Definition_Ptr def) + { + append_indentation(); + if (def->type() == Definition::MIXIN) { + append_token("@mixin", def); + append_mandatory_space(); + } else { + append_token("@function", def); + append_mandatory_space(); + } + append_string(def->name()); + def->parameters()->perform(this); + def->block()->perform(this); + } + + void Inspect::operator()(Mixin_Call_Ptr call) + { + append_indentation(); + append_token("@include", call); + append_mandatory_space(); + append_string(call->name()); + if (call->arguments()) { + call->arguments()->perform(this); + } + if (call->block()) { + append_optional_space(); + call->block()->perform(this); + } + if (!call->block()) append_delimiter(); + } + + void Inspect::operator()(Content_Ptr content) + { + append_indentation(); + append_token("@content", content); + append_delimiter(); + } + + void Inspect::operator()(Map_Ptr map) + { + if (output_style() == TO_SASS && map->empty()) { + append_string("()"); + return; + } + if (map->empty()) return; + if (map->is_invisible()) return; + bool items_output = false; + append_string("("); + for (auto key : map->keys()) { + if (items_output) append_comma_separator(); + key->perform(this); + append_colon_separator(); + LOCAL_FLAG(in_space_array, true); + LOCAL_FLAG(in_comma_array, true); + map->at(key)->perform(this); + items_output = true; + } + append_string(")"); + } + + std::string Inspect::lbracket(List_Ptr list) { + return list->is_bracketed() ? "[" : "("; + } + + std::string Inspect::rbracket(List_Ptr list) { + return list->is_bracketed() ? "]" : ")"; + } + + void Inspect::operator()(List_Ptr list) + { + if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { + append_string(lbracket(list)); + append_string(rbracket(list)); + return; + } + std::string sep(list->separator() == SASS_SPACE ? " " : ","); + if ((output_style() != COMPRESSED) && sep == ",") sep += " "; + else if (in_media_block && sep != " ") sep += " "; // verified + if (list->empty()) return; + bool items_output = false; + + bool was_space_array = in_space_array; + bool was_comma_array = in_comma_array; + // if the list is bracketed, always include the left bracket + if (list->is_bracketed()) { + append_string(lbracket(list)); + } + // probably ruby sass eqivalent of element_needs_parens + else if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(lbracket(list)); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(lbracket(list)); + } + + if (list->separator() == SASS_SPACE) in_space_array = true; + else if (list->separator() == SASS_COMMA) in_comma_array = true; + + for (size_t i = 0, L = list->size(); i < L; ++i) { + if (list->separator() == SASS_HASH) + { sep[0] = i % 2 ? ':' : ','; } + Expression_Obj list_item = list->at(i); + if (output_style() != TO_SASS) { + if (list_item->is_invisible()) { + // this fixes an issue with "" in a list + if (!Cast(list_item)) { + continue; + } + } + } + if (items_output) { + append_string(sep); + } + if (items_output && sep != " ") + append_optional_space(); + list_item->perform(this); + items_output = true; + } + + in_comma_array = was_comma_array; + in_space_array = was_space_array; + + // if the list is bracketed, always include the right bracket + if (list->is_bracketed()) { + if (list->separator() == SASS_COMMA && list->size() == 1) { + append_string(","); + } + append_string(rbracket(list)); + } + // probably ruby sass eqivalent of element_needs_parens + else if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(","); + append_string(rbracket(list)); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(rbracket(list)); + } + + } + + void Inspect::operator()(Binary_Expression_Ptr expr) + { + expr->left()->perform(this); + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_before + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + + )) append_string(" "); + switch (expr->optype()) { + case Sass_OP::AND: append_string("&&"); break; + case Sass_OP::OR: append_string("||"); break; + case Sass_OP::EQ: append_string("=="); break; + case Sass_OP::NEQ: append_string("!="); break; + case Sass_OP::GT: append_string(">"); break; + case Sass_OP::GTE: append_string(">="); break; + case Sass_OP::LT: append_string("<"); break; + case Sass_OP::LTE: append_string("<="); break; + case Sass_OP::ADD: append_string("+"); break; + case Sass_OP::SUB: append_string("-"); break; + case Sass_OP::MUL: append_string("*"); break; + case Sass_OP::DIV: append_string("/"); break; + case Sass_OP::MOD: append_string("%"); break; + default: break; // shouldn't get here + } + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_after + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + )) append_string(" "); + expr->right()->perform(this); + } + + void Inspect::operator()(Unary_Expression_Ptr expr) + { + if (expr->optype() == Unary_Expression::PLUS) append_string("+"); + else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); + else append_string("-"); + expr->operand()->perform(this); + } + + void Inspect::operator()(Function_Call_Ptr call) + { + append_token(call->name(), call); + call->arguments()->perform(this); + } + + void Inspect::operator()(Function_Call_Schema_Ptr call) + { + call->name()->perform(this); + call->arguments()->perform(this); + } + + void Inspect::operator()(Variable_Ptr var) + { + append_token(var->name(), var); + } + + void Inspect::operator()(Number_Ptr n) + { + + std::string res; + + // reduce units + n->reduce(); + + // check if the fractional part of the value equals to zero + // neat trick from http://stackoverflow.com/a/1521682/1550314 + // double int_part; bool is_int = modf(value, &int_part) == 0.0; + + // this all cannot be done with one run only, since fixed + // output differs from normal output and regular output + // can contain scientific notation which we do not want! + + // first sample + std::stringstream ss; + ss.precision(12); + ss << n->value(); + + // check if we got scientific notation in result + if (ss.str().find_first_of("e") != std::string::npos) { + ss.clear(); ss.str(std::string()); + ss.precision(std::max(12, opt.precision)); + ss << std::fixed << n->value(); + } + + std::string tmp = ss.str(); + size_t pos_point = tmp.find_first_of(".,"); + size_t pos_fract = tmp.find_last_not_of("0"); + bool is_int = pos_point == pos_fract || + pos_point == std::string::npos; + + // reset stream for another run + ss.clear(); ss.str(std::string()); + + // take a shortcut for integers + if (is_int) + { + ss.precision(0); + ss << std::fixed << n->value(); + res = std::string(ss.str()); + } + // process floats + else + { + // do we have have too much precision? + if (pos_fract < opt.precision + pos_point) + { ss.precision((int)(pos_fract - pos_point)); } + else { ss.precision(opt.precision); } + // round value again + ss << std::fixed << n->value(); + res = std::string(ss.str()); + // maybe we truncated up to decimal point + size_t pos = res.find_last_not_of("0"); + // handle case where we have a "0" + if (pos == std::string::npos) { + res = "0.0"; + } else { + bool at_dec_point = res[pos] == '.' || + res[pos] == ','; + // don't leave a blank point + if (at_dec_point) ++ pos; + res.resize (pos + 1); + } + } + + // some final cosmetics + if (res == "0.0") res = "0"; + else if (res == "") res = "0"; + else if (res == "-0") res = "0"; + else if (res == "-0.0") res = "0"; + else if (opt.output_style == COMPRESSED) + { + // check if handling negative nr + size_t off = res[0] == '-' ? 1 : 0; + // remove leading zero from floating point in compressed mode + if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + } + + // add unit now + res += n->unit(); + + // output the final token + append_token(res, n); + } + + // helper function for serializing colors + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + void Inspect::operator()(Color_Ptr c) + { + // output the final token + std::stringstream ss; + + // original color name + // maybe an unknown token + std::string name = c->disp(); + + if (opt.in_selector && name != "") { + append_token(name, c); + return; + } + + // resolved color + std::string res_name = name; + + double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); + double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); + double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); + double a = cap_channel<1> (c->a()); + + // get color from given name (if one was given at all) + if (name != "" && name_to_color(name)) { + Color_Ptr_Const n = name_to_color(name); + r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); + g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); + b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); + a = cap_channel<1> (n->a()); + } + // otherwise get the possible resolved color name + else { + double numval = r * 0x10000 + g * 0x100 + b; + if (color_to_name(numval)) + res_name = color_to_name(numval); + } + + std::stringstream hexlet; + // dart sass compressed all colors in regular css always + // ruby sass and libsass does it only when not delayed + // since color math is going to be removed, this can go too + bool compressed = opt.output_style == COMPRESSED; + hexlet << '#' << std::setw(1) << std::setfill('0'); + // create a short color hexlet if there is any need for it + if (compressed && is_color_doublet(r, g, b) && a == 1) { + hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); + } else { + hexlet << std::hex << std::setw(2) << static_cast(r); + hexlet << std::hex << std::setw(2) << static_cast(g); + hexlet << std::hex << std::setw(2) << static_cast(b); + } + + if (compressed && !c->is_delayed()) name = ""; + if (opt.output_style == INSPECT && a >= 1) { + append_token(hexlet.str(), c); + return; + } + + // retain the originally specified color definition if unchanged + if (name != "") { + ss << name; + } + else if (a >= 1) { + if (res_name != "") { + if (compressed && hexlet.str().size() < res_name.size()) { + ss << hexlet.str(); + } else { + ss << res_name; + } + } + else { + ss << hexlet.str(); + } + } + else { + ss << "rgba("; + ss << static_cast(r) << ","; + if (!compressed) ss << " "; + ss << static_cast(g) << ","; + if (!compressed) ss << " "; + ss << static_cast(b) << ","; + if (!compressed) ss << " "; + ss << a << ')'; + } + + append_token(ss.str(), c); + + } + + void Inspect::operator()(Boolean_Ptr b) + { + // output the final token + append_token(b->value() ? "true" : "false", b); + } + + void Inspect::operator()(String_Schema_Ptr ss) + { + // Evaluation should turn these into String_Constants, + // so this method is only for inspection purposes. + for (size_t i = 0, L = ss->length(); i < L; ++i) { + if ((*ss)[i]->is_interpolant()) append_string("#{"); + (*ss)[i]->perform(this); + if ((*ss)[i]->is_interpolant()) append_string("}"); + } + } + + void Inspect::operator()(String_Constant_Ptr s) + { + append_token(s->value(), s); + } + + void Inspect::operator()(String_Quoted_Ptr s) + { + if (const char q = s->quote_mark()) { + append_token(quote(s->value(), q), s); + } else { + append_token(s->value(), s); + } + } + + void Inspect::operator()(Custom_Error_Ptr e) + { + append_token(e->message(), e); + } + + void Inspect::operator()(Custom_Warning_Ptr w) + { + append_token(w->message(), w); + } + + void Inspect::operator()(Supports_Operator_Ptr so) + { + + if (so->needs_parens(so->left())) append_string("("); + so->left()->perform(this); + if (so->needs_parens(so->left())) append_string(")"); + + if (so->operand() == Supports_Operator::AND) { + append_mandatory_space(); + append_token("and", so); + append_mandatory_space(); + } else if (so->operand() == Supports_Operator::OR) { + append_mandatory_space(); + append_token("or", so); + append_mandatory_space(); + } + + if (so->needs_parens(so->right())) append_string("("); + so->right()->perform(this); + if (so->needs_parens(so->right())) append_string(")"); + } + + void Inspect::operator()(Supports_Negation_Ptr sn) + { + append_token("not", sn); + append_mandatory_space(); + if (sn->needs_parens(sn->condition())) append_string("("); + sn->condition()->perform(this); + if (sn->needs_parens(sn->condition())) append_string(")"); + } + + void Inspect::operator()(Supports_Declaration_Ptr sd) + { + append_string("("); + sd->feature()->perform(this); + append_string(": "); + sd->value()->perform(this); + append_string(")"); + } + + void Inspect::operator()(Supports_Interpolation_Ptr sd) + { + sd->value()->perform(this); + } + + void Inspect::operator()(Media_Query_Ptr mq) + { + size_t i = 0; + if (mq->media_type()) { + if (mq->is_negated()) append_string("not "); + else if (mq->is_restricted()) append_string("only "); + mq->media_type()->perform(this); + } + else { + (*mq)[i++]->perform(this); + } + for (size_t L = mq->length(); i < L; ++i) { + append_string(" and "); + (*mq)[i]->perform(this); + } + } + + void Inspect::operator()(Media_Query_Expression_Ptr mqe) + { + if (mqe->is_interpolated()) { + mqe->feature()->perform(this); + } + else { + append_string("("); + mqe->feature()->perform(this); + if (mqe->value()) { + append_string(": "); // verified + mqe->value()->perform(this); + } + append_string(")"); + } + } + + void Inspect::operator()(At_Root_Query_Ptr ae) + { + if (ae->feature()) { + append_string("("); + ae->feature()->perform(this); + if (ae->value()) { + append_colon_separator(); + ae->value()->perform(this); + } + append_string(")"); + } + } + + void Inspect::operator()(Function_Ptr f) + { + append_token("get-function", f); + append_string("("); + append_string(quote(f->name())); + append_string(")"); + } + + void Inspect::operator()(Null_Ptr n) + { + // output the final token + append_token("null", n); + } + + // parameters and arguments + void Inspect::operator()(Parameter_Ptr p) + { + append_token(p->name(), p); + if (p->default_value()) { + append_colon_separator(); + p->default_value()->perform(this); + } + else if (p->is_rest_parameter()) { + append_string("..."); + } + } + + void Inspect::operator()(Parameters_Ptr p) + { + append_string("("); + if (!p->empty()) { + (*p)[0]->perform(this); + for (size_t i = 1, L = p->length(); i < L; ++i) { + append_comma_separator(); + (*p)[i]->perform(this); + } + } + append_string(")"); + } + + void Inspect::operator()(Argument_Ptr a) + { + if (!a->name().empty()) { + append_token(a->name(), a); + append_colon_separator(); + } + if (!a->value()) return; + // Special case: argument nulls can be ignored + if (a->value()->concrete_type() == Expression::NULL_VAL) { + return; + } + if (a->value()->concrete_type() == Expression::STRING) { + String_Constant_Ptr s = Cast(a->value()); + if (s) s->perform(this); + } else { + a->value()->perform(this); + } + if (a->is_rest_argument()) { + append_string("..."); + } + } + + void Inspect::operator()(Arguments_Ptr a) + { + append_string("("); + if (!a->empty()) { + (*a)[0]->perform(this); + for (size_t i = 1, L = a->length(); i < L; ++i) { + append_string(", "); // verified + // Sass Bug? append_comma_separator(); + (*a)[i]->perform(this); + } + } + append_string(")"); + } + + void Inspect::operator()(Selector_Schema_Ptr s) + { + opt.in_selector = true; + s->contents()->perform(this); + opt.in_selector = false; + } + + void Inspect::operator()(Parent_Selector_Ptr p) + { + if (p->is_real_parent_ref()) append_string("&"); + } + + void Inspect::operator()(Placeholder_Selector_Ptr s) + { + append_token(s->name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + + } + + void Inspect::operator()(Element_Selector_Ptr s) + { + append_token(s->ns_name(), s); + } + + void Inspect::operator()(Class_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Id_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Attribute_Selector_Ptr s) + { + append_string("["); + add_open_mapping(s); + append_token(s->ns_name(), s); + if (!s->matcher().empty()) { + append_string(s->matcher()); + if (s->value() && *s->value()) { + s->value()->perform(this); + } + } + add_close_mapping(s); + if (s->modifier() != 0) { + append_mandatory_space(); + append_char(s->modifier()); + } + append_string("]"); + } + + void Inspect::operator()(Pseudo_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->expression()) { + append_string("("); + s->expression()->perform(this); + append_string(")"); + } + } + + void Inspect::operator()(Wrapped_Selector_Ptr s) + { + if (s->name() == " ") { + append_string(""); + } else { + bool was = in_wrapped; + in_wrapped = true; + append_token(s->name(), s); + append_string("("); + bool was_comma_array = in_comma_array; + in_comma_array = false; + s->selector()->perform(this); + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; + } + } + + void Inspect::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0, L = s->length(); i < L; ++i) { + (*s)[i]->perform(this); + } + if (s->has_line_break()) { + if (output_style() != COMPACT) { + append_optional_linefeed(); + } + } + } + + void Inspect::operator()(Complex_Selector_Ptr c) + { + Compound_Selector_Obj head = c->head(); + Complex_Selector_Obj tail = c->tail(); + Complex_Selector::Combinator comb = c->combinator(); + + if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { + if (tail) tail->perform(this); + return; + } + + if (c->has_line_feed()) { + if (!(c->has_parent_ref())) { + append_optional_linefeed(); + append_indentation(); + } + } + + if (head && head->length() != 0) head->perform(this); + bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); + bool is_tail = head && !head->is_empty_reference() && tail; + if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; + + switch (comb) { + case Complex_Selector::ANCESTOR_OF: + if (is_tail) append_mandatory_space(); + break; + case Complex_Selector::PARENT_OF: + append_optional_space(); + append_string(">"); + append_optional_space(); + break; + case Complex_Selector::ADJACENT_TO: + append_optional_space(); + append_string("+"); + append_optional_space(); + break; + case Complex_Selector::REFERENCE: + append_mandatory_space(); + append_string("/"); + if (c->reference()) c->reference()->perform(this); + append_string("/"); + append_mandatory_space(); + break; + case Complex_Selector::PRECEDES: + if (is_empty) append_optional_space(); + else append_mandatory_space(); + append_string("~"); + if (tail) append_mandatory_space(); + else append_optional_space(); + break; + default: break; + } + if (tail && comb != Complex_Selector::ANCESTOR_OF) { + if (c->has_line_break()) append_optional_linefeed(); + } + if (tail) tail->perform(this); + if (!tail && c->has_line_break()) { + if (output_style() == COMPACT) { + append_mandatory_space(); + } + } + } + + void Inspect::operator()(Selector_List_Ptr g) + { + + if (g->empty()) { + if (output_style() == TO_SASS) { + append_token("()", g); + } + return; + } + + + bool was_comma_array = in_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { + append_string("("); + } + else if (!in_declaration && in_comma_array) { + append_string("("); + } + + if (in_declaration) in_comma_array = true; + + for (size_t i = 0, L = g->length(); i < L; ++i) { + if (!in_wrapped && i == 0) append_indentation(); + if ((*g)[i] == 0) continue; + schedule_mapping(g->at(i)->last()); + // add_open_mapping((*g)[i]->last()); + (*g)[i]->perform(this); + // add_close_mapping((*g)[i]->last()); + if (i < L - 1) { + scheduled_space = 0; + append_comma_separator(); + } + } + + in_comma_array = was_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { + append_string(",)"); + } + else if (!in_declaration && in_comma_array) { + append_string(")"); + } + + } + + void Inspect::fallback_impl(AST_Node_Ptr n) + { + } + +} diff --git a/src/inspect.hpp b/src/inspect.hpp new file mode 100644 index 000000000..c36790b80 --- /dev/null +++ b/src/inspect.hpp @@ -0,0 +1,103 @@ +#ifndef SASS_INSPECT_H +#define SASS_INSPECT_H + +#include "position.hpp" +#include "operation.hpp" +#include "emitter.hpp" + +namespace Sass { + class Context; + + class Inspect : public Operation_CRTP, public Emitter { + protected: + // import all the class-specific methods and override as desired + using Operation_CRTP::operator(); + + void fallback_impl(AST_Node_Ptr n); + + public: + + Inspect(const Emitter& emi); + virtual ~Inspect(); + + // statements + virtual void operator()(Block_Ptr); + virtual void operator()(Ruleset_Ptr); + virtual void operator()(Bubble_Ptr); + virtual void operator()(Supports_Block_Ptr); + virtual void operator()(Media_Block_Ptr); + virtual void operator()(At_Root_Block_Ptr); + virtual void operator()(Directive_Ptr); + virtual void operator()(Keyframe_Rule_Ptr); + virtual void operator()(Declaration_Ptr); + virtual void operator()(Assignment_Ptr); + virtual void operator()(Import_Ptr); + virtual void operator()(Import_Stub_Ptr); + virtual void operator()(Warning_Ptr); + virtual void operator()(Error_Ptr); + virtual void operator()(Debug_Ptr); + virtual void operator()(Comment_Ptr); + virtual void operator()(If_Ptr); + virtual void operator()(For_Ptr); + virtual void operator()(Each_Ptr); + virtual void operator()(While_Ptr); + virtual void operator()(Return_Ptr); + virtual void operator()(Extension_Ptr); + virtual void operator()(Definition_Ptr); + virtual void operator()(Mixin_Call_Ptr); + virtual void operator()(Content_Ptr); + // expressions + virtual void operator()(Map_Ptr); + virtual void operator()(Function_Ptr); + virtual void operator()(List_Ptr); + virtual void operator()(Binary_Expression_Ptr); + virtual void operator()(Unary_Expression_Ptr); + virtual void operator()(Function_Call_Ptr); + virtual void operator()(Function_Call_Schema_Ptr); + // virtual void operator()(Custom_Warning_Ptr); + // virtual void operator()(Custom_Error_Ptr); + virtual void operator()(Variable_Ptr); + virtual void operator()(Number_Ptr); + virtual void operator()(Color_Ptr); + virtual void operator()(Boolean_Ptr); + virtual void operator()(String_Schema_Ptr); + virtual void operator()(String_Constant_Ptr); + virtual void operator()(String_Quoted_Ptr); + virtual void operator()(Custom_Error_Ptr); + virtual void operator()(Custom_Warning_Ptr); + virtual void operator()(Supports_Operator_Ptr); + virtual void operator()(Supports_Negation_Ptr); + virtual void operator()(Supports_Declaration_Ptr); + virtual void operator()(Supports_Interpolation_Ptr); + virtual void operator()(Media_Query_Ptr); + virtual void operator()(Media_Query_Expression_Ptr); + virtual void operator()(At_Root_Query_Ptr); + virtual void operator()(Null_Ptr); + virtual void operator()(Parent_Selector_Ptr p); + // parameters and arguments + virtual void operator()(Parameter_Ptr); + virtual void operator()(Parameters_Ptr); + virtual void operator()(Argument_Ptr); + virtual void operator()(Arguments_Ptr); + // selectors + virtual void operator()(Selector_Schema_Ptr); + virtual void operator()(Placeholder_Selector_Ptr); + virtual void operator()(Element_Selector_Ptr); + virtual void operator()(Class_Selector_Ptr); + virtual void operator()(Id_Selector_Ptr); + virtual void operator()(Attribute_Selector_Ptr); + virtual void operator()(Pseudo_Selector_Ptr); + virtual void operator()(Wrapped_Selector_Ptr); + virtual void operator()(Compound_Selector_Ptr); + virtual void operator()(Complex_Selector_Ptr); + virtual void operator()(Selector_List_Ptr); + + virtual std::string lbracket(List_Ptr); + virtual std::string rbracket(List_Ptr); + + // template + // void fallback(U x) { fallback_impl(reinterpret_cast(x)); } + }; + +} +#endif diff --git a/src/json.cpp b/src/json.cpp new file mode 100644 index 000000000..8f433f5d0 --- /dev/null +++ b/src/json.cpp @@ -0,0 +1,1436 @@ +/* + Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#include "json.hpp" + +// include utf8 library used by libsass +// ToDo: replace internal json utf8 code +#include "utf8.h" + +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +#ifdef snprintf +#undef snprintf +#endif +extern "C" int snprintf(char *, size_t, const char *, ...); +#endif + +#define out_of_memory() do { \ + fprintf(stderr, "Out of memory.\n"); \ + exit(EXIT_FAILURE); \ + } while (0) + +/* Sadly, strdup is not portable. */ +static char *json_strdup(const char *str) +{ + char *ret = (char*) malloc(strlen(str) + 1); + if (ret == NULL) + out_of_memory(); + strcpy(ret, str); + return ret; +} + +/* String buffer */ + +typedef struct +{ + char *cur; + char *end; + char *start; +} SB; + +static void sb_init(SB *sb) +{ + sb->start = (char*) malloc(17); + if (sb->start == NULL) + out_of_memory(); + sb->cur = sb->start; + sb->end = sb->start + 16; +} + +/* sb and need may be evaluated multiple times. */ +#define sb_need(sb, need) do { \ + if ((sb)->end - (sb)->cur < (need)) \ + sb_grow(sb, need); \ + } while (0) + +static void sb_grow(SB *sb, int need) +{ + size_t length = sb->cur - sb->start; + size_t alloc = sb->end - sb->start; + + do { + alloc *= 2; + } while (alloc < length + need); + + sb->start = (char*) realloc(sb->start, alloc + 1); + if (sb->start == NULL) + out_of_memory(); + sb->cur = sb->start + length; + sb->end = sb->start + alloc; +} + +static void sb_put(SB *sb, const char *bytes, int count) +{ + sb_need(sb, count); + memcpy(sb->cur, bytes, count); + sb->cur += count; +} + +#define sb_putc(sb, c) do { \ + if ((sb)->cur >= (sb)->end) \ + sb_grow(sb, 1); \ + *(sb)->cur++ = (c); \ + } while (0) + +static void sb_puts(SB *sb, const char *str) +{ + sb_put(sb, str, (int)strlen(str)); +} + +static char *sb_finish(SB *sb) +{ + *sb->cur = 0; + assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start)); + return sb->start; +} + +static void sb_free(SB *sb) +{ + free(sb->start); +} + +/* + * Unicode helper functions + * + * These are taken from the ccan/charset module and customized a bit. + * Putting them here means the compiler can (choose to) inline them, + * and it keeps ccan/json from having a dependency. + * + * We use uint32_t Type for Unicode codepoints. + * We need our own because wchar_t might be 16 bits. + */ + +/* + * Validate a single UTF-8 character starting at @s. + * The string must be null-terminated. + * + * If it's valid, return its length (1 thru 4). + * If it's invalid or clipped, return 0. + * + * This function implements the syntax given in RFC3629, which is + * the same as that given in The Unicode Standard, Version 6.0. + * + * It has the following properties: + * + * * All codepoints U+0000..U+10FFFF may be encoded, + * except for U+D800..U+DFFF, which are reserved + * for UTF-16 surrogate pair encoding. + * * UTF-8 byte sequences longer than 4 bytes are not permitted, + * as they exceed the range of Unicode. + * * The sixty-six Unicode "non-characters" are permitted + * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF). + */ +static int utf8_validate_cz(const char *s) +{ + unsigned char c = *s++; + + if (c <= 0x7F) { /* 00..7F */ + return 1; + } else if (c <= 0xC1) { /* 80..C1 */ + /* Disallow overlong 2-byte sequence. */ + return 0; + } else if (c <= 0xDF) { /* C2..DF */ + /* Make sure subsequent byte is in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 2; + } else if (c <= 0xEF) { /* E0..EF */ + /* Disallow overlong 3-byte sequence. */ + if (c == 0xE0 && (unsigned char)*s < 0xA0) + return 0; + + /* Disallow U+D800..U+DFFF. */ + if (c == 0xED && (unsigned char)*s > 0x9F) + return 0; + + /* Make sure subsequent bytes are in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 3; + } else if (c <= 0xF4) { /* F0..F4 */ + /* Disallow overlong 4-byte sequence. */ + if (c == 0xF0 && (unsigned char)*s < 0x90) + return 0; + + /* Disallow codepoints beyond U+10FFFF. */ + if (c == 0xF4 && (unsigned char)*s > 0x8F) + return 0; + + /* Make sure subsequent bytes are in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 4; + } else { /* F5..FF */ + return 0; + } +} + +/* Validate a null-terminated UTF-8 string. */ +static bool utf8_validate(const char *s) +{ + int len; + + for (; *s != 0; s += len) { + len = utf8_validate_cz(s); + if (len == 0) + return false; + } + + return true; +} + +/* + * Read a single UTF-8 character starting at @s, + * returning the length, in bytes, of the character read. + * + * This function assumes input is valid UTF-8, + * and that there are enough characters in front of @s. + */ +static int utf8_read_char(const char *s, uint32_t *out) +{ + const unsigned char *c = (const unsigned char*) s; + + assert(utf8_validate_cz(s)); + + if (c[0] <= 0x7F) { + /* 00..7F */ + *out = c[0]; + return 1; + } else if (c[0] <= 0xDF) { + /* C2..DF (unless input is invalid) */ + *out = ((uint32_t)c[0] & 0x1F) << 6 | + ((uint32_t)c[1] & 0x3F); + return 2; + } else if (c[0] <= 0xEF) { + /* E0..EF */ + *out = ((uint32_t)c[0] & 0xF) << 12 | + ((uint32_t)c[1] & 0x3F) << 6 | + ((uint32_t)c[2] & 0x3F); + return 3; + } else { + /* F0..F4 (unless input is invalid) */ + *out = ((uint32_t)c[0] & 0x7) << 18 | + ((uint32_t)c[1] & 0x3F) << 12 | + ((uint32_t)c[2] & 0x3F) << 6 | + ((uint32_t)c[3] & 0x3F); + return 4; + } +} + +/* + * Write a single UTF-8 character to @s, + * returning the length, in bytes, of the character written. + * + * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF. + * + * This function will write up to 4 bytes to @out. + */ +static int utf8_write_char(uint32_t unicode, char *out) +{ + unsigned char *o = (unsigned char*) out; + + assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF)); + + if (unicode <= 0x7F) { + /* U+0000..U+007F */ + *o++ = unicode; + return 1; + } else if (unicode <= 0x7FF) { + /* U+0080..U+07FF */ + *o++ = 0xC0 | unicode >> 6; + *o++ = 0x80 | (unicode & 0x3F); + return 2; + } else if (unicode <= 0xFFFF) { + /* U+0800..U+FFFF */ + *o++ = 0xE0 | unicode >> 12; + *o++ = 0x80 | (unicode >> 6 & 0x3F); + *o++ = 0x80 | (unicode & 0x3F); + return 3; + } else { + /* U+10000..U+10FFFF */ + *o++ = 0xF0 | unicode >> 18; + *o++ = 0x80 | (unicode >> 12 & 0x3F); + *o++ = 0x80 | (unicode >> 6 & 0x3F); + *o++ = 0x80 | (unicode & 0x3F); + return 4; + } +} + +/* + * Compute the Unicode codepoint of a UTF-16 surrogate pair. + * + * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF. + * If they aren't, this function returns false. + */ +static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uint32_t *unicode) +{ + if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) { + *unicode = 0x10000 + ((((uint32_t)uc & 0x3FF) << 10) | (lc & 0x3FF)); + return true; + } else { + return false; + } +} + +/* + * Construct a UTF-16 surrogate pair given a Unicode codepoint. + * + * @unicode must be U+10000..U+10FFFF. + */ +static void to_surrogate_pair(uint32_t unicode, uint16_t *uc, uint16_t *lc) +{ + uint32_t n; + + assert(unicode >= 0x10000 && unicode <= 0x10FFFF); + + n = unicode - 0x10000; + *uc = ((n >> 10) & 0x3FF) | 0xD800; + *lc = (n & 0x3FF) | 0xDC00; +} + +static bool is_space (const char *c); +static bool is_digit (const char *c); +static bool parse_value (const char **sp, JsonNode **out); +static bool parse_string (const char **sp, char **out); +static bool parse_number (const char **sp, double *out); +static bool parse_array (const char **sp, JsonNode **out); +static bool parse_object (const char **sp, JsonNode **out); +static bool parse_hex16 (const char **sp, uint16_t *out); + +static bool expect_literal (const char **sp, const char *str); +static void skip_space (const char **sp); + +static void emit_value (SB *out, const JsonNode *node); +static void emit_value_indented (SB *out, const JsonNode *node, const char *space, int indent_level); +static void emit_string (SB *out, const char *str); +static void emit_number (SB *out, double num); +static void emit_array (SB *out, const JsonNode *array); +static void emit_array_indented (SB *out, const JsonNode *array, const char *space, int indent_level); +static void emit_object (SB *out, const JsonNode *object); +static void emit_object_indented (SB *out, const JsonNode *object, const char *space, int indent_level); + +static int write_hex16(char *out, uint16_t val); + +static JsonNode *mknode(JsonTag tag); +static void append_node(JsonNode *parent, JsonNode *child); +static void prepend_node(JsonNode *parent, JsonNode *child); +static void append_member(JsonNode *object, char *key, JsonNode *value); + +/* Assertion-friendly validity checks */ +static bool tag_is_valid(unsigned int tag); +static bool number_is_valid(const char *num); + +JsonNode *json_decode(const char *json) +{ + const char *s = json; + JsonNode *ret; + + skip_space(&s); + if (!parse_value(&s, &ret)) + return NULL; + + skip_space(&s); + if (*s != 0) { + json_delete(ret); + return NULL; + } + + return ret; +} + +char *json_encode(const JsonNode *node) +{ + return json_stringify(node, NULL); +} + +char *json_encode_string(const char *str) +{ + SB sb; + sb_init(&sb); + + try { + emit_string(&sb, str); + } + catch (std::exception) { + sb_free(&sb); + throw; + } + + return sb_finish(&sb); +} + +char *json_stringify(const JsonNode *node, const char *space) +{ + SB sb; + sb_init(&sb); + + try { + if (space != NULL) + emit_value_indented(&sb, node, space, 0); + else + emit_value(&sb, node); + } + catch (std::exception) { + sb_free(&sb); + throw; + } + + return sb_finish(&sb); +} + +void json_delete(JsonNode *node) +{ + if (node != NULL) { + json_remove_from_parent(node); + + switch (node->tag) { + case JSON_STRING: + free(node->string_); + break; + case JSON_ARRAY: + case JSON_OBJECT: + { + JsonNode *child, *next; + for (child = node->children.head; child != NULL; child = next) { + next = child->next; + json_delete(child); + } + break; + } + default:; + } + + free(node); + } +} + +bool json_validate(const char *json) +{ + const char *s = json; + + skip_space(&s); + if (!parse_value(&s, NULL)) + return false; + + skip_space(&s); + if (*s != 0) + return false; + + return true; +} + +JsonNode *json_find_element(JsonNode *array, int index) +{ + JsonNode *element; + int i = 0; + + if (array == NULL || array->tag != JSON_ARRAY) + return NULL; + + json_foreach(element, array) { + if (i == index) + return element; + i++; + } + + return NULL; +} + +JsonNode *json_find_member(JsonNode *object, const char *name) +{ + JsonNode *member; + + if (object == NULL || object->tag != JSON_OBJECT) + return NULL; + + json_foreach(member, object) + if (strcmp(member->key, name) == 0) + return member; + + return NULL; +} + +JsonNode *json_first_child(const JsonNode *node) +{ + if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT)) + return node->children.head; + return NULL; +} + +static JsonNode *mknode(JsonTag tag) +{ + JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode)); + if (ret == NULL) + out_of_memory(); + ret->tag = tag; + return ret; +} + +JsonNode *json_mknull(void) +{ + return mknode(JSON_NULL); +} + +JsonNode *json_mkbool(bool b) +{ + JsonNode *ret = mknode(JSON_BOOL); + ret->bool_ = b; + return ret; +} + +static JsonNode *mkstring(char *s) +{ + JsonNode *ret = mknode(JSON_STRING); + ret->string_ = s; + return ret; +} + +JsonNode *json_mkstring(const char *s) +{ + return mkstring(json_strdup(s)); +} + +JsonNode *json_mknumber(double n) +{ + JsonNode *node = mknode(JSON_NUMBER); + node->number_ = n; + return node; +} + +JsonNode *json_mkarray(void) +{ + return mknode(JSON_ARRAY); +} + +JsonNode *json_mkobject(void) +{ + return mknode(JSON_OBJECT); +} + +static void append_node(JsonNode *parent, JsonNode *child) +{ + if (child != NULL && parent != NULL) { + child->parent = parent; + child->prev = parent->children.tail; + child->next = NULL; + + if (parent->children.tail != NULL) + parent->children.tail->next = child; + else + parent->children.head = child; + parent->children.tail = child; + } +} + +static void prepend_node(JsonNode *parent, JsonNode *child) +{ + if (child != NULL && parent != NULL) { + child->parent = parent; + child->prev = NULL; + child->next = parent->children.head; + + if (parent->children.head != NULL) + parent->children.head->prev = child; + else + parent->children.tail = child; + parent->children.head = child; + } +} + +static void append_member(JsonNode *object, char *key, JsonNode *value) +{ + if (value != NULL && object != NULL) { + value->key = key; + append_node(object, value); + } +} + +void json_append_element(JsonNode *array, JsonNode *element) +{ + if (array != NULL && element !=NULL) { + assert(array->tag == JSON_ARRAY); + assert(element->parent == NULL); + + append_node(array, element); + } +} + +void json_prepend_element(JsonNode *array, JsonNode *element) +{ + assert(array->tag == JSON_ARRAY); + assert(element->parent == NULL); + + prepend_node(array, element); +} + +void json_append_member(JsonNode *object, const char *key, JsonNode *value) +{ + if (object != NULL && key != NULL && value != NULL) { + assert(object->tag == JSON_OBJECT); + assert(value->parent == NULL); + + append_member(object, json_strdup(key), value); + } +} + +void json_prepend_member(JsonNode *object, const char *key, JsonNode *value) +{ + if (object != NULL && key != NULL && value != NULL) { + assert(object->tag == JSON_OBJECT); + assert(value->parent == NULL); + + value->key = json_strdup(key); + prepend_node(object, value); + } +} + +void json_remove_from_parent(JsonNode *node) +{ + if (node != NULL) { + JsonNode *parent = node->parent; + + if (parent != NULL) { + if (node->prev != NULL) + node->prev->next = node->next; + else + parent->children.head = node->next; + + if (node->next != NULL) + node->next->prev = node->prev; + else + parent->children.tail = node->prev; + + free(node->key); + + node->parent = NULL; + node->prev = node->next = NULL; + node->key = NULL; + } + } +} + +static bool parse_value(const char **sp, JsonNode **out) +{ + const char *s = *sp; + + switch (*s) { + case 'n': + if (expect_literal(&s, "null")) { + if (out) + *out = json_mknull(); + *sp = s; + return true; + } + return false; + + case 'f': + if (expect_literal(&s, "false")) { + if (out) + *out = json_mkbool(false); + *sp = s; + return true; + } + return false; + + case 't': + if (expect_literal(&s, "true")) { + if (out) + *out = json_mkbool(true); + *sp = s; + return true; + } + return false; + + case '"': { + char *str = NULL; + if (parse_string(&s, out ? &str : NULL)) { + if (out) + *out = mkstring(str); + *sp = s; + return true; + } + return false; + } + + case '[': + if (parse_array(&s, out)) { + *sp = s; + return true; + } + return false; + + case '{': + if (parse_object(&s, out)) { + *sp = s; + return true; + } + return false; + + default: { + double num; + if (parse_number(&s, out ? &num : NULL)) { + if (out) + *out = json_mknumber(num); + *sp = s; + return true; + } + return false; + } + } +} + +static bool parse_array(const char **sp, JsonNode **out) +{ + const char *s = *sp; + JsonNode *ret = out ? json_mkarray() : NULL; + JsonNode *element = NULL; + + if (*s++ != '[') + goto failure; + skip_space(&s); + + if (*s == ']') { + s++; + goto success; + } + + for (;;) { + if (!parse_value(&s, out ? &element : NULL)) + goto failure; + skip_space(&s); + + if (out) + json_append_element(ret, element); + + if (*s == ']') { + s++; + goto success; + } + + if (*s++ != ',') + goto failure; + skip_space(&s); + } + +success: + *sp = s; + if (out) + *out = ret; + return true; + +failure: + json_delete(ret); + return false; +} + +static bool parse_object(const char **sp, JsonNode **out) +{ + const char *s = *sp; + JsonNode *ret = out ? json_mkobject() : NULL; + char *key = NULL; + JsonNode *value = NULL; + + if (*s++ != '{') + goto failure; + skip_space(&s); + + if (*s == '}') { + s++; + goto success; + } + + for (;;) { + if (!parse_string(&s, out ? &key : NULL)) + goto failure; + skip_space(&s); + + if (*s++ != ':') + goto failure_free_key; + skip_space(&s); + + if (!parse_value(&s, out ? &value : NULL)) + goto failure_free_key; + skip_space(&s); + + if (out) + append_member(ret, key, value); + + if (*s == '}') { + s++; + goto success; + } + + if (*s++ != ',') + goto failure; + skip_space(&s); + } + +success: + *sp = s; + if (out) + *out = ret; + return true; + +failure_free_key: + if (out) + free(key); +failure: + json_delete(ret); + return false; +} + +bool parse_string(const char **sp, char **out) +{ + const char *s = *sp; + SB sb = { 0, 0, 0 }; + char throwaway_buffer[4]; + /* enough space for a UTF-8 character */ + char *b; + + if (*s++ != '"') + return false; + + if (out) { + sb_init(&sb); + sb_need(&sb, 4); + b = sb.cur; + } else { + b = throwaway_buffer; + } + + while (*s != '"') { + unsigned char c = *s++; + + /* Parse next character, and write it to b. */ + if (c == '\\') { + c = *s++; + switch (c) { + case '"': + case '\\': + case '/': + *b++ = c; + break; + case 'b': + *b++ = '\b'; + break; + case 'f': + *b++ = '\f'; + break; + case 'n': + *b++ = '\n'; + break; + case 'r': + *b++ = '\r'; + break; + case 't': + *b++ = '\t'; + break; + case 'u': + { + uint16_t uc, lc; + uint32_t unicode; + + if (!parse_hex16(&s, &uc)) + goto failed; + + if (uc >= 0xD800 && uc <= 0xDFFF) { + /* Handle UTF-16 surrogate pair. */ + if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc)) + goto failed; /* Incomplete surrogate pair. */ + if (!from_surrogate_pair(uc, lc, &unicode)) + goto failed; /* Invalid surrogate pair. */ + } else if (uc == 0) { + /* Disallow "\u0000". */ + goto failed; + } else { + unicode = uc; + } + + b += utf8_write_char(unicode, b); + break; + } + default: + /* Invalid escape */ + goto failed; + } + } else if (c <= 0x1F) { + /* Control characters are not allowed in string literals. */ + goto failed; + } else { + /* Validate and echo a UTF-8 character. */ + int len; + + s--; + len = utf8_validate_cz(s); + if (len == 0) + goto failed; /* Invalid UTF-8 character. */ + + while (len--) + *b++ = *s++; + } + + /* + * Update sb to know about the new bytes, + * and set up b to write another character. + */ + if (out) { + sb.cur = b; + sb_need(&sb, 4); + b = sb.cur; + } else { + b = throwaway_buffer; + } + } + s++; + + if (out) + *out = sb_finish(&sb); + *sp = s; + return true; + +failed: + if (out) + sb_free(&sb); + return false; +} + +bool is_space(const char *c) { + return ((*c) == '\t' || (*c) == '\n' || (*c) == '\r' || (*c) == ' '); +} + +bool is_digit(const char *c){ + return ((*c) >= '0' && (*c) <= '9'); +} + +/* + * The JSON spec says that a number shall follow this precise pattern + * (spaces and quotes added for readability): + * '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)? + * + * However, some JSON parsers are more liberal. For instance, PHP accepts + * '.5' and '1.'. JSON.parse accepts '+3'. + * + * This function takes the strict approach. + */ +bool parse_number(const char **sp, double *out) +{ + const char *s = *sp; + + /* '-'? */ + if (*s == '-') + s++; + + /* (0 | [1-9][0-9]*) */ + if (*s == '0') { + s++; + } else { + if (!is_digit(s)) + return false; + do { + s++; + } while (is_digit(s)); + } + + /* ('.' [0-9]+)? */ + if (*s == '.') { + s++; + if (!is_digit(s)) + return false; + do { + s++; + } while (is_digit(s)); + } + + /* ([Ee] [+-]? [0-9]+)? */ + if (*s == 'E' || *s == 'e') { + s++; + if (*s == '+' || *s == '-') + s++; + if (!is_digit(s)) + return false; + do { + s++; + } while (is_digit(s)); + } + + if (out) + *out = strtod(*sp, NULL); + + *sp = s; + return true; +} + +static void skip_space(const char **sp) +{ + const char *s = *sp; + while (is_space(s)) + s++; + *sp = s; +} + +static void emit_value(SB *out, const JsonNode *node) +{ + assert(tag_is_valid(node->tag)); + switch (node->tag) { + case JSON_NULL: + sb_puts(out, "null"); + break; + case JSON_BOOL: + sb_puts(out, node->bool_ ? "true" : "false"); + break; + case JSON_STRING: + emit_string(out, node->string_); + break; + case JSON_NUMBER: + emit_number(out, node->number_); + break; + case JSON_ARRAY: + emit_array(out, node); + break; + case JSON_OBJECT: + emit_object(out, node); + break; + default: + assert(false); + } +} + +void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level) +{ + assert(tag_is_valid(node->tag)); + switch (node->tag) { + case JSON_NULL: + sb_puts(out, "null"); + break; + case JSON_BOOL: + sb_puts(out, node->bool_ ? "true" : "false"); + break; + case JSON_STRING: + emit_string(out, node->string_); + break; + case JSON_NUMBER: + emit_number(out, node->number_); + break; + case JSON_ARRAY: + emit_array_indented(out, node, space, indent_level); + break; + case JSON_OBJECT: + emit_object_indented(out, node, space, indent_level); + break; + default: + assert(false); + } +} + +static void emit_array(SB *out, const JsonNode *array) +{ + const JsonNode *element; + + sb_putc(out, '['); + json_foreach(element, array) { + emit_value(out, element); + if (element->next != NULL) + sb_putc(out, ','); + } + sb_putc(out, ']'); +} + +static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level) +{ + const JsonNode *element = array->children.head; + int i; + + if (element == NULL) { + sb_puts(out, "[]"); + return; + } + + sb_puts(out, "[\n"); + while (element != NULL) { + for (i = 0; i < indent_level + 1; i++) + sb_puts(out, space); + emit_value_indented(out, element, space, indent_level + 1); + + element = element->next; + sb_puts(out, element != NULL ? ",\n" : "\n"); + } + for (i = 0; i < indent_level; i++) + sb_puts(out, space); + sb_putc(out, ']'); +} + +static void emit_object(SB *out, const JsonNode *object) +{ + const JsonNode *member; + + sb_putc(out, '{'); + json_foreach(member, object) { + emit_string(out, member->key); + sb_putc(out, ':'); + emit_value(out, member); + if (member->next != NULL) + sb_putc(out, ','); + } + sb_putc(out, '}'); +} + +static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level) +{ + const JsonNode *member = object->children.head; + int i; + + if (member == NULL) { + sb_puts(out, "{}"); + return; + } + + sb_puts(out, "{\n"); + while (member != NULL) { + for (i = 0; i < indent_level + 1; i++) + sb_puts(out, space); + emit_string(out, member->key); + sb_puts(out, ": "); + emit_value_indented(out, member, space, indent_level + 1); + + member = member->next; + sb_puts(out, member != NULL ? ",\n" : "\n"); + } + for (i = 0; i < indent_level; i++) + sb_puts(out, space); + sb_putc(out, '}'); +} + +void emit_string(SB *out, const char *str) +{ + bool escape_unicode = false; + const char *s = str; + char *b; + +// make assertion catchable +#ifndef NDEBUG + if (!utf8_validate(str)) { + throw utf8::invalid_utf8(0); + } +#endif + + assert(utf8_validate(str)); + + /* + * 14 bytes is enough space to write up to two + * \uXXXX escapes and two quotation marks. + */ + sb_need(out, 14); + b = out->cur; + + *b++ = '"'; + while (*s != 0) { + unsigned char c = *s++; + + /* Encode the next character, and write it to b. */ + switch (c) { + case '"': + *b++ = '\\'; + *b++ = '"'; + break; + case '\\': + *b++ = '\\'; + *b++ = '\\'; + break; + case '\b': + *b++ = '\\'; + *b++ = 'b'; + break; + case '\f': + *b++ = '\\'; + *b++ = 'f'; + break; + case '\n': + *b++ = '\\'; + *b++ = 'n'; + break; + case '\r': + *b++ = '\\'; + *b++ = 'r'; + break; + case '\t': + *b++ = '\\'; + *b++ = 't'; + break; + default: { + int len; + + s--; + len = utf8_validate_cz(s); + + if (len == 0) { + /* + * Handle invalid UTF-8 character gracefully in production + * by writing a replacement character (U+FFFD) + * and skipping a single byte. + * + * This should never happen when assertions are enabled + * due to the assertion at the beginning of this function. + */ + assert(false); + if (escape_unicode) { + strcpy(b, "\\uFFFD"); + b += 6; + } else { + *b++ = 0xEFu; + *b++ = 0xBFu; + *b++ = 0xBDu; + } + s++; + } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) { + /* Encode using \u.... */ + uint32_t unicode; + + s += utf8_read_char(s, &unicode); + + if (unicode <= 0xFFFF) { + *b++ = '\\'; + *b++ = 'u'; + b += write_hex16(b, unicode); + } else { + /* Produce a surrogate pair. */ + uint16_t uc, lc; + assert(unicode <= 0x10FFFF); + to_surrogate_pair(unicode, &uc, &lc); + *b++ = '\\'; + *b++ = 'u'; + b += write_hex16(b, uc); + *b++ = '\\'; + *b++ = 'u'; + b += write_hex16(b, lc); + } + } else { + /* Write the character directly. */ + while (len--) + *b++ = *s++; + } + + break; + } + } + + /* + * Update *out to know about the new bytes, + * and set up b to write another encoded character. + */ + out->cur = b; + sb_need(out, 14); + b = out->cur; + } + *b++ = '"'; + + out->cur = b; +} + +static void emit_number(SB *out, double num) +{ + /* + * This isn't exactly how JavaScript renders numbers, + * but it should produce valid JSON for reasonable numbers + * preserve precision well enough, and avoid some oddities + * like 0.3 -> 0.299999999999999988898 . + */ + char buf[64]; + sprintf(buf, "%.16g", num); + + if (number_is_valid(buf)) + sb_puts(out, buf); + else + sb_puts(out, "null"); +} + +static bool tag_is_valid(unsigned int tag) +{ + return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT); +} + +static bool number_is_valid(const char *num) +{ + return (parse_number(&num, NULL) && *num == '\0'); +} + +static bool expect_literal(const char **sp, const char *str) +{ + const char *s = *sp; + + while (*str != '\0') + if (*s++ != *str++) + return false; + + *sp = s; + return true; +} + +/* + * Parses exactly 4 hex characters (capital or lowercase). + * Fails if any input chars are not [0-9A-Fa-f]. + */ +static bool parse_hex16(const char **sp, uint16_t *out) +{ + const char *s = *sp; + uint16_t ret = 0; + uint16_t i; + uint16_t tmp; + char c; + + for (i = 0; i < 4; i++) { + c = *s++; + if (c >= '0' && c <= '9') + tmp = c - '0'; + else if (c >= 'A' && c <= 'F') + tmp = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + tmp = c - 'a' + 10; + else + return false; + + ret <<= 4; + ret += tmp; + } + + if (out) + *out = ret; + *sp = s; + return true; +} + +/* + * Encodes a 16-bit number into hexadecimal, + * writing exactly 4 hex chars. + */ +static int write_hex16(char *out, uint16_t val) +{ + const char *hex = "0123456789ABCDEF"; + + *out++ = hex[(val >> 12) & 0xF]; + *out++ = hex[(val >> 8) & 0xF]; + *out++ = hex[(val >> 4) & 0xF]; + *out++ = hex[ val & 0xF]; + + return 4; +} + +bool json_check(const JsonNode *node, char errmsg[256]) +{ + #define problem(...) do { \ + if (errmsg != NULL) \ + snprintf(errmsg, 256, __VA_ARGS__); \ + return false; \ + } while (0) + + if (node->key != NULL && !utf8_validate(node->key)) + problem("key contains invalid UTF-8"); + + if (!tag_is_valid(node->tag)) + problem("tag is invalid (%u)", node->tag); + + if (node->tag == JSON_BOOL) { + if (node->bool_ != false && node->bool_ != true) + problem("bool_ is neither false (%d) nor true (%d)", (int)false, (int)true); + } else if (node->tag == JSON_STRING) { + if (node->string_ == NULL) + problem("string_ is NULL"); + if (!utf8_validate(node->string_)) + problem("string_ contains invalid UTF-8"); + } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) { + JsonNode *head = node->children.head; + JsonNode *tail = node->children.tail; + + if (head == NULL || tail == NULL) { + if (head != NULL) + problem("tail is NULL, but head is not"); + if (tail != NULL) + problem("head is NULL, but tail is not"); + } else { + JsonNode *child; + JsonNode *last = NULL; + + if (head->prev != NULL) + problem("First child's prev pointer is not NULL"); + + for (child = head; child != NULL; last = child, child = child->next) { + if (child == node) + problem("node is its own child"); + if (child->next == child) + problem("child->next == child (cycle)"); + if (child->next == head) + problem("child->next == head (cycle)"); + + if (child->parent != node) + problem("child does not point back to parent"); + if (child->next != NULL && child->next->prev != child) + problem("child->next does not point back to child"); + + if (node->tag == JSON_ARRAY && child->key != NULL) + problem("Array element's key is not NULL"); + if (node->tag == JSON_OBJECT && child->key == NULL) + problem("Object member's key is NULL"); + + if (!json_check(child, errmsg)) + return false; + } + + if (last != tail) + problem("tail does not match pointer found by starting at head and following next links"); + } + } + + return true; + + #undef problem +} diff --git a/src/json.hpp b/src/json.hpp new file mode 100644 index 000000000..05b35cd94 --- /dev/null +++ b/src/json.hpp @@ -0,0 +1,117 @@ +/* + Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef CCAN_JSON_H +#define CCAN_JSON_H + +#include +#include + +typedef enum { + JSON_NULL, + JSON_BOOL, + JSON_STRING, + JSON_NUMBER, + JSON_ARRAY, + JSON_OBJECT, +} JsonTag; + +typedef struct JsonNode JsonNode; + +struct JsonNode +{ + /* only if parent is an object or array (NULL otherwise) */ + JsonNode *parent; + JsonNode *prev, *next; + + /* only if parent is an object (NULL otherwise) */ + char *key; /* Must be valid UTF-8. */ + + JsonTag tag; + union { + /* JSON_BOOL */ + bool bool_; + + /* JSON_STRING */ + char *string_; /* Must be valid UTF-8. */ + + /* JSON_NUMBER */ + double number_; + + /* JSON_ARRAY */ + /* JSON_OBJECT */ + struct { + JsonNode *head, *tail; + } children; + }; +}; + +/*** Encoding, decoding, and validation ***/ + +JsonNode *json_decode (const char *json); +char *json_encode (const JsonNode *node); +char *json_encode_string (const char *str); +char *json_stringify (const JsonNode *node, const char *space); +void json_delete (JsonNode *node); + +bool json_validate (const char *json); + +/*** Lookup and traversal ***/ + +JsonNode *json_find_element (JsonNode *array, int index); +JsonNode *json_find_member (JsonNode *object, const char *key); + +JsonNode *json_first_child (const JsonNode *node); + +#define json_foreach(i, object_or_array) \ + for ((i) = json_first_child(object_or_array); \ + (i) != NULL; \ + (i) = (i)->next) + +/*** Construction and manipulation ***/ + +JsonNode *json_mknull(void); +JsonNode *json_mkbool(bool b); +JsonNode *json_mkstring(const char *s); +JsonNode *json_mknumber(double n); +JsonNode *json_mkarray(void); +JsonNode *json_mkobject(void); + +void json_append_element(JsonNode *array, JsonNode *element); +void json_prepend_element(JsonNode *array, JsonNode *element); +void json_append_member(JsonNode *object, const char *key, JsonNode *value); +void json_prepend_member(JsonNode *object, const char *key, JsonNode *value); + +void json_remove_from_parent(JsonNode *node); + +/*** Debugging ***/ + +/* + * Look for structure and encoding problems in a JsonNode or its descendents. + * + * If a problem is detected, return false, writing a description of the problem + * to errmsg (unless errmsg is NULL). + */ +bool json_check(const JsonNode *node, char errmsg[256]); + +#endif diff --git a/src/kwd_arg_macros.hpp b/src/kwd_arg_macros.hpp new file mode 100644 index 000000000..e135da7de --- /dev/null +++ b/src/kwd_arg_macros.hpp @@ -0,0 +1,28 @@ +#ifndef SASS_KWD_ARG_MACROS_H +#define SASS_KWD_ARG_MACROS_H + +// Example usage: +// KWD_ARG_SET(Args) { +// KWD_ARG(Args, string, foo); +// KWD_ARG(Args, int, bar); +// ... +// }; +// +// ... and later ... +// +// something(Args().foo("hey").bar(3)); + +#define KWD_ARG_SET(set_name) class set_name + +#define KWD_ARG(set_name, type, name) \ +private: \ + type name##_; \ +public: \ + set_name& name(type name##__) { \ + name##_ = name##__; \ + return *this; \ + } \ + type name() { return name##_; } \ +private: + +#endif diff --git a/src/lexer.cpp b/src/lexer.cpp new file mode 100644 index 000000000..be7f67713 --- /dev/null +++ b/src/lexer.cpp @@ -0,0 +1,181 @@ +#include "sass.hpp" +#include +#include +#include +#include "lexer.hpp" +#include "constants.hpp" + + +namespace Sass { + using namespace Constants; + + namespace Prelexer { + + //#################################### + // BASIC CHARACTER MATCHERS + //#################################### + + // Match standard control chars + const char* kwd_at(const char* src) { return exactly<'@'>(src); } + const char* kwd_dot(const char* src) { return exactly<'.'>(src); } + const char* kwd_comma(const char* src) { return exactly<','>(src); }; + const char* kwd_colon(const char* src) { return exactly<':'>(src); }; + const char* kwd_star(const char* src) { return exactly<'*'>(src); }; + const char* kwd_plus(const char* src) { return exactly<'+'>(src); }; + const char* kwd_minus(const char* src) { return exactly<'-'>(src); }; + const char* kwd_slash(const char* src) { return exactly<'/'>(src); }; + + //#################################### + // implement some function that do exist in the standard + // but those are locale aware which brought some trouble + // this even seems to improve performance by quite a bit + //#################################### + + bool is_alpha(const char& chr) + { + return unsigned(chr - 'A') <= 'Z' - 'A' || + unsigned(chr - 'a') <= 'z' - 'a'; + } + + bool is_space(const char& chr) + { + // adapted the technique from is_alpha + return chr == ' ' || unsigned(chr - '\t') <= '\r' - '\t'; + } + + bool is_digit(const char& chr) + { + // adapted the technique from is_alpha + return unsigned(chr - '0') <= '9' - '0'; + } + + bool is_number(const char& chr) + { + // adapted the technique from is_alpha + return is_digit(chr) || chr == '-' || chr == '+'; + } + + bool is_xdigit(const char& chr) + { + // adapted the technique from is_alpha + return unsigned(chr - '0') <= '9' - '0' || + unsigned(chr - 'a') <= 'f' - 'a' || + unsigned(chr - 'A') <= 'F' - 'A'; + } + + bool is_punct(const char& chr) + { + // locale independent + return chr == '.'; + } + + bool is_alnum(const char& chr) + { + return is_alpha(chr) || is_digit(chr); + } + + // check if char is outside ascii range + bool is_unicode(const char& chr) + { + // check for unicode range + return unsigned(chr) > 127; + } + + // check if char is outside ascii range + // but with specific ranges (copied from Ruby Sass) + bool is_nonascii(const char& chr) + { + unsigned int cmp = unsigned(chr); + return ( + (cmp >= 128 && cmp <= 15572911) || + (cmp >= 15630464 && cmp <= 15712189) || + (cmp >= 4036001920) + ); + } + + // check if char is within a reduced ascii range + // valid in a uri (copied from Ruby Sass) + bool is_uri_character(const char& chr) + { + unsigned int cmp = unsigned(chr); + return (cmp > 41 && cmp < 127) || + cmp == ':' || cmp == '/'; + } + + // check if char is within a reduced ascii range + // valid for escaping (copied from Ruby Sass) + bool is_escapable_character(const char& chr) + { + unsigned int cmp = unsigned(chr); + return cmp > 31 && cmp < 127; + } + + // Match word character (look ahead) + bool is_character(const char& chr) + { + // valid alpha, numeric or unicode char (plus hyphen) + return is_alnum(chr) || is_unicode(chr) || chr == '-'; + } + + //#################################### + // BASIC CLASS MATCHERS + //#################################### + + // create matchers that advance the position + const char* space(const char* src) { return is_space(*src) ? src + 1 : 0; } + const char* alpha(const char* src) { return is_alpha(*src) ? src + 1 : 0; } + const char* unicode(const char* src) { return is_unicode(*src) ? src + 1 : 0; } + const char* nonascii(const char* src) { return is_nonascii(*src) ? src + 1 : 0; } + const char* digit(const char* src) { return is_digit(*src) ? src + 1 : 0; } + const char* xdigit(const char* src) { return is_xdigit(*src) ? src + 1 : 0; } + const char* alnum(const char* src) { return is_alnum(*src) ? src + 1 : 0; } + const char* punct(const char* src) { return is_punct(*src) ? src + 1 : 0; } + const char* hyphen(const char* src) { return *src && *src == '-' ? src + 1 : 0; } + const char* character(const char* src) { return is_character(*src) ? src + 1 : 0; } + const char* uri_character(const char* src) { return is_uri_character(*src) ? src + 1 : 0; } + const char* escapable_character(const char* src) { return is_escapable_character(*src) ? src + 1 : 0; } + + // Match multiple ctype characters. + const char* spaces(const char* src) { return one_plus(src); } + const char* digits(const char* src) { return one_plus(src); } + const char* hyphens(const char* src) { return one_plus(src); } + + // Whitespace handling. + const char* no_spaces(const char* src) { return negate< space >(src); } + const char* optional_spaces(const char* src) { return zero_plus< space >(src); } + + // Match any single character. + const char* any_char(const char* src) { return *src ? src + 1 : src; } + + // Match word boundary (zero-width lookahead). + const char* word_boundary(const char* src) { return is_character(*src) || *src == '#' ? 0 : src; } + + // Match linefeed /(?:\n|\r\n?)/ + const char* re_linebreak(const char* src) + { + // end of file or unix linefeed return here + if (*src == 0 || *src == '\n') return src + 1; + // a carriage return may optionally be followed by a linefeed + if (*src == '\r') return *(src + 1) == '\n' ? src + 2 : src + 1; + // no linefeed + return 0; + } + + // Assert string boundaries (/\Z|\z|\A/) + // This is a zero-width positive lookahead + const char* end_of_line(const char* src) + { + // end of file or unix linefeed return here + return *src == 0 || *src == '\n' || *src == '\r' ? src : 0; + } + + // Assert end_of_file boundary (/\z/) + // This is a zero-width positive lookahead + const char* end_of_file(const char* src) + { + // end of file or unix linefeed return here + return *src == 0 ? src : 0; + } + + } +} diff --git a/src/lexer.hpp b/src/lexer.hpp new file mode 100644 index 000000000..5838c291c --- /dev/null +++ b/src/lexer.hpp @@ -0,0 +1,315 @@ +#ifndef SASS_LEXER_H +#define SASS_LEXER_H + +#include + +namespace Sass { + namespace Prelexer { + + //#################################### + // BASIC CHARACTER MATCHERS + //#################################### + + // Match standard control chars + const char* kwd_at(const char* src); + const char* kwd_dot(const char* src); + const char* kwd_comma(const char* src); + const char* kwd_colon(const char* src); + const char* kwd_star(const char* src); + const char* kwd_plus(const char* src); + const char* kwd_minus(const char* src); + const char* kwd_slash(const char* src); + + //#################################### + // BASIC CLASS MATCHERS + //#################################### + + // These are locale independant + bool is_space(const char& src); + bool is_alpha(const char& src); + bool is_punct(const char& src); + bool is_digit(const char& src); + bool is_number(const char& src); + bool is_alnum(const char& src); + bool is_xdigit(const char& src); + bool is_unicode(const char& src); + bool is_nonascii(const char& src); + bool is_character(const char& src); + bool is_uri_character(const char& src); + bool escapable_character(const char& src); + + // Match a single ctype predicate. + const char* space(const char* src); + const char* alpha(const char* src); + const char* digit(const char* src); + const char* xdigit(const char* src); + const char* alnum(const char* src); + const char* punct(const char* src); + const char* hyphen(const char* src); + const char* unicode(const char* src); + const char* nonascii(const char* src); + const char* character(const char* src); + const char* uri_character(const char* src); + const char* escapable_character(const char* src); + + // Match multiple ctype characters. + const char* spaces(const char* src); + const char* digits(const char* src); + const char* hyphens(const char* src); + + // Whitespace handling. + const char* no_spaces(const char* src); + const char* optional_spaces(const char* src); + + // Match any single character (/./). + const char* any_char(const char* src); + + // Assert word boundary (/\b/) + // Is a zero-width positive lookaheads + const char* word_boundary(const char* src); + + // Match a single linebreak (/(?:\n|\r\n?)/). + const char* re_linebreak(const char* src); + + // Assert string boundaries (/\Z|\z|\A/) + // There are zero-width positive lookaheads + const char* end_of_line(const char* src); + + // Assert end_of_file boundary (/\z/) + const char* end_of_file(const char* src); + // const char* start_of_string(const char* src); + + // Type definition for prelexer functions + typedef const char* (*prelexer)(const char*); + + //#################################### + // BASIC "REGEX" CONSTRUCTORS + //#################################### + + // Match a single character literal. + // Regex equivalent: /(?:x)/ + template + const char* exactly(const char* src) { + return *src == chr ? src + 1 : 0; + } + + // Match the full string literal. + // Regex equivalent: /(?:literal)/ + template + const char* exactly(const char* src) { + if (str == NULL) return 0; + const char* pre = str; + if (src == NULL) return 0; + // there is a small chance that the search string + // is longer than the rest of the string to look at + while (*pre && *src == *pre) { + ++src, ++pre; + } + // did the matcher finish? + return *pre == 0 ? src : 0; + } + + + // Match a single character literal. + // Regex equivalent: /(?:x)/i + // only define lower case alpha chars + template + const char* insensitive(const char* src) { + return *src == chr || *src+32 == chr ? src + 1 : 0; + } + + // Match the full string literal. + // Regex equivalent: /(?:literal)/i + // only define lower case alpha chars + template + const char* insensitive(const char* src) { + if (str == NULL) return 0; + const char* pre = str; + if (src == NULL) return 0; + // there is a small chance that the search string + // is longer than the rest of the string to look at + while (*pre && (*src == *pre || *src+32 == *pre)) { + ++src, ++pre; + } + // did the matcher finish? + return *pre == 0 ? src : 0; + } + + // Match for members of char class. + // Regex equivalent: /[axy]/ + template + const char* class_char(const char* src) { + const char* cc = char_class; + while (*cc && *src != *cc) ++cc; + return *cc ? src + 1 : 0; + } + + // Match for members of char class. + // Regex equivalent: /[axy]+/ + template + const char* class_chars(const char* src) { + const char* p = src; + while (class_char(p)) ++p; + return p == src ? 0 : p; + } + + // Match for members of char class. + // Regex equivalent: /[^axy]/ + template + const char* neg_class_char(const char* src) { + if (*src == 0) return 0; + const char* cc = neg_char_class; + while (*cc && *src != *cc) ++cc; + return *cc ? 0 : src + 1; + } + + // Match for members of char class. + // Regex equivalent: /[^axy]+/ + template + const char* neg_class_chars(const char* src) { + const char* p = src; + while (neg_class_char(p)) ++p; + return p == src ? 0 : p; + } + + // Match all except the supplied one. + // Regex equivalent: /[^x]/ + template + const char* any_char_but(const char* src) { + return (*src && *src != chr) ? src + 1 : 0; + } + + // Succeeds if the matcher fails. + // Aka. zero-width negative lookahead. + // Regex equivalent: /(?!literal)/ + template + const char* negate(const char* src) { + return mx(src) ? 0 : src; + } + + // Succeeds if the matcher succeeds. + // Aka. zero-width positive lookahead. + // Regex equivalent: /(?=literal)/ + // just hangs around until we need it + template + const char* lookahead(const char* src) { + return mx(src) ? src : 0; + } + + // Tries supplied matchers in order. + // Succeeds if one of them succeeds. + // Regex equivalent: /(?:FOO|BAR)/ + template + const char* alternatives(const char* src) { + const char* rslt; + if ((rslt = mx(src))) return rslt; + return 0; + } + template + const char* alternatives(const char* src) { + const char* rslt; + if ((rslt = mx1(src))) return rslt; + return alternatives(src); + } + + // Tries supplied matchers in order. + // Succeeds if all of them succeeds. + // Regex equivalent: /(?:FOO)(?:BAR)/ + template + const char* sequence(const char* src) { + const char* rslt = src; + if (!(rslt = mx1(rslt))) return 0; + return rslt; + } + template + const char* sequence(const char* src) { + const char* rslt = src; + if (!(rslt = mx1(rslt))) return 0; + return sequence(rslt); + } + + + // Match a pattern or not. Always succeeds. + // Regex equivalent: /(?:literal)?/ + template + const char* optional(const char* src) { + const char* p = mx(src); + return p ? p : src; + } + + // Match zero or more of the patterns. + // Regex equivalent: /(?:literal)*/ + template + const char* zero_plus(const char* src) { + const char* p = mx(src); + while (p) src = p, p = mx(src); + return src; + } + + // Match one or more of the patterns. + // Regex equivalent: /(?:literal)+/ + template + const char* one_plus(const char* src) { + const char* p = mx(src); + if (!p) return 0; + while (p) src = p, p = mx(src); + return src; + } + + // Match mx non-greedy until delimiter. + // Other prelexers are greedy by default. + // Regex equivalent: /(?:$mx)*?(?=$delim)\b/ + template + const char* non_greedy(const char* src) { + while (!delim(src)) { + const char* p = mx(src); + if (p == src) return 0; + if (p == 0) return 0; + src = p; + } + return src; + } + + //#################################### + // ADVANCED "REGEX" CONSTRUCTORS + //#################################### + + // Match with word boundary rule. + // Regex equivalent: /(?:$mx)\b/i + template + const char* keyword(const char* src) { + return sequence < + insensitive < str >, + word_boundary + >(src); + } + + // Match with word boundary rule. + // Regex equivalent: /(?:$mx)\b/ + template + const char* word(const char* src) { + return sequence < + exactly < str >, + word_boundary + >(src); + } + + template + const char* loosely(const char* src) { + return sequence < + optional_spaces, + exactly < chr > + >(src); + } + template + const char* loosely(const char* src) { + return sequence < + optional_spaces, + exactly < str > + >(src); + } + + } +} + +#endif diff --git a/src/listize.cpp b/src/listize.cpp new file mode 100644 index 000000000..cb921ae67 --- /dev/null +++ b/src/listize.cpp @@ -0,0 +1,86 @@ +#include "sass.hpp" +#include +#include +#include + +#include "listize.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "error_handling.hpp" + +namespace Sass { + + Listize::Listize() + { } + + Expression_Ptr Listize::operator()(Selector_List_Ptr sel) + { + List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + l->from_selector(true); + for (size_t i = 0, L = sel->length(); i < L; ++i) { + if (!sel->at(i)) continue; + l->append(sel->at(i)->perform(this)); + } + if (l->length()) return l.detach(); + return SASS_MEMORY_NEW(Null, l->pstate()); + } + + Expression_Ptr Listize::operator()(Compound_Selector_Ptr sel) + { + std::string str; + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Expression_Ptr e = (*sel)[i]->perform(this); + if (e) str += e->to_string(); + } + return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); + } + + Expression_Ptr Listize::operator()(Complex_Selector_Ptr sel) + { + List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), 2); + l->from_selector(true); + Compound_Selector_Obj head = sel->head(); + if (head && !head->is_empty_reference()) + { + Expression_Ptr hh = head->perform(this); + if (hh) l->append(hh); + } + + std::string reference = ! sel->reference() ? "" + : sel->reference()->to_string(); + switch(sel->combinator()) + { + case Complex_Selector::PARENT_OF: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), ">")); + break; + case Complex_Selector::ADJACENT_TO: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "+")); + break; + case Complex_Selector::REFERENCE: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "/" + reference + "/")); + break; + case Complex_Selector::PRECEDES: + l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "~")); + break; + case Complex_Selector::ANCESTOR_OF: + break; + default: break; + } + + Complex_Selector_Obj tail = sel->tail(); + if (tail) + { + Expression_Obj tt = tail->perform(this); + if (List_Ptr ls = Cast(tt)) + { l->concat(ls); } + } + if (l->length() == 0) return 0; + return l.detach(); + } + + Expression_Ptr Listize::fallback_impl(AST_Node_Ptr n) + { + return Cast(n); + } + +} diff --git a/src/listize.hpp b/src/listize.hpp new file mode 100644 index 000000000..9716ebefc --- /dev/null +++ b/src/listize.hpp @@ -0,0 +1,34 @@ +#ifndef SASS_LISTIZE_H +#define SASS_LISTIZE_H + +#include +#include + +#include "ast.hpp" +#include "context.hpp" +#include "operation.hpp" +#include "environment.hpp" + +namespace Sass { + + struct Backtrace; + + class Listize : public Operation_CRTP { + + Expression_Ptr fallback_impl(AST_Node_Ptr n); + + public: + Listize(); + ~Listize() { } + + Expression_Ptr operator()(Selector_List_Ptr); + Expression_Ptr operator()(Complex_Selector_Ptr); + Expression_Ptr operator()(Compound_Selector_Ptr); + + template + Expression_Ptr fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/mapping.hpp b/src/mapping.hpp new file mode 100644 index 000000000..54fb4a0f7 --- /dev/null +++ b/src/mapping.hpp @@ -0,0 +1,18 @@ +#ifndef SASS_MAPPING_H +#define SASS_MAPPING_H + +#include "position.hpp" + +namespace Sass { + + struct Mapping { + Position original_position; + Position generated_position; + + Mapping(const Position& original_position, const Position& generated_position) + : original_position(original_position), generated_position(generated_position) { } + }; + +} + +#endif diff --git a/src/memory/SharedPtr.cpp b/src/memory/SharedPtr.cpp new file mode 100644 index 000000000..2530360a5 --- /dev/null +++ b/src/memory/SharedPtr.cpp @@ -0,0 +1,114 @@ +#include "../sass.hpp" +#include +#include + +#include "SharedPtr.hpp" +#include "../ast_fwd_decl.hpp" + +#ifdef DEBUG_SHARED_PTR +#include "../debugger.hpp" +#endif + +namespace Sass { + + #ifdef DEBUG_SHARED_PTR + void SharedObj::dumpMemLeaks() { + if (!all.empty()) { + std::cerr << "###################################\n"; + std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; + std::cerr << "###################################\n"; + for (SharedObj* var : all) { + if (AST_Node_Ptr ast = dynamic_cast(var)) { + debug_ast(ast); + } else { + std::cerr << "LEAKED " << var << "\n"; + } + } + } + } + std::vector SharedObj::all; + #endif + + bool SharedObj::taint = false; + + SharedObj::SharedObj() + : detached(false) + #ifdef DEBUG_SHARED_PTR + , dbg(false) + #endif + { + refcounter = 0; + #ifdef DEBUG_SHARED_PTR + if (taint) all.push_back(this); + #endif + }; + + SharedObj::~SharedObj() { + #ifdef DEBUG_SHARED_PTR + if (dbg) std::cerr << "Destruct " << this << "\n"; + if(!all.empty()) { // check needed for MSVC (no clue why?) + all.erase(std::remove(all.begin(), all.end(), this), all.end()); + } + #endif + }; + + void SharedPtr::decRefCount() { + if (node) { + -- node->refcounter; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "- " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; + #endif + if (node->refcounter == 0) { + #ifdef DEBUG_SHARED_PTR + // AST_Node_Ptr ast = dynamic_cast(node); + if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; + #endif + if (!node->detached) { + delete(node); + } + } + } + } + + void SharedPtr::incRefCount() { + if (node) { + ++ node->refcounter; + node->detached = false; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) { + std::cerr << "+ " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; + } + #endif + } + } + + SharedPtr::~SharedPtr() { + decRefCount(); + } + + + // the create constructor + SharedPtr::SharedPtr(SharedObj* ptr) + : node(ptr) { + incRefCount(); + } + // copy assignment operator + SharedPtr& SharedPtr::operator=(const SharedPtr& rhs) { + void* cur_ptr = (void*) node; + void* rhs_ptr = (void*) rhs.node; + if (cur_ptr == rhs_ptr) { + return *this; + } + decRefCount(); + node = rhs.node; + incRefCount(); + return *this; + } + + // the copy constructor + SharedPtr::SharedPtr(const SharedPtr& obj) + : node(obj.node) { + incRefCount(); + } + +} \ No newline at end of file diff --git a/src/memory/SharedPtr.hpp b/src/memory/SharedPtr.hpp new file mode 100644 index 000000000..f20dfa39b --- /dev/null +++ b/src/memory/SharedPtr.hpp @@ -0,0 +1,206 @@ +#ifndef SASS_MEMORY_SHARED_PTR_H +#define SASS_MEMORY_SHARED_PTR_H + +#include "sass/base.h" + +#include + +namespace Sass { + + class SharedPtr; + + /////////////////////////////////////////////////////////////////////////////// + // Use macros for the allocation task, since overloading operator `new` + // has been proven to be flaky under certain compilers (see comment below). + /////////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUG_SHARED_PTR + + #define SASS_MEMORY_NEW(Class, ...) \ + ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy(__FILE__, __LINE__)) \ + + #define SASS_MEMORY_CLONE(obj) \ + ((obj)->clone(__FILE__, __LINE__)) \ + + #else + + #define SASS_MEMORY_NEW(Class, ...) \ + new Class(__VA_ARGS__) \ + + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy()) \ + + #define SASS_MEMORY_CLONE(obj) \ + ((obj)->clone()) \ + + #endif + + class SharedObj { + protected: + friend class SharedPtr; + friend class Memory_Manager; + #ifdef DEBUG_SHARED_PTR + static std::vector all; + std::string file; + size_t line; + #endif + static bool taint; + long refcounter; + // long refcount; + bool detached; + #ifdef DEBUG_SHARED_PTR + bool dbg; + #endif + public: + #ifdef DEBUG_SHARED_PTR + static void dumpMemLeaks(); + SharedObj* trace(std::string file, size_t line) { + this->file = file; + this->line = line; + return this; + } + #endif + SharedObj(); + #ifdef DEBUG_SHARED_PTR + std::string getDbgFile() { + return file; + } + size_t getDbgLine() { + return line; + } + void setDbg(bool dbg) { + this->dbg = dbg; + } + #endif + static void setTaint(bool val) { + taint = val; + } + virtual ~SharedObj(); + long getRefCount() { + return refcounter; + } + }; + + + class SharedPtr { + protected: + SharedObj* node; + protected: + void decRefCount(); + void incRefCount(); + public: + // the empty constructor + SharedPtr() + : node(NULL) {}; + // the create constructor + SharedPtr(SharedObj* ptr); + // the copy constructor + SharedPtr(const SharedPtr& obj); + // the move constructor + SharedPtr(SharedPtr&& obj); + // copy assignment operator + SharedPtr& operator=(const SharedPtr& obj); + // move assignment operator + SharedPtr& operator=(SharedPtr&& obj); + // pure virtual destructor + virtual ~SharedPtr() = 0; + public: + SharedObj* obj () const { + return node; + }; + SharedObj* operator-> () const { + return node; + }; + bool isNull () { + return node == NULL; + }; + bool isNull () const { + return node == NULL; + }; + SharedObj* detach() const { + if (node) { + node->detached = true; + } + return node; + }; + operator bool() const { + return node != NULL; + }; + + }; + + template < class T > + class SharedImpl : private SharedPtr { + public: + SharedImpl() + : SharedPtr(NULL) {}; + SharedImpl(T* node) + : SharedPtr(node) {}; + template < class U > + SharedImpl(SharedImpl obj) + : SharedPtr(static_cast(obj.ptr())) {} + SharedImpl(T&& node) + : SharedPtr(node) {}; + SharedImpl(const T& node) + : SharedPtr(node) {}; + // the copy constructor + SharedImpl(const SharedImpl& impl) + : SharedPtr(impl.node) {}; + // the move constructor + SharedImpl(SharedImpl&& impl) + : SharedPtr(impl.node) {}; + // copy assignment operator + SharedImpl& operator=(const SharedImpl& rhs) { + if (node) decRefCount(); + node = rhs.node; + incRefCount(); + return *this; + } + // move assignment operator + SharedImpl& operator=(SharedImpl&& rhs) { + // don't move our self + if (this != &rhs) { + if (node) decRefCount(); + node = std::move(rhs.node); + rhs.node = NULL; + } + return *this; + } + ~SharedImpl() {}; + public: + operator T*() const { + return static_cast(this->obj()); + } + operator T&() const { + return *static_cast(this->obj()); + } + T& operator* () const { + return *static_cast(this->obj()); + }; + T* operator-> () const { + return static_cast(this->obj()); + }; + T* ptr () const { + return static_cast(this->obj()); + }; + T* detach() const { + if (this->obj() == NULL) return NULL; + return static_cast(SharedPtr::detach()); + } + bool isNull() const { + return this->obj() == NULL; + } + bool operator<(const T& rhs) const { + return *this->ptr() < rhs; + }; + operator bool() const { + return this->obj() != NULL; + }; + }; + +} + +#endif \ No newline at end of file diff --git a/src/node.cpp b/src/node.cpp new file mode 100644 index 000000000..08eada733 --- /dev/null +++ b/src/node.cpp @@ -0,0 +1,319 @@ +#include "sass.hpp" +#include + +#include "node.hpp" +#include "context.hpp" +#include "parser.hpp" + +namespace Sass { + + + Node Node::createCombinator(const Complex_Selector::Combinator& combinator) { + NodeDequePtr null; + return Node(COMBINATOR, combinator, NULL /*pSelector*/, null /*pCollection*/); + } + + + Node Node::createSelector(const Complex_Selector& pSelector) { + NodeDequePtr null; + + Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(&pSelector); + pStripped->tail(NULL); + pStripped->combinator(Complex_Selector::ANCESTOR_OF); + + Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/); + n.got_line_feed = pSelector.has_line_feed(); + return n; + } + + + Node Node::createCollection() { + NodeDequePtr pEmptyCollection = std::make_shared(); + return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pEmptyCollection); + } + + + Node Node::createCollection(const NodeDeque& values) { + NodeDequePtr pShallowCopiedCollection = std::make_shared(values); + return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pShallowCopiedCollection); + } + + + Node Node::createNil() { + NodeDequePtr null; + return Node(NIL, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, null /*pCollection*/); + } + + + Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection) + : got_line_feed(false), mType(type), mCombinator(combinator), mpSelector(pSelector), mpCollection(pCollection) + { if (pSelector) got_line_feed = pSelector->has_line_feed(); } + + + Node Node::klone() const { + NodeDequePtr pNewCollection = std::make_shared(); + if (mpCollection) { + for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { + Node& toClone = *iter; + pNewCollection->push_back(toClone.klone()); + } + } + + Node n(mType, mCombinator, mpSelector ? SASS_MEMORY_COPY(mpSelector) : NULL, pNewCollection); + n.got_line_feed = got_line_feed; + return n; + } + + + bool Node::contains(const Node& potentialChild) const { + bool found = false; + + for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { + Node& toTest = *iter; + + if (toTest == potentialChild) { + found = true; + break; + } + } + + return found; + } + + + bool Node::operator==(const Node& rhs) const { + if (this->type() != rhs.type()) { + return false; + } + + if (this->isCombinator()) { + + return this->combinator() == rhs.combinator(); + + } else if (this->isNil()) { + + return true; // no state to check + + } else if (this->isSelector()){ + + return *this->selector() == *rhs.selector(); + + } else if (this->isCollection()) { + + if (this->collection()->size() != rhs.collection()->size()) { + return false; + } + + for (NodeDeque::iterator lhsIter = this->collection()->begin(), lhsIterEnd = this->collection()->end(), + rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { + + if (*lhsIter != *rhsIter) { + return false; + } + + } + + return true; + + } + + // We shouldn't get here. + throw "Comparing unknown node types. A new type was probably added and this method wasn't implemented for it."; + } + + + void Node::plus(Node& rhs) { + if (!this->isCollection() || !rhs.isCollection()) { + throw "Both the current node and rhs must be collections."; + } + this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); + } + +#ifdef DEBUG + std::ostream& operator<<(std::ostream& os, const Node& node) { + + if (node.isCombinator()) { + + switch (node.combinator()) { + case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; + case Complex_Selector::PARENT_OF: os << "\">\""; break; + case Complex_Selector::PRECEDES: os << "\"~\""; break; + case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; + case Complex_Selector::REFERENCE: os << "\"/\""; break; + } + + } else if (node.isNil()) { + + os << "nil"; + + } else if (node.isSelector()){ + + os << node.selector()->head()->to_string(); + + } else if (node.isCollection()) { + + os << "["; + + for (NodeDeque::iterator iter = node.collection()->begin(), iterBegin = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { + if (iter != iterBegin) { + os << ", "; + } + + os << (*iter); + } + + os << "]"; + + } + + return os; + + } +#endif + + + Node complexSelectorToNode(Complex_Selector_Ptr pToConvert) { + if (pToConvert == NULL) { + return Node::createNil(); + } + Node node = Node::createCollection(); + node.got_line_feed = pToConvert->has_line_feed(); + bool has_lf = pToConvert->has_line_feed(); + + // unwrap the selector from parent ref + if (pToConvert->head() && pToConvert->head()->has_parent_ref()) { + Complex_Selector_Obj tail = pToConvert->tail(); + if (tail) tail->has_line_feed(pToConvert->has_line_feed()); + pToConvert = tail; + } + + while (pToConvert) { + + bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); + + // the first Complex_Selector may contain a dummy head pointer, skip it. + if (pToConvert->head() && !empty_parent_ref) { + node.collection()->push_back(Node::createSelector(*pToConvert)); + if (has_lf) node.collection()->back().got_line_feed = has_lf; + if (pToConvert->head() || empty_parent_ref) { + if (pToConvert->tail()) { + pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); + } + } + has_lf = false; + } + + if (pToConvert->combinator() != Complex_Selector::ANCESTOR_OF) { + node.collection()->push_back(Node::createCombinator(pToConvert->combinator())); + if (has_lf) node.collection()->back().got_line_feed = has_lf; + has_lf = false; + } + + if (pToConvert && empty_parent_ref && pToConvert->tail()) { + // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); + } + + pToConvert = pToConvert->tail(); + } + + return node; + } + + + Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert) { + if (toConvert.isNil()) { + return NULL; + } + + + if (!toConvert.isCollection()) { + throw "The node to convert to a Complex_Selector_Ptr must be a collection type or nil."; + } + + + NodeDeque& childNodes = *toConvert.collection(); + + std::string noPath(""); + Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL); + + Complex_Selector_Obj pCurrent = pFirst; + + if (toConvert.isSelector()) pFirst->has_line_feed(toConvert.got_line_feed); + if (toConvert.isCombinator()) pFirst->has_line_feed(toConvert.got_line_feed); + + for (NodeDeque::iterator childIter = childNodes.begin(), childIterEnd = childNodes.end(); childIter != childIterEnd; childIter++) { + + Node& child = *childIter; + + if (child.isSelector()) { + // JMA - need to clone the selector, because they can end up getting shared across Node + // collections, and can result in an infinite loop during the call to parentSuperselector() + pCurrent->tail(SASS_MEMORY_COPY(child.selector())); + // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); + pCurrent = pCurrent->tail(); + } else if (child.isCombinator()) { + pCurrent->combinator(child.combinator()); + if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); + + // if the next node is also a combinator, create another Complex_Selector to hold it so it doesn't replace the current combinator + if (childIter+1 != childIterEnd) { + Node& nextNode = *(childIter+1); + if (nextNode.isCombinator()) { + pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL)); + if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed); + pCurrent = pCurrent->tail(); + } + } + } else { + throw "The node to convert's children must be only combinators or selectors."; + } + } + + // Put the dummy Compound_Selector in the first position, for consistency with the rest of libsass + Compound_Selector_Ptr fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[NODE]"), 1); + Parent_Selector_Ptr selectorRef = SASS_MEMORY_NEW(Parent_Selector, ParserState("[NODE]")); + fakeHead->elements().push_back(selectorRef); + if (toConvert.got_line_feed) pFirst->has_line_feed(toConvert.got_line_feed); + // pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed); + pFirst->head(fakeHead); + return SASS_MEMORY_COPY(pFirst); + } + + // A very naive trim function, which removes duplicates in a node + // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs + Node Node::naiveTrim(Node& seqses) { + + std::vector res; + std::vector known; + + NodeDeque::reverse_iterator seqsesIter = seqses.collection()->rbegin(), + seqsesIterEnd = seqses.collection()->rend(); + + for (; seqsesIter != seqsesIterEnd; ++seqsesIter) + { + Node& seqs1 = *seqsesIter; + if( seqs1.isSelector() ) { + Complex_Selector_Obj sel = seqs1.selector(); + std::vector::iterator it; + bool found = false; + for (it = known.begin(); it != known.end(); ++it) { + if (**it == *sel) { found = true; break; } + } + if( !found ) { + known.push_back(seqs1.selector()); + res.push_back(&seqs1); + } + } else { + res.push_back(&seqs1); + } + } + + Node result = Node::createCollection(); + + for (size_t i = res.size() - 1; i != std::string::npos; --i) { + result.collection()->push_back(*res[i]); + } + + return result; + } +} diff --git a/src/node.hpp b/src/node.hpp new file mode 100644 index 000000000..23ba360c3 --- /dev/null +++ b/src/node.hpp @@ -0,0 +1,118 @@ +#ifndef SASS_NODE_H +#define SASS_NODE_H + +#include +#include + +#include "ast.hpp" + + +namespace Sass { + + + + + class Context; + + /* + There are a lot of stumbling blocks when trying to port the ruby extend code to C++. The biggest is the choice of + data type. The ruby code will pretty seamlessly switch types between an Array (libsass' + equivalent is the Complex_Selector) to a Sequence, which contains more metadata about the sequence than just the + selector info. They also have the ability to have arbitrary nestings of arrays like [1, [2]], which is hard to + implement using Array equivalents in C++ (like the deque or vector). They also have the ability to include nil + in the arrays, like [1, nil, 3], which has potential semantic differences than an empty array [1, [], 3]. To be + able to represent all of these as unique cases, we need to create a tree of variant objects. The tree nature allows + the inconsistent nesting levels. The variant nature (while making some of the C++ code uglier) allows the code to + more closely match the ruby code, which is a huge benefit when attempting to implement an complex algorithm like + the Extend operator. + + Note that the current libsass data model also pairs the combinator with the Complex_Selector that follows it, but + ruby sass has no such restriction, so we attempt to create a data structure that can handle them split apart. + */ + + class Node; + typedef std::deque NodeDeque; + typedef std::shared_ptr NodeDequePtr; + + class Node { + public: + enum TYPE { + SELECTOR, + COMBINATOR, + COLLECTION, + NIL + }; + + TYPE type() const { return mType; } + bool isCombinator() const { return mType == COMBINATOR; } + bool isSelector() const { return mType == SELECTOR; } + bool isCollection() const { return mType == COLLECTION; } + bool isNil() const { return mType == NIL; } + bool got_line_feed; + + Complex_Selector::Combinator combinator() const { return mCombinator; } + + Complex_Selector_Obj selector() { return mpSelector; } + Complex_Selector_Obj selector() const { return mpSelector; } + + NodeDequePtr collection() { return mpCollection; } + const NodeDequePtr collection() const { return mpCollection; } + + static Node createCombinator(const Complex_Selector::Combinator& combinator); + + // This method will klone the selector, stripping off the tail and combinator + static Node createSelector(const Complex_Selector& pSelector); + + static Node createCollection(); + static Node createCollection(const NodeDeque& values); + + static Node createNil(); + static Node naiveTrim(Node& seqses); + + Node klone() const; + + bool operator==(const Node& rhs) const; + inline bool operator!=(const Node& rhs) const { return !(*this == rhs); } + + + /* + COLLECTION FUNCTIONS + + Most types don't need any helper methods (nil and combinator due to their simplicity and + selector due to the fact that we leverage the non-node selector code on the Complex_Selector + whereever possible). The following methods are intended to be called on Node objects whose + type is COLLECTION only. + */ + + // rhs and this must be node collections. Shallow copy the nodes from rhs to the end of this. + // This function DOES NOT remove the nodes from rhs. + void plus(Node& rhs); + + // potentialChild must be a node collection of selectors/combinators. this must be a collection + // of collections of nodes/combinators. This method checks if potentialChild is a child of this + // Node. + bool contains(const Node& potentialChild) const; + + private: + // Private constructor; Use the static methods (like createCombinator and createSelector) + // to instantiate this object. This is more expressive, and it allows us to break apart each + // case into separate functions. + Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection); + + TYPE mType; + + // TODO: can we union these to save on memory? + Complex_Selector::Combinator mCombinator; + Complex_Selector_Obj mpSelector; + NodeDequePtr mpCollection; + }; + +#ifdef DEBUG + std::ostream& operator<<(std::ostream& os, const Node& node); +#endif + Node complexSelectorToNode(Complex_Selector_Ptr pToConvert); + Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert); + +} + +#endif diff --git a/src/operation.hpp b/src/operation.hpp new file mode 100644 index 000000000..2d4fbec9d --- /dev/null +++ b/src/operation.hpp @@ -0,0 +1,173 @@ +#ifndef SASS_OPERATION_H +#define SASS_OPERATION_H + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + template + class Operation { + public: + virtual T operator()(AST_Node_Ptr x) = 0; + virtual ~Operation() { } + // statements + virtual T operator()(Block_Ptr x) = 0; + virtual T operator()(Ruleset_Ptr x) = 0; + virtual T operator()(Bubble_Ptr x) = 0; + virtual T operator()(Trace_Ptr x) = 0; + virtual T operator()(Supports_Block_Ptr x) = 0; + virtual T operator()(Media_Block_Ptr x) = 0; + virtual T operator()(At_Root_Block_Ptr x) = 0; + virtual T operator()(Directive_Ptr x) = 0; + virtual T operator()(Keyframe_Rule_Ptr x) = 0; + virtual T operator()(Declaration_Ptr x) = 0; + virtual T operator()(Assignment_Ptr x) = 0; + virtual T operator()(Import_Ptr x) = 0; + virtual T operator()(Import_Stub_Ptr x) = 0; + virtual T operator()(Warning_Ptr x) = 0; + virtual T operator()(Error_Ptr x) = 0; + virtual T operator()(Debug_Ptr x) = 0; + virtual T operator()(Comment_Ptr x) = 0; + virtual T operator()(If_Ptr x) = 0; + virtual T operator()(For_Ptr x) = 0; + virtual T operator()(Each_Ptr x) = 0; + virtual T operator()(While_Ptr x) = 0; + virtual T operator()(Return_Ptr x) = 0; + virtual T operator()(Content_Ptr x) = 0; + virtual T operator()(Extension_Ptr x) = 0; + virtual T operator()(Definition_Ptr x) = 0; + virtual T operator()(Mixin_Call_Ptr x) = 0; + // expressions + virtual T operator()(List_Ptr x) = 0; + virtual T operator()(Map_Ptr x) = 0; + virtual T operator()(Function_Ptr x) = 0; + virtual T operator()(Binary_Expression_Ptr x) = 0; + virtual T operator()(Unary_Expression_Ptr x) = 0; + virtual T operator()(Function_Call_Ptr x) = 0; + virtual T operator()(Function_Call_Schema_Ptr x) = 0; + virtual T operator()(Custom_Warning_Ptr x) = 0; + virtual T operator()(Custom_Error_Ptr x) = 0; + virtual T operator()(Variable_Ptr x) = 0; + virtual T operator()(Number_Ptr x) = 0; + virtual T operator()(Color_Ptr x) = 0; + virtual T operator()(Boolean_Ptr x) = 0; + virtual T operator()(String_Schema_Ptr x) = 0; + virtual T operator()(String_Quoted_Ptr x) = 0; + virtual T operator()(String_Constant_Ptr x) = 0; + virtual T operator()(Supports_Condition_Ptr x) = 0; + virtual T operator()(Supports_Operator_Ptr x) = 0; + virtual T operator()(Supports_Negation_Ptr x) = 0; + virtual T operator()(Supports_Declaration_Ptr x) = 0; + virtual T operator()(Supports_Interpolation_Ptr x) = 0; + virtual T operator()(Media_Query_Ptr x) = 0; + virtual T operator()(Media_Query_Expression_Ptr x) = 0; + virtual T operator()(At_Root_Query_Ptr x) = 0; + virtual T operator()(Null_Ptr x) = 0; + virtual T operator()(Parent_Selector_Ptr x) = 0; + // parameters and arguments + virtual T operator()(Parameter_Ptr x) = 0; + virtual T operator()(Parameters_Ptr x) = 0; + virtual T operator()(Argument_Ptr x) = 0; + virtual T operator()(Arguments_Ptr x) = 0; + // selectors + virtual T operator()(Selector_Schema_Ptr x) = 0; + virtual T operator()(Placeholder_Selector_Ptr x) = 0; + virtual T operator()(Element_Selector_Ptr x) = 0; + virtual T operator()(Class_Selector_Ptr x) = 0; + virtual T operator()(Id_Selector_Ptr x) = 0; + virtual T operator()(Attribute_Selector_Ptr x) = 0; + virtual T operator()(Pseudo_Selector_Ptr x) = 0; + virtual T operator()(Wrapped_Selector_Ptr x) = 0; + virtual T operator()(Compound_Selector_Ptr x)= 0; + virtual T operator()(Complex_Selector_Ptr x) = 0; + virtual T operator()(Selector_List_Ptr x) = 0; + + template + T fallback(U x) { return T(); } + }; + + template + class Operation_CRTP : public Operation { + public: + D& impl() { return static_cast(*this); } + public: + T operator()(AST_Node_Ptr x) { return static_cast(this)->fallback(x); } + // statements + T operator()(Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Ruleset_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Bubble_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Trace_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Media_Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Block_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Directive_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Keyframe_Rule_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Declaration_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Assignment_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Import_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Import_Stub_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Warning_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Error_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Debug_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Comment_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(If_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(For_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Each_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(While_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Return_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Content_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Extension_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Definition_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Mixin_Call_Ptr x) { return static_cast(this)->fallback(x); } + // expressions + T operator()(List_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Map_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Function_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Binary_Expression_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Unary_Expression_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Function_Call_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Function_Call_Schema_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Custom_Warning_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Custom_Error_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Variable_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Number_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Color_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Boolean_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(String_Schema_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(String_Constant_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(String_Quoted_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Condition_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Operator_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Negation_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Declaration_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Interpolation_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Media_Query_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Media_Query_Expression_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Query_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Null_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Parent_Selector_Ptr x) { return static_cast(this)->fallback(x); } + // parameters and arguments + T operator()(Parameter_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Parameters_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Argument_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Arguments_Ptr x) { return static_cast(this)->fallback(x); } + // selectors + T operator()(Selector_Schema_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Placeholder_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Element_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Class_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Id_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Attribute_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Pseudo_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Wrapped_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Compound_Selector_Ptr x){ return static_cast(this)->fallback(x); } + T operator()(Complex_Selector_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Selector_List_Ptr x) { return static_cast(this)->fallback(x); } + + template + T fallback(U x) { return T(); } + }; + +} + +#endif diff --git a/src/operators.cpp b/src/operators.cpp new file mode 100644 index 000000000..a1fd56235 --- /dev/null +++ b/src/operators.cpp @@ -0,0 +1,267 @@ +#include "sass.hpp" +#include "operators.hpp" + +namespace Sass { + + namespace Operators { + + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } // x/0 checked by caller + + inline double mod(double x, double y) { // x/0 checked by caller + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } else { + return std::fmod(x, y); + } + } + + typedef double (*bop)(double, double); + bop ops[Sass_OP::NUM_OPS] = { + 0, 0, // and, or + 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte + add, sub, mul, div, mod + }; + + /* static function, has no pstate or traces */ + bool eq(Expression_Obj lhs, Expression_Obj rhs) + { + // operation is undefined if one is not a number + if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); + // use compare operator from ast node + return *lhs == *rhs; + } + + /* static function, throws OperationError, has no pstate or traces */ + bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) + { + // can only compare numbers!? + Number_Obj l = Cast(lhs); + Number_Obj r = Cast(rhs); + // operation is undefined if one is not a number + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); + // use compare operator from ast node + return *l < *r; + } + + /* static functions, throws OperationError, has no pstate or traces */ + bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } + bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } + bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } + bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } + bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } + + /* colour math deprecation warning */ + void op_color_deprecation(enum Sass_OP op, std::string lsh, std::string rhs, const ParserState& pstate) + { + std::string op_str( + op == Sass_OP::ADD ? "plus" : + op == Sass_OP::DIV ? "div" : + op == Sass_OP::SUB ? "minus" : + op == Sass_OP::MUL ? "times" : "" + ); + + std::string msg("The operation `" + lsh + " " + op_str + " " + rhs + "` is deprecated and will be an error in future versions."); + std::string tail("Consider using Sass's color functions instead.\nhttp://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions"); + + deprecated(msg, tail, false, pstate); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + enum Sass_OP op = operand.operand; + + String_Quoted_Ptr lqstr = Cast(&lhs); + String_Quoted_Ptr rqstr = Cast(&rhs); + + std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); + std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); + + if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + + std::string sep; + switch (op) { + case Sass_OP::ADD: sep = ""; break; + case Sass_OP::SUB: sep = "-"; break; + case Sass_OP::DIV: sep = "/"; break; + case Sass_OP::EQ: sep = "=="; break; + case Sass_OP::NEQ: sep = "!="; break; + case Sass_OP::LT: sep = "<"; break; + case Sass_OP::GT: sep = ">"; break; + case Sass_OP::LTE: sep = "<="; break; + case Sass_OP::GTE: sep = ">="; break; + default: + throw Exception::UndefinedOperation(&lhs, &rhs, op); + break; + } + + if (op == Sass_OP::ADD) { + // create string that might be quoted on output (but do not unquote what we pass) + return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); + } + + // add whitespace around operator + // but only if result is not delayed + if (sep != "" && delayed == false) { + if (operand.ws_before) sep = " " + sep; + if (operand.ws_after) sep = sep + " "; + } + + if (op == Sass_OP::SUB || op == Sass_OP::DIV) { + if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); + if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); + } + + return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + + if (lhs.a() != rhs.a()) { + throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); + } + if ((op == Sass_OP::DIV || op == Sass_OP::MOD) && (!rhs.r() || !rhs.g() || !rhs.b())) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + + op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); + + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rhs.r()), + ops[op](lhs.g(), rhs.g()), + ops[op](lhs.b(), rhs.b()), + lhs.a()); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + double rval = rhs.value(); + + if (op == Sass_OP::MOD && rval == 0) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); + } + + if (op == Sass_OP::DIV && rval == 0) { + std::string result(lval ? "Infinity" : "NaN"); + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + size_t l_n_units = lhs.numerators.size(); + size_t l_d_units = lhs.numerators.size(); + size_t r_n_units = rhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + // optimize out the most common and simplest case + if (l_n_units == r_n_units && l_d_units == r_d_units) { + if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { + if (lhs.numerators == rhs.numerators) { + if (lhs.denominators == rhs.denominators) { + Number_Ptr v = SASS_MEMORY_COPY(&lhs); + v->value(ops[op](lval, rval)); + return v; + } + } + } + } + + Number_Obj v = SASS_MEMORY_COPY(&lhs); + + if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerators = rhs.numerators; + v->denominators = rhs.denominators; + } + + if (op == Sass_OP::MUL) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->reduce(); + } + else if (op == Sass_OP::DIV) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->reduce(); + } + else { + Number ln(lhs), rn(rhs); + ln.reduce(); rn.reduce(); + double f(rn.convert_factor(ln)); + v->value(ops[op](lval, rn.value() * f)); + } + + v->pstate(pstate); + return v.detach(); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + + switch (op) { + case Sass_OP::ADD: + case Sass_OP::MUL: { + op_color_deprecation(op, lhs.to_string(), rhs.to_string(opt), pstate); + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lval, rhs.r()), + ops[op](lval, rhs.g()), + ops[op](lval, rhs.b()), + rhs.a()); + } + case Sass_OP::SUB: + case Sass_OP::DIV: { + std::string color(rhs.to_string(opt)); + op_color_deprecation(op, lhs.to_string(), color, pstate); + return SASS_MEMORY_NEW(String_Quoted, + pstate, + lhs.to_string(opt) + + sass_op_separator(op) + + color); + } + default: break; + } + throw Exception::UndefinedOperation(&lhs, &rhs, op); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double rval = rhs.value(); + + if ((op == Sass_OP::DIV || op == Sass_OP::DIV) && rval == 0) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(lhs, rhs); + } + + op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); + + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rval), + ops[op](lhs.g(), rval), + ops[op](lhs.b(), rval), + lhs.a()); + } + + } + +} diff --git a/src/operators.hpp b/src/operators.hpp new file mode 100644 index 000000000..f89eb4ee2 --- /dev/null +++ b/src/operators.hpp @@ -0,0 +1,30 @@ +#ifndef SASS_OPERATORS_H +#define SASS_OPERATORS_H + +#include "values.hpp" +#include "sass/values.h" + +namespace Sass { + + namespace Operators { + + // equality operator using AST Node operator== + bool eq(Expression_Obj, Expression_Obj); + bool neq(Expression_Obj, Expression_Obj); + // specific operators based on cmp and eq + bool lt(Expression_Obj, Expression_Obj); + bool gt(Expression_Obj, Expression_Obj); + bool lte(Expression_Obj, Expression_Obj); + bool gte(Expression_Obj, Expression_Obj); + // arithmetic for all the combinations that matter + Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + + }; + +} + +#endif diff --git a/src/output.cpp b/src/output.cpp new file mode 100644 index 000000000..b2ca65e7e --- /dev/null +++ b/src/output.cpp @@ -0,0 +1,336 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "output.hpp" + +namespace Sass { + + Output::Output(Sass_Output_Options& opt) + : Inspect(Emitter(opt)), + charset(""), + top_nodes(0) + {} + + Output::~Output() { } + + void Output::fallback_impl(AST_Node_Ptr n) + { + return n->perform(this); + } + + void Output::operator()(Number_Ptr n) + { + // check for a valid unit here + // includes result for reporting + if (!n->is_valid_css_unit()) { + // should be handle in check_expression + throw Exception::InvalidValue({}, *n); + } + // use values to_string facility + std::string res = n->to_string(opt); + // output the final token + append_token(res, n); + } + + void Output::operator()(Import_Ptr imp) + { + top_nodes.push_back(imp); + } + + void Output::operator()(Map_Ptr m) + { + // should be handle in check_expression + throw Exception::InvalidValue({}, *m); + } + + OutputBuffer Output::get_buffer(void) + { + + Emitter emitter(opt); + Inspect inspect(emitter); + + size_t size_nodes = top_nodes.size(); + for (size_t i = 0; i < size_nodes; i++) { + top_nodes[i]->perform(&inspect); + inspect.append_mandatory_linefeed(); + } + + // flush scheduled outputs + // maybe omit semicolon if possible + inspect.finalize(wbuf.buffer.size() == 0); + // prepend buffer on top + prepend_output(inspect.output()); + // make sure we end with a linefeed + if (!ends_with(wbuf.buffer, opt.linefeed)) { + // if the output is not completely empty + if (!wbuf.buffer.empty()) append_string(opt.linefeed); + } + + // search for unicode char + for(const char& chr : wbuf.buffer) { + // skip all ascii chars + // static cast to unsigned to handle `char` being signed / unsigned + if (static_cast(chr) < 128) continue; + // declare the charset + if (output_style() != COMPRESSED) + charset = "@charset \"UTF-8\";" + + std::string(opt.linefeed); + else charset = "\xEF\xBB\xBF"; + // abort search + break; + } + + // add charset as first line, before comments and imports + if (!charset.empty()) prepend_string(charset); + + return wbuf; + + } + + void Output::operator()(Comment_Ptr c) + { + std::string txt = c->text()->to_string(opt); + // if (indentation && txt == "/**/") return; + bool important = c->is_important(); + if (output_style() != COMPRESSED || important) { + if (buffer().size() == 0) { + top_nodes.push_back(c); + } else { + in_comment = true; + append_indentation(); + c->text()->perform(this); + in_comment = false; + if (indentation == 0) { + append_mandatory_linefeed(); + } else { + append_optional_linefeed(); + } + } + } + } + + void Output::operator()(Ruleset_Ptr r) + { + Selector_Obj s = r->selector(); + Block_Obj b = r->block(); + + // Filter out rulesets that aren't printable (process its children though) + if (!Util::isPrintable(r, output_style())) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + const Statement_Obj& stm = b->at(i); + if (Cast(stm)) { + if (!Cast(stm)) { + stm->perform(this); + } + } + } + return; + } + + if (output_style() == NESTED) indentation += r->tabs(); + if (opt.source_comments) { + std::stringstream ss; + append_indentation(); + std::string path(File::abs2rel(r->pstate().path)); + ss << "/* line " << r->pstate().line + 1 << ", " << path << " */"; + append_string(ss.str()); + append_optional_linefeed(); + } + scheduled_crutch = s; + if (s) s->perform(this); + append_scope_opener(b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + bool bPrintExpression = true; + // Check print conditions + if (Declaration_Ptr dec = Cast(stm)) { + if (String_Constant_Ptr valConst = Cast(dec->value())) { + std::string val(valConst->value()); + if (String_Quoted_Ptr qstr = Cast(valConst)) { + if (!qstr->quote_mark() && val.empty()) { + bPrintExpression = false; + } + } + } + else if (List_Ptr list = Cast(dec->value())) { + bool all_invisible = true; + for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { + Expression_Ptr item = list->at(list_i); + if (!item->is_invisible()) all_invisible = false; + } + if (all_invisible && !list->is_bracketed()) bPrintExpression = false; + } + } + // Print if OK + if (bPrintExpression) { + stm->perform(this); + } + } + if (output_style() == NESTED) indentation -= r->tabs(); + append_scope_closer(b); + + } + void Output::operator()(Keyframe_Rule_Ptr r) + { + Block_Obj b = r->block(); + Selector_Obj v = r->name(); + + if (!v.isNull()) { + v->perform(this); + } + + if (!b) { + append_colon_separator(); + return; + } + + append_scope_opener(); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + append_scope_closer(); + } + + void Output::operator()(Supports_Block_Ptr f) + { + if (f->is_invisible()) return; + + Supports_Condition_Obj c = f->condition(); + Block_Obj b = f->block(); + + // Filter out feature blocks that aren't printable (process its children though) + if (!Util::isPrintable(f, output_style())) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (Cast(stm)) { + stm->perform(this); + } + } + return; + } + + if (output_style() == NESTED) indentation += f->tabs(); + append_indentation(); + append_token("@supports", f); + append_mandatory_space(); + c->perform(this); + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + + if (output_style() == NESTED) indentation -= f->tabs(); + + append_scope_closer(); + + } + + void Output::operator()(Media_Block_Ptr m) + { + if (m->is_invisible()) return; + + Block_Obj b = m->block(); + + // Filter out media blocks that aren't printable (process its children though) + if (!Util::isPrintable(m, output_style())) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (Cast(stm)) { + stm->perform(this); + } + } + return; + } + if (output_style() == NESTED) indentation += m->tabs(); + append_indentation(); + append_token("@media", m); + append_mandatory_space(); + in_media_block = true; + m->media_queries()->perform(this); + in_media_block = false; + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->at(i)) { + Statement_Obj stm = b->at(i); + stm->perform(this); + } + if (i < L - 1) append_special_linefeed(); + } + + if (output_style() == NESTED) indentation -= m->tabs(); + append_scope_closer(); + } + + void Output::operator()(Directive_Ptr a) + { + std::string kwd = a->keyword(); + Selector_Obj s = a->selector(); + Expression_Obj v = a->value(); + Block_Obj b = a->block(); + + append_indentation(); + append_token(kwd, a); + if (s) { + append_mandatory_space(); + in_wrapped = true; + s->perform(this); + in_wrapped = false; + } + if (v) { + append_mandatory_space(); + // ruby sass bug? should use options? + append_token(v->to_string(/* opt */), v); + } + if (!b) { + append_delimiter(); + return; + } + + if (b->is_invisible() || b->length() == 0) { + append_optional_space(); + return append_string("{}"); + } + + append_scope_opener(); + + bool format = kwd != "@font-face";; + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + stm->perform(this); + if (i < L - 1 && format) append_special_linefeed(); + } + + append_scope_closer(); + } + + void Output::operator()(String_Quoted_Ptr s) + { + if (s->quote_mark()) { + append_token(quote(s->value(), s->quote_mark()), s); + } else if (!in_comment) { + append_token(string_to_output(s->value()), s); + } else { + append_token(s->value(), s); + } + } + + void Output::operator()(String_Constant_Ptr s) + { + std::string value(s->value()); + if (s->can_compress_whitespace() && output_style() == COMPRESSED) { + value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); + } + if (!in_comment && !in_custom_property) { + append_token(string_to_output(value), s); + } else { + append_token(value, s); + } + } + +} diff --git a/src/output.hpp b/src/output.hpp new file mode 100644 index 000000000..c460b13fe --- /dev/null +++ b/src/output.hpp @@ -0,0 +1,54 @@ +#ifndef SASS_OUTPUT_H +#define SASS_OUTPUT_H + +#include +#include + +#include "util.hpp" +#include "inspect.hpp" +#include "operation.hpp" + +namespace Sass { + class Context; + + // Refactor to make it generic to find linefeed (look behind) + inline bool ends_with(std::string const & value, std::string const & ending) + { + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + } + + class Output : public Inspect { + protected: + using Inspect::operator(); + + public: + Output(Sass_Output_Options& opt); + virtual ~Output(); + + protected: + std::string charset; + std::vector top_nodes; + + public: + OutputBuffer get_buffer(void); + + virtual void operator()(Map_Ptr); + virtual void operator()(Ruleset_Ptr); + virtual void operator()(Supports_Block_Ptr); + virtual void operator()(Media_Block_Ptr); + virtual void operator()(Directive_Ptr); + virtual void operator()(Keyframe_Rule_Ptr); + virtual void operator()(Import_Ptr); + virtual void operator()(Comment_Ptr); + virtual void operator()(Number_Ptr); + virtual void operator()(String_Quoted_Ptr); + virtual void operator()(String_Constant_Ptr); + + void fallback_impl(AST_Node_Ptr n); + + }; + +} + +#endif diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 000000000..28fe02244 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,3137 @@ +#include "sass.hpp" +#include "parser.hpp" +#include "file.hpp" +#include "inspect.hpp" +#include "constants.hpp" +#include "util.hpp" +#include "prelexer.hpp" +#include "color_maps.hpp" +#include "sass/functions.h" +#include "error_handling.hpp" + +// Notes about delayed: some ast nodes can have delayed evaluation so +// they can preserve their original semantics if needed. This is most +// prominently exhibited by the division operation, since it is not +// only a valid operation, but also a valid css statement (i.e. for +// fonts, as in `16px/24px`). When parsing lists and expression we +// unwrap single items from lists and other operations. A nested list +// must not be delayed, only the items of the first level sometimes +// are delayed (as with argument lists). To achieve this we need to +// pass status to the list parser, so this can be set correctly. +// Another case with delayed values are colors. In compressed mode +// only processed values get compressed (other are left as written). + +#include +#include +#include +#include + +namespace Sass { + using namespace Constants; + using namespace Prelexer; + + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate, traces); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate, traces); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = end ? end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + void Parser::advanceToNextToken() { + lex < css_comments >(false); + // advance to position + pstate += pstate.offset; + pstate.offset.column = 0; + pstate.offset.line = 0; + } + + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); + // ToDo: ruby sass errors on parent references + // ToDo: remap the source-map entries somehow + return p.parse_selector_list(false); + } + + bool Parser::peek_newline(const char* start) + { + return peek_linefeed(start ? start : position) + && ! peek_css>(start); + } + + Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + Parser p(ctx, pstate, traces); + p.source = source ? source : t.begin; + p.position = t.begin ? t.begin : p.source; + p.end = t.end ? t.end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + /* main entry point to parse root block */ + Block_Obj Parser::parse() + { + + // consume unicode BOM + read_bom(); + + // scan the input to find invalid utf8 sequences + const char* it = utf8::find_invalid(position, end); + + // report invalid utf8 + if (it != end) { + pstate += Offset::init(position, it); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); + } + + // create a block AST node to hold children + Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // check seems a bit esoteric but works + if (ctx.resources.size() == 1) { + // apply headers only on very first include + ctx.apply_custom_headers(root, path, pstate); + } + + // parse children nodes + block_stack.push_back(root); + parse_block_nodes(true); + block_stack.pop_back(); + + // update final position + root->update_pstate(pstate); + + if (position != end) { + css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); + } + + return root; + } + + + // convenience function for block parsing + // will create a new block ad-hoc for you + // this is the base block parsing function + Block_Obj Parser::parse_css_block(bool is_root) + { + + // parse comments before block + // lex < optional_css_comments >(); + + // lex mandatory opener or error out + if (!lex_css < exactly<'{'> >()) { + css_error("Invalid CSS", " after ", ": expected \"{\", was "); + } + // create new block and push to the selector stack + Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); + block_stack.push_back(block); + + if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + + if (!lex_css < exactly<'}'> >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + + // update for end position + // this seems to be done somewhere else + // but that fixed selector schema issue + // block->update_pstate(pstate); + + // parse comments after block + // lex < optional_css_comments >(); + + block_stack.pop_back(); + + return block; + } + + // convenience function for block parsing + // will create a new block ad-hoc for you + // also updates the `in_at_root` flag + Block_Obj Parser::parse_block(bool is_root) + { + return parse_css_block(is_root); + } + + // the main block parsing function + // parses stuff between `{` and `}` + bool Parser::parse_block_nodes(bool is_root) + { + + // loop until end of string + while (position < end) { + + // we should be able to refactor this + parse_block_comments(); + lex < css_whitespace >(); + + if (lex < exactly<';'> >()) continue; + if (peek < end_of_file >()) return true; + if (peek < exactly<'}'> >()) return true; + + if (parse_block_node(is_root)) continue; + + parse_block_comments(); + + if (lex_css < exactly<';'> >()) continue; + if (peek_css < end_of_file >()) return true; + if (peek_css < exactly<'}'> >()) return true; + + // illegal sass + return false; + } + // return success + return true; + } + + // parser for a single node in a block + // semicolons must be lexed beforehand + bool Parser::parse_block_node(bool is_root) { + + Block_Obj block = block_stack.back(); + + parse_block_comments(); + + // throw away white-space + // includes line comments + lex < css_whitespace >(); + + Lookahead lookahead_result; + + // also parse block comments + + // first parse everything that is allowed in functions + if (lex < variable >(true)) { block->append(parse_assignment()); } + else if (lex < kwd_err >(true)) { block->append(parse_error()); } + else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } + else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } + else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } + else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } + else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } + else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } + else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } + + // parse imports to process later + else if (lex < kwd_import >(true)) { + Scope parent = stack.empty() ? Scope::Rules : stack.back(); + if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { + if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 + error("Import directives may not be used within control directives or mixins."); + } + } + // this puts the parsed doc into sheets + // import stub will fetch this in expand + Import_Obj imp = parse_import(); + // if it is a url, we only add the statement + if (!imp->urls().empty()) block->append(imp); + // process all resources now (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + else if (lex < kwd_extend >(true)) { + Lookahead lookahead = lookahead_for_include(position); + if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); + Selector_List_Obj target; + if (!lookahead.has_interpolants) { + target = parse_selector_list(true); + } + else { + target = SASS_MEMORY_NEW(Selector_List, pstate); + target->schema(parse_selector_schema(lookahead.found, true)); + } + + block->append(SASS_MEMORY_NEW(Extension, pstate, target)); + } + + // selector may contain interpolations which need delayed evaluation + else if ( + !(lookahead_result = lookahead_for_selector(position)).error && + !lookahead_result.is_custom_property + ) + { + block->append(parse_ruleset(lookahead_result)); + } + + // parse multiple specific keyword directives + else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } + else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } + else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } + else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } + else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } + else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } + + // ignore the @charset directive for now + else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } + + // generic at keyword (keep last) + else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } + else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } + else if (lex< at_keyword >(true)) { block->append(parse_directive()); } + + else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { + lex< css_whitespace >(); + if (position >= end) return true; + css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); + } + // parse a declaration + else + { + // ToDo: how does it handle parse errors? + // maybe we are expected to parse something? + Declaration_Obj decl = parse_declaration(); + decl->tabs(indentation); + block->append(decl); + // maybe we have a "sub-block" + if (peek< exactly<'{'> >()) { + if (decl->is_indented()) ++ indentation; + // parse a propset that rides on the declaration's property + stack.push_back(Scope::Properties); + decl->block(parse_block()); + stack.pop_back(); + if (decl->is_indented()) -- indentation; + } + } + // something matched + return true; + } + // EO parse_block_nodes + + // parse imports inside the + Import_Obj Parser::parse_import() + { + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + std::vector> to_import; + bool first = true; + do { + while (lex< block_comment >()); + if (lex< quoted_string >()) { + to_import.push_back(std::pair(std::string(lexed), 0)); + } + else if (lex< uri_prefix >()) { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); + + if (lex< quoted_string >()) { + Expression_Obj quoted_url = parse_string(); + args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); + } + else if (String_Obj string_url = parse_url_function_argument()) { + args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); + } + else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { + Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); + args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); + } + else { + error("malformed URL"); + } + if (!lex< exactly<')'> >()) error("URI is missing ')'"); + to_import.push_back(std::pair("", result)); + } + else { + if (first) error("@import directive requires a url or quoted path"); + else error("expecting another url or quoted path in @import list"); + } + first = false; + } while (lex_css< exactly<','> >()); + + if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { + List_Obj import_queries = parse_media_queries(); + imp->import_queries(import_queries); + } + + for(auto location : to_import) { + if (location.second) { + imp->urls().push_back(location.second); + } + // check if custom importers want to take over the handling + else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { + // nobody wants it, so we do our import + ctx.import_url(imp, location.first, path); + } + } + + return imp; + } + + Definition_Obj Parser::parse_definition(Definition::Type which_type) + { + std::string which_str(lexed); + if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); + std::string name(Util::normalize_underscores(lexed)); + if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) + { error("Invalid function name \"" + name + "\"."); } + ParserState source_position_of_def = pstate; + Parameters_Obj params = parse_parameters(); + if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); + else stack.push_back(Scope::Function); + Block_Obj body = parse_block(); + stack.pop_back(); + return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); + } + + Parameters_Obj Parser::parse_parameters() + { + Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do { + if (peek< exactly<')'> >()) break; + params->append(parse_parameter()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + } + return params; + } + + Parameter_Obj Parser::parse_parameter() + { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); + } + while (lex< alternatives < spaces, block_comment > >()); + lex < variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState pos = pstate; + Expression_Obj val; + bool is_rest = false; + while (lex< alternatives < spaces, block_comment > >()); + if (lex< exactly<':'> >()) { // there's a default value + while (lex< block_comment >()); + val = parse_space_list(); + } + else if (lex< exactly< ellipsis > >()) { + is_rest = true; + } + return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); + } + + Arguments_Obj Parser::parse_arguments() + { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do { + if (peek< exactly<')'> >()) break; + args->append(parse_argument()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + return args; + } + + Argument_Obj Parser::parse_argument() + { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { + position += 2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + Argument_Obj arg; + if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { + lex_css< variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState p = pstate; + lex_css< exactly<':'> >(); + Expression_Obj val = parse_space_list(); + arg = SASS_MEMORY_NEW(Argument, p, val, name); + } + else { + bool is_arglist = false; + bool is_keyword = false; + Expression_Obj val = parse_space_list(); + List_Ptr l = Cast(val); + if (lex_css< exactly< ellipsis > >()) { + if (val->concrete_type() == Expression::MAP || ( + (l != NULL && l->separator() == SASS_HASH) + )) is_keyword = true; + else is_arglist = true; + } + arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); + } + return arg; + } + + Assignment_Obj Parser::parse_assignment() + { + std::string name(Util::normalize_underscores(lexed)); + ParserState var_source_position = pstate; + if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); + if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj val; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.has_interpolants && lookahead.found) { + val = parse_value_schema(lookahead.found); + } else { + val = parse_list(); + } + bool is_default = false; + bool is_global = false; + while (peek< alternatives < default_flag, global_flag > >()) { + if (lex< default_flag >()) is_default = true; + else if (lex< global_flag >()) is_global = true; + } + return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); + } + + // a ruleset connects a selector and a block + Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) + { + NESTING_GUARD(nestings); + // inherit is_root from parent block + Block_Obj parent = block_stack.back(); + bool is_root = parent && parent->is_root(); + // make sure to move up the the last position + lex < optional_css_whitespace >(false, true); + // create the connector object (add parts later) + Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); + // parse selector static or as schema to be evaluated later + if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + else { + Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); + list->schema(parse_selector_schema(lookahead.position, false)); + ruleset->selector(list); + } + // then parse the inner block + stack.push_back(Scope::Rules); + ruleset->block(parse_block()); + stack.pop_back(); + // update for end position + ruleset->update_pstate(pstate); + ruleset->block()->update_pstate(pstate); + // need this info for sanity checks + ruleset->is_root(is_root); + // return AST Node + return ruleset; + } + + // parse a selector schema that will be evaluated in the eval stage + // uses a string schema internally to do the actual schema handling + // in the eval stage we will be re-parse it into an actual selector + Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) + { + NESTING_GUARD(nestings); + // move up to the start + lex< optional_spaces >(); + const char* i = position; + // selector schema re-uses string schema implementation + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + // the selector schema is pretty much just a wrapper for the string schema + Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + selector_schema->connect_parent(chroot == false); + selector_schema->media_block(last_media_block); + + // process until end + while (i < end_of_selector) { + // try to parse mutliple interpolants + if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { + // accumulate the preceding segment if the position has advanced + if (i < p) { + std::string parsed(i, p); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + schema->append(str); + } + + // skip over all nested inner interpolations up to our own delimiter + const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); + // check if the interpolation never ends of only contains white-space (error out) + if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { + position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + // pass inner expression to the parser to resolve nested interpolations + pstate.add(p, p+2); + Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); + // set status on the list expression + interpolant->is_interpolant(true); + // schema->has_interpolants(true); + // add to the string schema + schema->append(interpolant); + // advance parser state + pstate.add(p+2, j); + // advance position + i = j; + } + // no more interpolants have been found + // add the last segment if there is one + else { + // make sure to add the last bits of the string up to the end (if any) + if (i < end_of_selector) { + std::string parsed(i, end_of_selector); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + i = end_of_selector; + schema->append(str); + } + // exit loop + } + } + // EO until eos + + // update position + position = i; + + // update for end position + selector_schema->update_pstate(pstate); + schema->update_pstate(pstate); + + after_token = before_token = pstate; + + // return parsed result + return selector_schema.detach(); + } + // EO parse_selector_schema + + void Parser::parse_charset_directive() + { + lex < + sequence < + quoted_string, + optional_spaces, + exactly <';'> + > + >(); + } + + // called after parsing `kwd_include_directive` + Mixin_Call_Obj Parser::parse_include_directive() + { + // lex identifier into `lexed` var + lex_identifier(); // may error out + // normalize underscores to hyphens + std::string name(Util::normalize_underscores(lexed)); + // create the initial mixin call object + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); + // parse mandatory arguments + call->arguments(parse_arguments()); + // parse optional block + if (peek < exactly <'{'> >()) { + call->block(parse_block()); + } + // return ast node + return call.detach(); + } + // EO parse_include_directive + + // parse a list of complex selectors + // this is the main entry point for most + Selector_List_Obj Parser::parse_selector_list(bool chroot) + { + bool reloop; + bool had_linefeed = false; + NESTING_GUARD(nestings); + Complex_Selector_Obj sel; + Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); + group->media_block(last_media_block); + + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + + do { + reloop = false; + + had_linefeed = had_linefeed || peek_newline(); + + if (peek_css< alternatives < class_char < selector_list_delims > > >()) + break; // in case there are superfluous commas at the end + + // now parse the complex selector + sel = parse_complex_selector(chroot); + + if (!sel) return group.detach(); + + sel->has_line_feed(had_linefeed); + + had_linefeed = false; + + while (peek_css< exactly<','> >()) + { + lex< css_comments >(false); + // consume everything up and including the comma separator + reloop = lex< exactly<','> >() != 0; + // remember line break (also between some commas) + had_linefeed = had_linefeed || peek_newline(); + // remember line break (also between some commas) + } + group->append(sel); + } + while (reloop); + while (lex_css< kwd_optional >()) { + group->is_optional(true); + } + // update for end position + group->update_pstate(pstate); + if (sel) sel->last()->has_line_break(false); + return group.detach(); + } + // EO parse_selector_list + + // a complex selector combines a compound selector with another + // complex selector, with one of four combinator operations. + // the compound selector (head) is optional, since the combinator + // can come first in the whole selector sequence (like `> DIV'). + Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) + { + + NESTING_GUARD(nestings); + String_Obj reference = 0; + lex < block_comment >(); + advanceToNextToken(); + Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); + + if (peek < end_of_file >()) return 0; + + // parse the left hand side + Compound_Selector_Obj lhs; + // special case if it starts with combinator ([+~>]) + if (!peek_css< class_char < selector_combinator_ops > >()) { + // parse the left hand side + lhs = parse_compound_selector(); + } + + + // parse combinator between lhs and rhs + Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; + if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; + else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; + else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; + else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { + // comments are allowed, but not spaces? + combinator = Complex_Selector::REFERENCE; + if (!lex < re_reference_combinator >()) return 0; + reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? + } + + if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; + + // lex < block_comment >(); + sel->head(lhs); + sel->combinator(combinator); + sel->media_block(last_media_block); + + if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); + // has linfeed after combinator? + sel->has_line_break(peek_newline()); + // sel->has_line_feed(has_line_feed); + + // check if we got the abort condition (ToDo: optimize) + if (!peek_css< class_char < complex_selector_delims > >()) { + // parse next selector in sequence + sel->tail(parse_complex_selector(true)); + } + + // add a parent selector if we are not in a root + // also skip adding parent ref if we only have refs + if (!sel->has_parent_ref() && !chroot) { + // create the objects to wrap parent selector reference + Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); + Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); + parent->media_block(last_media_block); + head->media_block(last_media_block); + // add simple selector + head->append(parent); + // selector may not have any head yet + if (!sel->head()) { sel->head(head); } + // otherwise we need to create a new complex selector and set the old one as its tail + else { + sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); + sel->media_block(last_media_block); + } + // peek for linefeed and remember result on head + // if (peek_newline()) head->has_line_break(true); + } + + sel->update_pstate(pstate); + // complex selector + return sel; + } + // EO parse_complex_selector + + // parse one compound selector, which is basically + // a list of simple selectors (directly adjacent) + // lex them exactly (without skipping white-space) + Compound_Selector_Obj Parser::parse_compound_selector() + { + // init an empty compound selector wrapper + Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); + seq->media_block(last_media_block); + + // skip initial white-space + lex< css_whitespace >(); + + // parse list + while (true) + { + // remove all block comments (don't skip white-space) + lex< delimited_by< slash_star, star_slash, false > >(false); + // parse functional + if (match < re_pseudo_selector >()) + { + seq->append(parse_simple_selector()); + } + // parse parent selector + else if (lex< exactly<'&'> >(false)) + { + // this produces a linefeed!? + seq->has_parent_reference(true); + seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); + // parent selector only allowed at start + // upcoming Sass may allow also trailing + if (seq->length() > 1) { + ParserState state(pstate); + Simple_Selector_Obj cur = (*seq)[seq->length()-1]; + Simple_Selector_Obj prev = (*seq)[seq->length()-2]; + std::string sel(prev->to_string({ NESTED, 5 })); + std::string found(cur->to_string({ NESTED, 5 })); + if (lex < identifier >()) { found += std::string(lexed); } + error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" + "\"" + found + "\" may only be used at the beginning of a compound selector.", state); + } + } + // parse type selector + else if (lex< re_type_selector >(false)) + { + seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); + } + // peek for abort conditions + else if (peek< spaces >()) break; + else if (peek< end_of_file >()) { break; } + else if (peek_css < class_char < selector_combinator_ops > >()) break; + else if (peek_css < class_char < complex_selector_delims > >()) break; + // otherwise parse another simple selector + else { + Simple_Selector_Obj sel = parse_simple_selector(); + if (!sel) return 0; + seq->append(sel); + } + } + + if (seq && !peek_css>>()) { + seq->has_line_break(peek_newline()); + } + + // EO while true + return seq; + + } + // EO parse_compound_selector + + Simple_Selector_Obj Parser::parse_simple_selector() + { + lex < css_comments >(false); + if (lex< class_name >()) { + return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); + } + else if (lex< id_name >()) { + return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); + } + else if (lex< alternatives < variable, number, static_reference_combinator > >()) { + return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); + } + else if (peek< pseudo_not >()) { + return parse_negated_selector(); + } + else if (peek< re_pseudo_selector >()) { + return parse_pseudo_selector(); + } + else if (peek< exactly<':'> >()) { + return parse_pseudo_selector(); + } + else if (lex < exactly<'['> >()) { + return parse_attribute_selector(); + } + else if (lex< placeholder >()) { + Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); + sel->media_block(last_media_block); + return sel; + } + else { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + // failed + return 0; + } + + Wrapped_Selector_Obj Parser::parse_negated_selector() + { + lex< pseudo_not >(); + std::string name(lexed); + ParserState nsource_position = pstate; + Selector_List_Obj negated = parse_selector_list(true); + if (!lex< exactly<')'> >()) { + error("negated selector is missing ')'"); + } + name.erase(name.size() - 1); + return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); + } + + // a pseudo selector often starts with one or two colons + // it can contain more selectors inside parentheses + Simple_Selector_Obj Parser::parse_pseudo_selector() { + if (lex< sequence< + optional < pseudo_prefix >, + // we keep the space within the name, strange enough + // ToDo: refactor output to schedule the space for it + // or do we really want to keep the real white-space? + sequence< identifier, optional < block_comment >, exactly<'('> > + > >()) + { + + std::string name(lexed); + name.erase(name.size() - 1); + ParserState p = pstate; + + // specially parse static stuff + // ToDo: really everything static? + if (peek_css < + sequence < + alternatives < + static_value, + binomial + >, + optional_css_whitespace, + exactly<')'> + > + >() + ) { + lex_css< alternatives < static_value, binomial > >(); + String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (lex_css< exactly<')'> >()) { + expr->can_compress_whitespace(true); + return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); + } + } + else if (Selector_List_Obj wrapped = parse_selector_list(true)) { + if (wrapped && lex_css< exactly<')'> >()) { + return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); + } + } + + } + // EO if pseudo selector + + else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { + return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); + } + else if(lex < pseudo_prefix >()) { + css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + } + + css_error("Invalid CSS", " after ", ": expected \")\", was "); + + // unreachable statement + return 0; + } + + const char* Parser::re_attr_sensitive_close(const char* src) + { + return alternatives < exactly<']'>, exactly<'/'> >(src); + } + + const char* Parser::re_attr_insensitive_close(const char* src) + { + return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + } + + Attribute_Selector_Obj Parser::parse_attribute_selector() + { + ParserState p = pstate; + if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); + std::string name(lexed); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); + } + if (!lex_css< alternatives< exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match > >()) { + error("invalid operator in attribute selector for " + name); + } + std::string matcher(lexed); + + String_Obj value = 0; + if (lex_css< identifier >()) { + value = SASS_MEMORY_NEW(String_Constant, p, lexed); + } + else if (lex_css< quoted_string >()) { + value = parse_interpolated_chunk(lexed, true); // needed! + } + else { + error("expected a string constant or identifier in attribute selector for " + name); + } + + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); + } + error("unterminated attribute selector for " + name); + return NULL; // to satisfy compilers (error must not return) + } + + /* parse block comment and add to block */ + void Parser::parse_block_comments() + { + Block_Obj block = block_stack.back(); + + while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; + // flag on second param is to skip loosely over comments + String_Obj contents = parse_interpolated_chunk(lexed, true, false); + block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); + } + } + + Declaration_Obj Parser::parse_declaration() { + String_Obj prop; + bool is_custom_property = false; + if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; + prop = parse_identifier_schema(); + } + else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; + prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + else { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + bool is_indented = true; + const std::string property(lexed); + if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); + if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); + if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty + if (is_custom_property) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); + } + lex < css_comments >(false); + if (peek_css< static_value >()) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); + } + else { + Expression_Obj value; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.found) { + if (lookahead.has_interpolants) { + value = parse_value_schema(lookahead.found); + } else { + value = parse_list(DELAYED); + } + } + else { + value = parse_list(DELAYED); + if (List_Ptr list = Cast(value)) { + if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + } + lex < css_comments >(false); + Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); + decl->is_indented(is_indented); + decl->update_pstate(pstate); + return decl; + } + } + + // parse +/- and return false if negative + // this is never hit via spec tests + bool Parser::parse_number_prefix() + { + bool positive = true; + while(true) { + if (lex < block_comment >()) continue; + if (lex < number_prefix >()) continue; + if (lex < exactly < '-' > >()) { + positive = !positive; + continue; + } + break; + } + return positive; + } + + Expression_Obj Parser::parse_map() + { + NESTING_GUARD(nestings); + Expression_Obj key = parse_list(); + List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); + + // it's not a map so return the lexed value as a list value + if (!lex_css< exactly<':'> >()) + { return key; } + + List_Obj l = Cast(key); + if (l && l->separator() == SASS_COMMA) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + + Expression_Obj value = parse_space_list(); + + map->append(key); + map->append(value); + + while (lex_css< exactly<','> >()) + { + // allow trailing commas - #495 + if (peek_css< exactly<')'> >(position)) + { break; } + + key = parse_space_list(); + + if (!(lex< exactly<':'> >())) + { css_error("Invalid CSS", " after ", ": expected \":\", was "); } + + value = parse_space_list(); + + map->append(key); + map->append(value); + } + + ParserState ps = map->pstate(); + ps.offset = pstate - ps + pstate.offset; + map->pstate(ps); + + return map; + } + + Expression_Obj Parser::parse_bracket_list() + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); + } + + bool has_paren = peek_css< exactly<'('> >() != NULL; + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + List_Obj l = Cast(list); + if (!l || l->is_bracketed() || has_paren) { + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); + bracketed_list->append(list); + return bracketed_list; + } + l->is_bracketed(true); + return l; + } + + // if we got so far, we actually do have a comma list + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); + // wrap the first expression + bracketed_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + bracketed_list->append(parse_space_list()); + } + // return the list + return bracketed_list; + } + + // parse list returns either a space separated list, + // a comma separated list or any bare expression found. + // so to speak: we unwrap items from lists if possible here! + Expression_Obj Parser::parse_list(bool delayed) + { + NESTING_GUARD(nestings); + return parse_comma_list(delayed); + } + + // will return singletons unwrapped + Expression_Obj Parser::parse_comma_list(bool delayed) + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0); + } + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + // set_delay doesn't apply to list children + // so this will only undelay single values + if (!delayed) list->set_delayed(false); + return list; + } + + // if we got so far, we actually do have a comma list + List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); + // wrap the first expression + comma_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + comma_list->append(parse_space_list()); + } + // return the list + return comma_list; + } + // EO parse_comma_list + + // will return singletons unwrapped + Expression_Obj Parser::parse_space_list() + { + NESTING_GUARD(nestings); + Expression_Obj disj1 = parse_disjunction(); + // if it's a singleton, return it (don't wrap it) + if (peek_css< space_list_terminator >(position) + ) { + return disj1; } + + List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); + space_list->append(disj1); + + while ( + !(peek_css< space_list_terminator >(position)) && + peek_css< optional_css_whitespace >() != end + ) { + // the space is parsed implicitly? + space_list->append(parse_disjunction()); + } + // return the list + return space_list; + } + // EO parse_space_list + + // parse logical OR operation + Expression_Obj Parser::parse_disjunction() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side conjunction + Expression_Obj conj = parse_conjunction(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_or >()) + operands.push_back(parse_conjunction()); + // if it's a singleton, return it directly + if (operands.size() == 0) return conj; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_disjunction + + // parse logical AND operation + Expression_Obj Parser::parse_conjunction() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side relation + Expression_Obj rel = parse_relation(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_and >()) { + operands.push_back(parse_relation()); + } + // if it's a singleton, return it directly + if (operands.size() == 0) return rel; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_conjunction + + // parse comparison operations + Expression_Obj Parser::parse_relation() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side expression + Expression_Obj lhs = parse_expression(); + std::vector operands; + std::vector operators; + // if it's a singleton, return it (don't wrap it) + while (peek< alternatives < + kwd_eq, + kwd_neq, + kwd_gte, + kwd_gt, + kwd_lte, + kwd_lt + > >(position)) + { + // is directly adjancent to expression? + bool left_ws = peek < css_comments >() != NULL; + // parse the operator + enum Sass_OP op + = lex() ? Sass_OP::EQ + : lex() ? Sass_OP::NEQ + : lex() ? Sass_OP::GTE + : lex() ? Sass_OP::LTE + : lex() ? Sass_OP::GT + : lex() ? Sass_OP::LT + // we checked the possibilities on top of fn + : Sass_OP::EQ; + // is directly adjacent to expression? + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ op, left_ws, right_ws }); + operands.push_back(parse_expression()); + } + // we are called recursively for list, so we first + // fold inner binary expression which has delayed + // correctly set to zero. After folding we also unwrap + // single nested items. So we cannot set delay on the + // returned result here, as we have lost nestings ... + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // parse_relation + + // parse expression valid for operations + // called from parse_relation + // called from parse_for_directive + // called from parse_media_expression + // parse addition and subtraction operations + Expression_Obj Parser::parse_expression() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parses multiple add and subtract operations + // NOTE: make sure that identifiers starting with + // NOTE: dashes do NOT count as subtract operation + Expression_Obj lhs = parse_operators(); + // if it's a singleton, return it (don't wrap it) + if (!(peek_css< exactly<'+'> >(position) || + // condition is a bit misterious, but some combinations should not be counted as operations + (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || + (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || + peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) + { return lhs; } + + std::vector operands; + std::vector operators; + bool left_ws = peek < css_comments >() != NULL; + while ( + lex_css< exactly<'+'> >() || + + ( + ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) + && lex_css< sequence< negate< digit >, exactly<'-'> > >() + ) + + ) { + + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); + operands.push_back(parse_operators()); + left_ws = peek < css_comments >() != NULL; + } + + if (operands.size() == 0) return lhs; + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + + // parse addition and subtraction operations + Expression_Obj Parser::parse_operators() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + Expression_Obj factor = parse_factor(); + // if it's a singleton, return it (don't wrap it) + std::vector operands; // factors + std::vector operators; // ops + // lex operations to apply to lhs + const char* left_ws = peek < css_comments >(); + while (lex_css< class_char< static_ops > >()) { + const char* right_ws = peek < css_comments >(); + switch(*lexed.begin) { + case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; + case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; + case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; + default: throw std::runtime_error("unknown static op parsed"); + } + operands.push_back(parse_factor()); + left_ws = peek < css_comments >(); + } + // operands and operators to binary expression + Expression_Obj ex = fold_operands(factor, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_operators + + + // called from parse_operators + // called from parse_value_schema + Expression_Obj Parser::parse_factor() + { + NESTING_GUARD(nestings); + lex < css_comments >(false); + if (lex_css< exactly<'('> >()) { + // parse_map may return a list + Expression_Obj value = parse_map(); + // lex the expected closing parenthesis + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); + // expression can be evaluated + return value; + } + else if (lex_css< exactly<'['> >()) { + // explicit bracketed + Expression_Obj value = parse_bracket_list(); + // lex the expected closing square bracket + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); + return value; + } + // string may be interpolated + // if (lex< quoted_string >()) { + // return &parse_string(); + // } + else if (peek< ie_property >()) { + return parse_ie_property(); + } + else if (peek< ie_keyword_arg >()) { + return parse_ie_keyword_arg(); + } + else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { + return parse_calc_function(); + } + else if (lex < functional_schema >()) { + return parse_function_call_schema(); + } + else if (lex< identifier_schema >()) { + String_Obj string = parse_identifier_schema(); + if (String_Schema_Ptr schema = Cast(string)) { + if (lex < exactly < '(' > >()) { + schema->append(parse_list()); + lex < exactly < ')' > >(); + } + } + return string; + } + else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { + return parse_url_function_string(); + } + else if (peek< re_functional >()) { + return parse_function_call(); + } + else if (lex< exactly<'+'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'-'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'/'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< sequence< kwd_not > >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + // this whole branch is never hit via spec tests + else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { + if (parse_number_prefix()) return parse_value(); // prefix is positive + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); + if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else { + return parse_value(); + } + } + + bool number_has_zero(const std::string& parsed) + { + size_t L = parsed.length(); + return !( (L > 0 && parsed.substr(0, 1) == ".") || + (L > 1 && parsed.substr(0, 2) == "0.") || + (L > 1 && parsed.substr(0, 2) == "-.") || + (L > 2 && parsed.substr(0, 3) == "-0.") ); + } + + Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "", + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "%", + true); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) + { + size_t L = parsed.length(); + size_t num_pos = parsed.find_first_not_of(" \n\r\t"); + if (num_pos == std::string::npos) num_pos = L; + size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); + if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { + unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); + } + if (unit_pos == std::string::npos) unit_pos = L; + const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(num.c_str()), + Token(number(parsed.c_str())), + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + { + Color_Ptr color = NULL; + if (parsed[0] != '#') { + return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); + } + // chop off the '#' + std::string hext(parsed.substr(1)); + if (parsed.length() == 4) { + std::string r(2, parsed[1]); + std::string g(2, parsed[2]); + std::string b(2, parsed[3]); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 5) { + std::string r(2, parsed[1]); + std::string g(2, parsed[2]); + std::string b(2, parsed[3]); + std::string a(2, parsed[4]); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + static_cast(strtol(a.c_str(), NULL, 16)) / 255, + parsed); + } + else if (parsed.length() == 7) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 9) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + std::string a(parsed.substr(7,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + static_cast(strtol(a.c_str(), NULL, 16)) / 255, + parsed); + } + color->is_interpolant(false); + color->is_delayed(false); + return color; + } + + Value_Ptr Parser::color_or_string(const std::string& lexed) const + { + if (auto color = name_to_color(lexed)) { + auto c = SASS_MEMORY_NEW(Color, color); + c->is_delayed(true); + c->pstate(pstate); + c->disp(lexed); + return c; + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + + // parse one value for a list + Expression_Obj Parser::parse_value() + { + lex< css_comments >(false); + if (lex< ampersand >()) + { + if (match< ampersand >()) { + warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); + } + return SASS_MEMORY_NEW(Parent_Selector, pstate); } + + if (lex< kwd_important >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } + + // parse `10%4px` into separated items and not a schema + if (lex< sequence < percentage, lookahead < number > > >()) + { return lexed_percentage(lexed); } + + if (lex< sequence < number, lookahead< sequence < op, number > > > >()) + { return lexed_number(lexed); } + + // string may be interpolated + if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) + { return parse_string(); } + + if (const char* stop = peek< value_schema >()) + { return parse_value_schema(stop); } + + // string may be interpolated + if (lex< quoted_string >()) + { return parse_string(); } + + if (lex< kwd_true >()) + { return SASS_MEMORY_NEW(Boolean, pstate, true); } + + if (lex< kwd_false >()) + { return SASS_MEMORY_NEW(Boolean, pstate, false); } + + if (lex< kwd_null >()) + { return SASS_MEMORY_NEW(Null, pstate); } + + if (lex< identifier >()) { + return color_or_string(lexed); + } + + if (lex< percentage >()) + { return lexed_percentage(lexed); } + + // match hex number first because 0x000 looks like a number followed by an identifier + if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) + { return lexed_hex_color(lexed); } + + if (lex< hexa >()) + { return lexed_hex_color(lexed); } + + if (lex< sequence < exactly <'#'>, identifier > >()) + { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } + + // also handle the 10em- foo special case + // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression + if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) + { return lexed_dimension(lexed); } + + if (lex< sequence< static_component, one_plus< strict_identifier > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + if (lex< number >()) + { return lexed_number(lexed); } + + if (lex< variable >()) + { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } + + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + + // unreachable statement + return 0; + } + + // this parses interpolation inside other strings + // means the result should later be quoted again + String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) + { + const char* i = chunk.begin; + // see if there any interpolants + const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + + if (!p) { + String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); + if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); + return str_quoted; + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); + schema->is_interpolant(true); + while (i < chunk.end) { + p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace + if (j) { --j; + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside string constant " + chunk.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + // check if we need quotes here (was not sure after merge) + if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); + break; + } + ++ i; + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj tok; + if (!(tok = parse_css_variable_value_token(top_level))) { + return NULL; + } + + schema->concat(tok); + while ((tok = parse_css_variable_value_token(top_level))) { + schema->concat(tok); + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if ( + (top_level && lex< css_variable_top_level_value >(false)) || + (!top_level && lex< css_variable_value >(false)) + ) { + Token str(lexed); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); + } + else if (Expression_Obj tok = lex_interpolation()) { + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else if (lex< quoted_string >()) { + Expression_Obj tok = parse_string(); + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else { + if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { + if (lex< exactly<'('> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); + } + else if (lex< exactly<'['> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); + } + else if (lex< exactly<'{'> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); + } + } + } + + return schema->length() > 0 ? schema.detach() : NULL; + } + + Value_Obj Parser::parse_static_value() + { + lex< static_value >(); + Token str(lexed); + // static values always have trailing white- + // space and end delimiter (\s*[;]$) included + --pstate.offset.column; + --after_token.column; + --str.end; + --position; + + return color_or_string(str.time_wspace());; + } + + String_Obj Parser::parse_string() + { + return parse_interpolated_chunk(Token(lexed)); + } + + String_Obj Parser::parse_ie_property() + { + lex< ie_property >(); + Token str(lexed); + const char* i = str.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); + if (!p) { + return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); + } + + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < str.end) { + p = find_first_in_interval< exactly, block_comment >(i, str.end); + if (p) { + if (i < p) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty + } + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside IE function " + str.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < str.end) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); + } + break; + } + } + return schema; + } + + String_Obj Parser::parse_ie_keyword_arg() + { + String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); + if (lex< variable >()) { + kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); + } else { + lex< alternatives< identifier_schema, identifier > >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + lex< exactly<'='> >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (peek< variable >()) kwd_arg->append(parse_list()); + else if (lex< number >()) { + std::string parsed(lexed); + Util::normalize_decimals(parsed); + kwd_arg->append(lexed_number(parsed)); + } + else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } + return kwd_arg; + } + + String_Schema_Obj Parser::parse_value_schema(const char* stop) + { + // initialize the string schema object to add tokens + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + + if (peek>()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + const char* e; + const char* ee = end; + end = stop; + size_t num_items = 0; + bool need_space = false; + while (position < stop) { + // parse space between tokens + if (lex< spaces >() && num_items) { + need_space = true; + } + if (need_space) { + need_space = false; + // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + } + if ((e = peek< re_functional >()) && e < stop) { + schema->append(parse_function_call()); + } + // lex an interpolant /#{...}/ + else if (lex< exactly < hash_lbrace > >()) { + // Try to lex static expression first + if (peek< exactly< rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj ex; + if (lex< re_static_expression >()) { + ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } else { + ex = parse_list(true); + } + ex->is_interpolant(true); + schema->append(ex); + if (!lex < exactly < rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + } + // lex some string constants or other valid token + // Note: [-+] chars are left over from i.e. `#{3}+3` + else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + // lex a quoted string + else if (lex< quoted_string >()) { + // need_space = true; + // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + // else need_space = true; + schema->append(parse_string()); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + if (peek < exactly < '-' > >()) break; + } + else if (lex< identifier >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + } + // lex (normalized) variable + else if (lex< variable >()) { + std::string name(Util::normalize_underscores(lexed)); + schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); + } + // lex percentage value + else if (lex< percentage >()) { + schema->append(lexed_percentage(lexed)); + } + // lex dimension value + else if (lex< dimension >()) { + schema->append(lexed_dimension(lexed)); + } + // lex number value + else if (lex< number >()) { + schema->append(lexed_number(lexed)); + } + // lex hex color value + else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { + schema->append(lexed_hex_color(lexed)); + } + else if (lex< sequence < exactly <'#'>, identifier > >()) { + schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); + } + // lex a value in parentheses + else if (peek< parenthese_scope >()) { + schema->append(parse_factor()); + } + else { + break; + } + ++num_items; + } + if (position != stop) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); + position = stop; + } + end = ee; + return schema; + } + + // this parses interpolation outside other strings + // means the result must not be quoted again later + String_Obj Parser::parse_identifier_schema() + { + Token id(lexed); + const char* i = id.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); + if (!p) { + return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < id.end) { + p = find_first_in_interval< exactly, block_comment >(i, id.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + const char* o = position; position = i; + schema->append(parse_value_schema(p)); + position = o; + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); + interp_node->is_interpolant(true); + schema->append(interp_node); + // schema->has_interpolants(true); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside interpolated identifier " + id.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < end) { + const char* o = position; position = i; + schema->append(parse_value_schema(id.end)); + position = o; + } + break; + } + } + return schema ? schema.detach() : 0; + } + + // calc functions should preserve arguments + Function_Call_Obj Parser::parse_calc_function() + { + lex< identifier >(); + std::string name(lexed); + ParserState call_pos = pstate; + lex< exactly<'('> >(); + ParserState arg_pos = pstate; + const char* arg_beg = position; + parse_list(); + const char* arg_end = position; + lex< skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > >(); + + Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); + args->append(arg); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + String_Obj Parser::parse_url_function_string() + { + std::string prefix(""); + if (lex< uri_prefix >()) { + prefix = std::string(lexed); + } + + lex < optional_spaces >(); + String_Obj url_string = parse_url_function_argument(); + + std::string suffix(""); + if (lex< real_uri_suffix >()) { + suffix = std::string(lexed); + } + + std::string uri(""); + if (url_string) { + uri = url_string->to_string({ NESTED, 5 }); + } + + if (String_Schema_Ptr schema = Cast(url_string)) { + String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); + res->append(schema); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); + return res; + } else { + std::string res = prefix + uri + suffix; + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + } + + String_Obj Parser::parse_url_function_argument() + { + const char* p = position; + + std::string uri(""); + if (lex< real_uri_value >(false)) { + uri = lexed.to_string(); + } + + if (peek< exactly< hash_lbrace > >()) { + const char* pp = position; + // TODO: error checking for unclosed interpolants + while (pp && peek< exactly< hash_lbrace > >(pp)) { + pp = sequence< interpolant, real_uri_value >(pp); + } + if (!pp) return 0; + position = pp; + return parse_interpolated_chunk(Token(p, position)); + } + else if (uri != "") { + std::string res = Util::rtrim(uri); + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + + return 0; + } + + Function_Call_Obj Parser::parse_function_call() + { + lex< identifier >(); + std::string name(lexed); + + if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) + { error("Cannot call content-exists() except within a mixin."); } + + ParserState call_pos = pstate; + Arguments_Obj args = parse_arguments(); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + Function_Call_Schema_Obj Parser::parse_function_call_schema() + { + String_Obj name = parse_identifier_schema(); + ParserState source_position_of_call = pstate; + Arguments_Obj args = parse_arguments(); + + return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); + } + + Content_Obj Parser::parse_content_directive() + { + return SASS_MEMORY_NEW(Content, pstate); + } + + If_Obj Parser::parse_if_directive(bool else_if) + { + stack.push_back(Scope::Control); + ParserState if_source_position = pstate; + bool root = block_stack.back()->is_root(); + Expression_Obj predicate = parse_list(); + Block_Obj block = parse_block(root); + Block_Obj alternative = NULL; + + // only throw away comment if we parse a case + // we want all other comments to be parsed + if (lex_css< elseif_directive >()) { + alternative = SASS_MEMORY_NEW(Block, pstate); + alternative->append(parse_if_directive(true)); + } + else if (lex_css< kwd_else_directive >()) { + alternative = parse_block(root); + } + stack.pop_back(); + return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); + } + + For_Obj Parser::parse_for_directive() + { + stack.push_back(Scope::Control); + ParserState for_source_position = pstate; + bool root = block_stack.back()->is_root(); + lex_variable(); + std::string var(Util::normalize_underscores(lexed)); + if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); + Expression_Obj lower_bound = parse_expression(); + bool inclusive = false; + if (lex< kwd_through >()) inclusive = true; + else if (lex< kwd_to >()) inclusive = false; + else error("expected 'through' or 'to' keyword in @for directive"); + Expression_Obj upper_bound = parse_expression(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); + } + + // helper to parse a var token + Token Parser::lex_variable() + { + // peek for dollar sign first + if (!peek< exactly <'$'> >()) { + css_error("Invalid CSS", " after ", ": expected \"$\", was "); + } + // we expect a simple identifier as the call name + if (!lex< sequence < exactly <'$'>, identifier > >()) { + lex< exactly <'$'> >(); // move pstate and position up + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + // helper to parse identifier + Token Parser::lex_identifier() + { + // we expect a simple identifier as the call name + if (!lex< identifier >()) { // ToDo: pstate wrong? + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + + Each_Obj Parser::parse_each_directive() + { + stack.push_back(Scope::Control); + ParserState each_source_position = pstate; + bool root = block_stack.back()->is_root(); + std::vector vars; + lex_variable(); + vars.push_back(Util::normalize_underscores(lexed)); + while (lex< exactly<','> >()) { + if (!lex< variable >()) error("@each directive requires an iteration variable"); + vars.push_back(Util::normalize_underscores(lexed)); + } + if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); + Expression_Obj list = parse_list(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); + } + + // called after parsing `kwd_while_directive` + While_Obj Parser::parse_while_directive() + { + stack.push_back(Scope::Control); + bool root = block_stack.back()->is_root(); + // create the initial while call object + While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); + // parse mandatory predicate + Expression_Obj predicate = parse_list(); + List_Obj l = Cast(predicate); + if (!predicate || (l && !l->length())) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); + } + call->predicate(predicate); + // parse mandatory block + call->block(parse_block(root)); + // return ast node + stack.pop_back(); + // return ast node + return call.detach(); + } + + // EO parse_while_directive + Media_Block_Obj Parser::parse_media_block() + { + stack.push_back(Scope::Media); + Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); + + media_block->media_queries(parse_media_queries()); + + Media_Block_Obj prev_media_block = last_media_block; + last_media_block = media_block; + media_block->block(parse_css_block()); + last_media_block = prev_media_block; + stack.pop_back(); + return media_block.detach(); + } + + List_Obj Parser::parse_media_queries() + { + advanceToNextToken(); + List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); + if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); + while (lex_css < exactly <','> >()) queries->append(parse_media_query()); + queries->update_pstate(pstate); + return queries.detach(); + } + + // Expression_Ptr Parser::parse_media_query() + Media_Query_Obj Parser::parse_media_query() + { + advanceToNextToken(); + Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); + if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } + else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } + + if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); + else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); + else media_query->append(parse_media_expression()); + + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); + if (lex < identifier_schema >()) { + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + schema->append(media_query->media_type()); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + schema->append(parse_identifier_schema()); + media_query->media_type(schema); + } + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); + + media_query->update_pstate(pstate); + + return media_query; + } + + Media_Query_Expression_Obj Parser::parse_media_expression() + { + if (lex < identifier_schema >()) { + String_Obj ss = parse_identifier_schema(); + return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); + } + if (!lex_css< exactly<'('> >()) { + error("media query expression must begin with '('"); + } + Expression_Obj feature; + if (peek_css< exactly<')'> >()) { + error("media feature required in media query expression"); + } + feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!lex_css< exactly<')'> >()) { + error("unclosed parenthesis in media query expression"); + } + return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); + } + + // lexed after `kwd_supports_directive` + // these are very similar to media blocks + Supports_Block_Obj Parser::parse_supports_directive() + { + Supports_Condition_Obj cond = parse_supports_condition(); + if (!cond) { + css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); + } + // create the ast node object for the support queries + Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); + // additional block is mandatory + // parse inner block + query->block(parse_block()); + // return ast node + return query; + } + + // parse one query operation + // may encounter nested queries + Supports_Condition_Obj Parser::parse_supports_condition() + { + lex < css_whitespace >(); + Supports_Condition_Obj cond; + if ((cond = parse_supports_negation())) return cond; + if ((cond = parse_supports_operator())) return cond; + if ((cond = parse_supports_interpolation())) return cond; + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_negation() + { + if (!lex < kwd_not >()) return 0; + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); + } + + Supports_Condition_Obj Parser::parse_supports_operator() + { + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + if (cond.isNull()) return 0; + + while (true) { + Supports_Operator::Operand op = Supports_Operator::OR; + if (lex < kwd_and >()) { op = Supports_Operator::AND; } + else if(!lex < kwd_or >()) { break; } + + lex < css_whitespace >(); + Supports_Condition_Obj right = parse_supports_condition_in_parens(); + + // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); + cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); + } + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_interpolation() + { + if (!lex < interpolant >()) return 0; + + String_Obj interp = parse_interpolated_chunk(lexed); + if (!interp) return 0; + + return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); + } + + // TODO: This needs some major work. Although feature conditions + // look like declarations their semantics differ significantly + Supports_Condition_Obj Parser::parse_supports_declaration() + { + Supports_Condition_Ptr cond; + // parse something declaration like + Expression_Obj feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!feature || !expression) error("@supports condition expected declaration"); + cond = SASS_MEMORY_NEW(Supports_Declaration, + feature->pstate(), + feature, + expression); + // ToDo: maybe we need an additional error condition? + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_condition_in_parens() + { + Supports_Condition_Obj interp = parse_supports_interpolation(); + if (interp != 0) return interp; + + if (!lex < exactly <'('> >()) return 0; + lex < css_whitespace >(); + + Supports_Condition_Obj cond = parse_supports_condition(); + if (cond != 0) { + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); + } else { + cond = parse_supports_declaration(); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); + } + lex < css_whitespace >(); + return cond; + } + + At_Root_Block_Obj Parser::parse_at_root_block() + { + stack.push_back(Scope::AtRoot); + ParserState at_source_position = pstate; + Block_Obj body = 0; + At_Root_Query_Obj expr; + Lookahead lookahead_result; + if (lex_css< exactly<'('> >()) { + expr = parse_at_root_query(); + } + if (peek_css < exactly<'{'> >()) { + lex (); + body = parse_block(true); + } + else if ((lookahead_result = lookahead_for_selector(position)).found) { + Ruleset_Obj r = parse_ruleset(lookahead_result); + body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); + body->append(r); + } + At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); + if (!expr.isNull()) at_root->expression(expr); + stack.pop_back(); + return at_root; + } + + At_Root_Query_Obj Parser::parse_at_root_query() + { + if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); + + if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { + css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); + } + + Expression_Obj feature = parse_list(); + if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); + Expression_Obj expression = parse_list(); + List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); + + if (expression->concrete_type() == Expression::LIST) { + value = Cast(expression); + } + else value->append(expression); + + At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, + value->pstate(), + feature, + value); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); + return cond; + } + + Directive_Obj Parser::parse_special_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); + + // this whole branch is never hit via spec tests + + Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(parse_selector_list(false)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + // this whole branch is never hit via spec tests + Directive_Obj Parser::parse_prefixed_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); + + Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(parse_selector_list(false)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + + Directive_Obj Parser::parse_directive() + { + Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); + String_Schema_Obj val = parse_almost_any_value(); + // strip left and right if they are of type string + directive->value(val); + if (peek< exactly<'{'> >()) { + directive->block(parse_block()); + } + return directive; + } + + Expression_Obj Parser::lex_interpolation() + { + if (lex < interpolant >(true) != NULL) { + return parse_interpolated_chunk(lexed, true); + } + return 0; + } + + Expression_Obj Parser::lex_interp_uri() + { + // create a string schema by lexing optional interpolations + return lex_interp< re_string_uri_open, re_string_uri_close >(); + } + + Expression_Obj Parser::lex_interp_string() + { + Expression_Obj rv; + if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; + if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; + return rv; + } + + Expression_Obj Parser::lex_almost_any_value_chars() + { + const char* match = + lex < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + sequence < + exactly < url_kwd >, + exactly <'('> + > + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + > + >(false); + if (match) { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + return NULL; + } + + Expression_Obj Parser::lex_almost_any_value_token() + { + Expression_Obj rv; + if (*position == 0) return 0; + if ((rv = lex_almost_any_value_chars())) return rv; + // if ((rv = lex_block_comment())) return rv; + // if ((rv = lex_single_line_comment())) return rv; + if ((rv = lex_interp_string())) return rv; + if ((rv = lex_interp_uri())) return rv; + if ((rv = lex_interpolation())) return rv; + if (lex< alternatives< hex, hex0 > >()) + { return lexed_hex_color(lexed); } + return rv; + } + + String_Schema_Obj Parser::parse_almost_any_value() + { + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if (*position == 0) return 0; + lex < spaces >(false); + Expression_Obj token = lex_almost_any_value_token(); + if (!token) return 0; + schema->append(token); + if (*position == 0) { + schema->rtrim(); + return schema.detach(); + } + + while ((token = lex_almost_any_value_token())) { + schema->append(token); + } + + lex < css_whitespace >(); + + schema->rtrim(); + + return schema.detach(); + } + + Warning_Obj Parser::parse_warning() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); + } + + Error_Obj Parser::parse_error() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); + } + + Debug_Obj Parser::parse_debug() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); + } + + Return_Obj Parser::parse_return_directive() + { + // check that we do not have an empty list (ToDo: check if we got all cases) + if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) + { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } + return SASS_MEMORY_NEW(Return, pstate, parse_list()); + } + + Lookahead Parser::lookahead_for_selector(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + rv.error = p; + if (const char* q = + peek < + re_selector_list + >(p) + ) { + bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; + bool could_be_escaped = false; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + // A property that's ambiguous with a nested selector is interpreted as a + // custom property. + if (*p == ':' && !could_be_escaped) { + rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); + } + could_be_escaped = *p == '\\'; + ++ p; + } + // store anyway } + + + // ToDo: remove + rv.error = q; + rv.position = q; + // check expected opening bracket + // only after successfull matching + if (peek < exactly<'{'> >(q)) rv.found = q; + // else if (peek < end_of_file >(q)) rv.found = q; + else if (peek < exactly<'('> >(q)) rv.found = q; + // else if (peek < exactly<';'> >(q)) rv.found = q; + // else if (peek < exactly<'}'> >(q)) rv.found = q; + if (rv.found || *p == 0) rv.error = 0; + } + + rv.parsable = ! rv.has_interpolants; + + // return result + return rv; + + } + // EO lookahead_for_selector + + // used in parse_block_nodes and parse_special_directive + // ToDo: actual usage is still not really clear to me? + Lookahead Parser::lookahead_for_include(const char* start) + { + // we actually just lookahead for a selector + Lookahead rv = lookahead_for_selector(start); + // but the "found" rules are different + if (const char* p = rv.position) { + // check for additional abort condition + if (peek < exactly<';'> >(p)) rv.found = p; + else if (peek < exactly<'}'> >(p)) rv.found = p; + } + // return result + return rv; + } + // EO lookahead_for_include + + // look ahead for a token with interpolation in it + // we mostly use the result if there is an interpolation + // everything that passes here gets parsed as one schema + // meaning it will not be parsed as a space separated list + Lookahead Parser::lookahead_for_value(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + if (const char* q = + peek < + non_greedy < + alternatives < + // consume whitespace + block_comment, // spaces, + // main tokens + sequence < + interpolant, + optional < + quoted_string + > + >, + identifier, + variable, + // issue #442 + sequence < + parenthese_scope, + interpolant, + optional < + quoted_string + > + > + >, + sequence < + // optional_spaces, + alternatives < + // end_of_file, + exactly<'{'>, + exactly<'}'>, + exactly<';'> + > + > + > + >(p) + ) { + if (p == q) return rv; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + ++ p; + } + // store anyway + // ToDo: remove + rv.position = q; + // check expected opening bracket + // only after successful matching + if (peek < exactly<'{'> >(q)) rv.found = q; + else if (peek < exactly<';'> >(q)) rv.found = q; + else if (peek < exactly<'}'> >(q)) rv.found = q; + } + + // return result + return rv; + } + // EO lookahead_for_value + + void Parser::read_bom() + { + size_t skip = 0; + std::string encoding; + bool utf_8 = false; + switch ((unsigned char) source[0]) { + case 0xEF: + skip = check_bom_chars(source, end, utf_8_bom, 3); + encoding = "UTF-8"; + utf_8 = true; + break; + case 0xFE: + skip = check_bom_chars(source, end, utf_16_bom_be, 2); + encoding = "UTF-16 (big endian)"; + break; + case 0xFF: + skip = check_bom_chars(source, end, utf_16_bom_le, 2); + skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); + encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); + break; + case 0x00: + skip = check_bom_chars(source, end, utf_32_bom_be, 4); + encoding = "UTF-32 (big endian)"; + break; + case 0x2B: + skip = check_bom_chars(source, end, utf_7_bom_1, 4) + | check_bom_chars(source, end, utf_7_bom_2, 4) + | check_bom_chars(source, end, utf_7_bom_3, 4) + | check_bom_chars(source, end, utf_7_bom_4, 4) + | check_bom_chars(source, end, utf_7_bom_5, 5); + encoding = "UTF-7"; + break; + case 0xF7: + skip = check_bom_chars(source, end, utf_1_bom, 3); + encoding = "UTF-1"; + break; + case 0xDD: + skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); + encoding = "UTF-EBCDIC"; + break; + case 0x0E: + skip = check_bom_chars(source, end, scsu_bom, 3); + encoding = "SCSU"; + break; + case 0xFB: + skip = check_bom_chars(source, end, bocu_1_bom, 3); + encoding = "BOCU-1"; + break; + case 0x84: + skip = check_bom_chars(source, end, gb_18030_bom, 4); + encoding = "GB-18030"; + break; + default: break; + } + if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); + position += skip; + } + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) + { + size_t skip = 0; + if (src + len > end) return 0; + for (size_t i = 0; i < len; ++i, ++skip) { + if ((unsigned char) src[i] != bom[i]) return 0; + } + return skip; + } + + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) + { + for (size_t i = 0, S = operands.size(); i < S; ++i) { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); + } + return base; + } + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) + { + if (String_Schema_Ptr schema = Cast(base)) { + // return schema; + if (schema->has_interpolants()) { + if (i + 1 < operands.size() && ( + (ops[0].operand == Sass_OP::EQ) + || (ops[0].operand == Sass_OP::ADD) + || (ops[0].operand == Sass_OP::DIV) + || (ops[0].operand == Sass_OP::MUL) + || (ops[0].operand == Sass_OP::NEQ) + || (ops[0].operand == Sass_OP::LT) + || (ops[0].operand == Sass_OP::GT) + || (ops[0].operand == Sass_OP::LTE) + || (ops[0].operand == Sass_OP::GTE) + )) { + Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); + return rhs; + } + // return schema; + } + } + + for (size_t S = operands.size(); i < S; ++i) { + if (String_Schema_Ptr schema = Cast(operands[i])) { + if (schema->has_interpolants()) { + if (i + 1 < S) { + // this whole branch is never hit via spec tests + Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); + return base; + } + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + return base; + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + Binary_Expression_Ptr b = Cast(base.ptr()); + if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { + base->is_delayed(true); + } + } + // nested binary expression are never to be delayed + if (Binary_Expression_Ptr b = Cast(base)) { + if (Cast(b->left())) base->set_delayed(false); + if (Cast(b->right())) base->set_delayed(false); + } + return base; + } + + void Parser::error(std::string msg, Position pos) + { + Position p(pos.line ? pos : before_token); + ParserState pstate(path, source, p, Offset(0, 0)); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, msg); + } + + void Parser::error(std::string msg) + { + error(msg, pstate); + } + + // print a css parsing error with actual context information from parsed source + void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) + { + int max_len = 18; + const char* end = this->end; + while (*end != 0) ++ end; + const char* pos = peek < optional_spaces >(); + if (!pos) pos = position; + + const char* last_pos(pos); + if (last_pos > source) { + utf8::prior(last_pos, source); + } + // backup position to last significant char + while (trim && last_pos > source && last_pos < end) { + if (!Prelexer::is_space(*last_pos)) break; + utf8::prior(last_pos, source); + } + + bool ellipsis_left = false; + const char* pos_left(last_pos); + const char* end_left(last_pos); + + if (*pos_left) utf8::next(pos_left, end); + if (*end_left) utf8::next(end_left, end); + while (pos_left > source) { + if (utf8::distance(pos_left, end_left) >= max_len) { + utf8::prior(pos_left, source); + ellipsis_left = *(pos_left) != '\n' && + *(pos_left) != '\r'; + utf8::next(pos_left, end); + break; + } + + const char* prev = pos_left; + utf8::prior(prev, source); + if (*prev == '\r') break; + if (*prev == '\n') break; + pos_left = prev; + } + if (pos_left < source) { + pos_left = source; + } + + bool ellipsis_right = false; + const char* end_right(pos); + const char* pos_right(pos); + while (end_right < end) { + if (utf8::distance(pos_right, end_right) > max_len) { + ellipsis_left = *(pos_right) != '\n' && + *(pos_right) != '\r'; + break; + } + if (*end_right == '\r') break; + if (*end_right == '\n') break; + utf8::next(end_right, end); + } + // if (*end_right == 0) end_right ++; + + std::string left(pos_left, end_left); + std::string right(pos_right, end_right); + size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; + size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; + if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); + if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; + // Hotfix when source is null, probably due to interpolation parsing!? + if (source == NULL || *source == 0) source = pstate.src; + // now pass new message to the more generic error function + error(msg + prefix + quote(left) + middle + quote(right)); + } + +} diff --git a/src/parser.hpp b/src/parser.hpp new file mode 100644 index 000000000..d2a6ddc1a --- /dev/null +++ b/src/parser.hpp @@ -0,0 +1,400 @@ +#ifndef SASS_PARSER_H +#define SASS_PARSER_H + +#include +#include + +#include "ast.hpp" +#include "position.hpp" +#include "context.hpp" +#include "position.hpp" +#include "prelexer.hpp" + +#ifndef MAX_NESTING +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +#define MAX_NESTING 512 +#endif + +struct Lookahead { + const char* found; + const char* error; + const char* position; + bool parsable; + bool has_interpolants; + bool is_custom_property; +}; + +namespace Sass { + + class Parser : public ParserState { + public: + + enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; + + Context& ctx; + std::vector block_stack; + std::vector stack; + Media_Block_Ptr last_media_block; + const char* source; + const char* position; + const char* end; + Position before_token; + Position after_token; + ParserState pstate; + Backtraces traces; + size_t indentation; + size_t nestings; + + Token lexed; + + Parser(Context& ctx, const ParserState& pstate, Backtraces traces) + : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), + source(0), position(0), end(0), before_token(pstate), after_token(pstate), + pstate(pstate), traces(traces), indentation(0), nestings(0) + { + stack.push_back(Scope::Root); + } + + // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + // special static parsers to convert strings into certain selectors + static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + +#ifdef __clang__ + + // lex and peak uses the template parameter to branch on the action, which + // triggers clangs tautological comparison on the single-comparison + // branches. This is not a bug, just a merging of behaviour into + // one function + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-compare" + +#endif + + + // skip current token and next whitespace + // moves ParserState right before next token + void advanceToNextToken(); + + bool peek_newline(const char* start = 0); + + // skip over spaces, tabs and line comments + template + const char* sneak(const char* start = 0) + { + using namespace Prelexer; + + // maybe use optional start position from arguments? + const char* it_position = start ? start : position; + + // skip white-space? + if (mx == spaces || + mx == no_spaces || + mx == css_comments || + mx == css_whitespace || + mx == optional_spaces || + mx == optional_css_comments || + mx == optional_css_whitespace + ) { + return it_position; + } + + // skip over spaces, tabs and sass line comments + const char* pos = optional_css_whitespace(it_position); + // always return a valid position + return pos ? pos : it_position; + + } + + // match will not skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* match(const char* start = 0) + { + // match the given prelexer + return mx(position); + } + + // peek will only skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* peek(const char* start = 0) + { + + // sneak up to the actual token we want to lex + // this should skip over white-space if desired + const char* it_before_token = sneak < mx >(start); + + // match the given prelexer + const char* match = mx(it_before_token); + + // check if match is in valid range + return match <= end ? match : 0; + + } + + // white-space handling is built into the lexer + // this way you do not need to parse it yourself + // some matchers don't accept certain white-space + // we do not support start arg, since we manipulate + // sourcemap offset and we modify the position pointer! + // lex will only skip over space, tabs and line comment + template + const char* lex(bool lazy = true, bool force = false) + { + + if (*position == 0) return 0; + + // position considered before lexed token + // we can skip whitespace or comments for + // lazy developers (but we need control) + const char* it_before_token = position; + + // sneak up to the actual token we want to lex + // this should skip over white-space if desired + if (lazy) it_before_token = sneak < mx >(position); + + // now call matcher to get position after token + const char* it_after_token = mx(it_before_token); + + // check if match is in valid range + if (it_after_token > end) return 0; + + // maybe we want to update the parser state anyway? + if (force == false) { + // assertion that we got a valid match + if (it_after_token == 0) return 0; + // assertion that we actually lexed something + if (it_after_token == it_before_token) return 0; + } + + // create new lexed token object (holds the parse results) + lexed = Token(position, it_before_token, it_after_token); + + // advance position (add whitespace before current token) + before_token = after_token.add(position, it_before_token); + + // update after_token position for current token + after_token.add(it_before_token, it_after_token); + + // ToDo: could probably do this incremetal on original object (API wants offset?) + pstate = ParserState(path, source, lexed, before_token, after_token - before_token); + + // advance internal char iterator + return position = it_after_token; + + } + + // lex_css skips over space, tabs, line and block comment + // all block comments will be consumed and thrown away + // source-map position will point to token after the comment + template + const char* lex_css() + { + // copy old token + Token prev = lexed; + // store previous pointer + const char* oldpos = position; + Position bt = before_token; + Position at = after_token; + ParserState op = pstate; + // throw away comments + // update srcmap position + lex < Prelexer::css_comments >(); + // now lex a new token + const char* pos = lex< mx >(); + // maybe restore prev state + if (pos == 0) { + pstate = op; + lexed = prev; + position = oldpos; + after_token = at; + before_token = bt; + } + // return match + return pos; + } + + // all block comments will be skipped and thrown away + template + const char* peek_css(const char* start = 0) + { + // now peek a token (skip comments first) + return peek< mx >(peek < Prelexer::css_comments >(start)); + } + +#ifdef __clang__ + +#pragma clang diagnostic pop + +#endif + + void error(std::string msg); + void error(std::string msg, Position pos); + // generate message with given and expected sample + // text before and in the middle are configurable + void css_error(const std::string& msg, + const std::string& prefix = " after ", + const std::string& middle = ", was: ", + const bool trim = true); + void read_bom(); + + Block_Obj parse(); + Import_Obj parse_import(); + Definition_Obj parse_definition(Definition::Type which_type); + Parameters_Obj parse_parameters(); + Parameter_Obj parse_parameter(); + Mixin_Call_Obj parse_include_directive(); + Arguments_Obj parse_arguments(); + Argument_Obj parse_argument(); + Assignment_Obj parse_assignment(); + Ruleset_Obj parse_ruleset(Lookahead lookahead); + Selector_List_Obj parse_selector_list(bool chroot); + Complex_Selector_Obj parse_complex_selector(bool chroot); + Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); + Compound_Selector_Obj parse_compound_selector(); + Simple_Selector_Obj parse_simple_selector(); + Wrapped_Selector_Obj parse_negated_selector(); + Simple_Selector_Obj parse_pseudo_selector(); + Attribute_Selector_Obj parse_attribute_selector(); + Block_Obj parse_block(bool is_root = false); + Block_Obj parse_css_block(bool is_root = false); + bool parse_block_nodes(bool is_root = false); + bool parse_block_node(bool is_root = false); + + bool parse_number_prefix(); + Declaration_Obj parse_declaration(); + Expression_Obj parse_map(); + Expression_Obj parse_bracket_list(); + Expression_Obj parse_list(bool delayed = false); + Expression_Obj parse_comma_list(bool delayed = false); + Expression_Obj parse_space_list(); + Expression_Obj parse_disjunction(); + Expression_Obj parse_conjunction(); + Expression_Obj parse_relation(); + Expression_Obj parse_expression(); + Expression_Obj parse_operators(); + Expression_Obj parse_factor(); + Expression_Obj parse_value(); + Function_Call_Obj parse_calc_function(); + Function_Call_Obj parse_function_call(); + Function_Call_Schema_Obj parse_function_call_schema(); + String_Obj parse_url_function_string(); + String_Obj parse_url_function_argument(); + String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); + String_Obj parse_string(); + Value_Obj parse_static_value(); + String_Schema_Obj parse_css_variable_value(bool top_level = true); + String_Schema_Obj parse_css_variable_value_token(bool top_level = true); + String_Obj parse_ie_property(); + String_Obj parse_ie_keyword_arg(); + String_Schema_Obj parse_value_schema(const char* stop); + String_Obj parse_identifier_schema(); + If_Obj parse_if_directive(bool else_if = false); + For_Obj parse_for_directive(); + Each_Obj parse_each_directive(); + While_Obj parse_while_directive(); + Return_Obj parse_return_directive(); + Content_Obj parse_content_directive(); + void parse_charset_directive(); + Media_Block_Obj parse_media_block(); + List_Obj parse_media_queries(); + Media_Query_Obj parse_media_query(); + Media_Query_Expression_Obj parse_media_expression(); + Supports_Block_Obj parse_supports_directive(); + Supports_Condition_Obj parse_supports_condition(); + Supports_Condition_Obj parse_supports_negation(); + Supports_Condition_Obj parse_supports_operator(); + Supports_Condition_Obj parse_supports_interpolation(); + Supports_Condition_Obj parse_supports_declaration(); + Supports_Condition_Obj parse_supports_condition_in_parens(); + At_Root_Block_Obj parse_at_root_block(); + At_Root_Query_Obj parse_at_root_query(); + String_Schema_Obj parse_almost_any_value(); + Directive_Obj parse_special_directive(); + Directive_Obj parse_prefixed_directive(); + Directive_Obj parse_directive(); + Warning_Obj parse_warning(); + Error_Obj parse_error(); + Debug_Obj parse_debug(); + + Value_Ptr color_or_string(const std::string& lexed) const; + + // be more like ruby sass + Expression_Obj lex_almost_any_value_token(); + Expression_Obj lex_almost_any_value_chars(); + Expression_Obj lex_interp_string(); + Expression_Obj lex_interp_uri(); + Expression_Obj lex_interpolation(); + + // these will throw errors + Token lex_variable(); + Token lex_identifier(); + + void parse_block_comments(); + + Lookahead lookahead_for_value(const char* start = 0); + Lookahead lookahead_for_selector(const char* start = 0); + Lookahead lookahead_for_include(const char* start = 0); + + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); + + void throw_syntax_error(std::string message, size_t ln = 0); + void throw_read_error(std::string message, size_t ln = 0); + + + template + Expression_Obj lex_interp() + { + if (lex < open >(false)) { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (!itpl.isNull()) schema->append(itpl); + while (lex < close >(false)) { + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (!itpl.isNull()) schema->append(itpl); + } else { + return schema; + } + } + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + return 0; + } + + public: + static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); + static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); + private: + Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; + Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; + Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; + Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; + + static const char* re_attr_sensitive_close(const char* src); + static const char* re_attr_insensitive_close(const char* src); + + }; + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); +} + +#endif diff --git a/src/paths.hpp b/src/paths.hpp new file mode 100644 index 000000000..aabab94ae --- /dev/null +++ b/src/paths.hpp @@ -0,0 +1,71 @@ +#ifndef SASS_PATHS_H +#define SASS_PATHS_H + +#include +#include +#include + + +template +std::string vector_to_string(std::vector v) +{ + std::stringstream buffer; + buffer << "["; + + if (!v.empty()) + { buffer << v[0]; } + else + { buffer << "]"; } + + if (v.size() == 1) + { buffer << "]"; } + else + { + for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; + buffer << "]"; + } + + return buffer.str(); +} + +namespace Sass { + + + template + std::vector > paths(std::vector > strata, size_t from_end = 0) + { + if (strata.empty()) { + return std::vector >(); + } + + size_t end = strata.size() - from_end; + if (end <= 1) { + std::vector > starting_points; + starting_points.reserve(strata[0].size()); + for (size_t i = 0, S = strata[0].size(); i < S; ++i) { + std::vector starting_point; + starting_point.push_back(strata[0][i]); + starting_points.push_back(starting_point); + } + return starting_points; + } + + std::vector > up_to_here = paths(strata, from_end + 1); + std::vector here = strata[end-1]; + + std::vector > branches; + branches.reserve(up_to_here.size() * here.size()); + for (size_t i = 0, S1 = up_to_here.size(); i < S1; ++i) { + for (size_t j = 0, S2 = here.size(); j < S2; ++j) { + std::vector branch = up_to_here[i]; + branch.push_back(here[j]); + branches.push_back(branch); + } + } + + return branches; + } + +} + +#endif diff --git a/src/plugins.cpp b/src/plugins.cpp new file mode 100644 index 000000000..eecba7880 --- /dev/null +++ b/src/plugins.cpp @@ -0,0 +1,184 @@ +#include "sass.hpp" +#include +#include "output.hpp" +#include "plugins.hpp" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +namespace Sass { + + Plugins::Plugins(void) { } + Plugins::~Plugins(void) + { + for (auto function : functions) { + sass_delete_function(function); + } + for (auto importer : importers) { + sass_delete_importer(importer); + } + for (auto header : headers) { + sass_delete_importer(header); + } + } + + // check if plugin is compatible with this version + // plugins may be linked static against libsass + // we try to be compatible between major versions + inline bool compatibility(const char* their_version) + { +// const char* their_version = "3.1.2"; + // first check if anyone has an unknown version + const char* our_version = libsass_version(); + if (!strcmp(their_version, "[na]")) return false; + if (!strcmp(our_version, "[na]")) return false; + + // find the position of the second dot + size_t pos = std::string(our_version).find('.', 0); + if (pos != std::string::npos) pos = std::string(our_version).find('.', pos + 1); + + // if we do not have two dots we fallback to compare complete string + if (pos == std::string::npos) { return strcmp(their_version, our_version) ? 0 : 1; } + // otherwise only compare up to the second dot (major versions) + else { return strncmp(their_version, our_version, pos) ? 0 : 1; } + + } + + // load one specific plugin + bool Plugins::load_plugin (const std::string& path) + { + + typedef const char* (*__plugin_version__)(void); + typedef Sass_Function_List (*__plugin_load_fns__)(void); + typedef Sass_Importer_List (*__plugin_load_imps__)(void); + + if (LOAD_LIB(plugin, path)) + { + // try to load initial function to query libsass version suppor + if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version")) + { + // get the libsass version of the plugin + if (!compatibility(plugin_version())) return false; + // try to get import address for "libsass_load_functions" + if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) + { + Sass_Function_List fns = plugin_load_functions(), _p = fns; + while (fns && *fns) { functions.push_back(*fns); ++ fns; } + sass_free_memory(_p); // only delete the container, items not yet + } + // try to get import address for "libsass_load_importers" + if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers")) + { + Sass_Importer_List imps = plugin_load_importers(), _p = imps; + while (imps && *imps) { importers.push_back(*imps); ++ imps; } + sass_free_memory(_p); // only delete the container, items not yet + } + // try to get import address for "libsass_load_headers" + if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers")) + { + Sass_Importer_List imps = plugin_load_headers(), _p = imps; + while (imps && *imps) { headers.push_back(*imps); ++ imps; } + sass_free_memory(_p); // only delete the container, items not yet + } + // success + return true; + } + else + { + // print debug message to stderr (should not happen) + std::cerr << "failed loading 'libsass_support' in <" << path << ">" << std::endl; + if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << std::endl; + CLOSE_LIB(plugin); + } + } + else + { + // print debug message to stderr (should not happen) + std::cerr << "failed loading plugin <" << path << ">" << std::endl; + if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << std::endl; + } + + return false; + + } + + size_t Plugins::load_plugins(const std::string& path) + { + + // count plugins + size_t loaded = 0; + + #ifdef _WIN32 + + try + { + + // use wchar (utf16) + WIN32_FIND_DATAW data; + // trailing slash is guaranteed + std::string globsrch(path + "*.dll"); + // convert to wide chars (utf16) for system call + std::wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); + HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); + // check if system called returned a result + // ToDo: maybe we should print a debug message + if (hFile == INVALID_HANDLE_VALUE) return -1; + + // read directory + while (true) + { + try + { + // the system will report the filenames with wide chars (utf16) + std::string entry = UTF_8::convert_from_utf16(data.cFileName); + // check if file ending matches exactly + if (!ends_with(entry, ".dll")) continue; + // load the plugin and increase counter + if (load_plugin(path + entry)) ++ loaded; + // check if there should be more entries + if (GetLastError() == ERROR_NO_MORE_FILES) break; + // load next entry (check for return type) + if (!FindNextFileW(hFile, &data)) break; + } + catch (...) + { + // report the error to the console (should not happen) + // seems like we got strange data from the system call? + std::cerr << "filename in plugin path has invalid utf8?" << std::endl; + } + } + } + catch (utf8::invalid_utf8) + { + // report the error to the console (should not happen) + // implementors should make sure to provide valid utf8 + std::cerr << "plugin path contains invalid utf8" << std::endl; + } + + #else + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(path.c_str())) == NULL) return -1; + while ((dirp = readdir(dp)) != NULL) { + #if __APPLE__ + if (!ends_with(dirp->d_name, ".dylib")) continue; + #else + if (!ends_with(dirp->d_name, ".so")) continue; + #endif + if (load_plugin(path + dirp->d_name)) ++ loaded; + } + closedir(dp); + + #endif + return loaded; + + } + +} diff --git a/src/plugins.hpp b/src/plugins.hpp new file mode 100644 index 000000000..fe4eed010 --- /dev/null +++ b/src/plugins.hpp @@ -0,0 +1,57 @@ +#ifndef SASS_PLUGINS_H +#define SASS_PLUGINS_H + +#include +#include +#include "utf8_string.hpp" +#include "sass/functions.h" + +#ifdef _WIN32 + + #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str()) + #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str()) + #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name) + #define CLOSE_LIB(var) FreeLibrary(var) + + #ifndef dlerror + #define dlerror() 0 + #endif + +#else + + #define LOAD_LIB(var, path) void* var = dlopen(path.c_str(), RTLD_LAZY) + #define LOAD_LIB_FN(type, var, name) type var = (type) dlsym(plugin, name) + #define CLOSE_LIB(var) dlclose(var) + +#endif + +namespace Sass { + + + class Plugins { + + public: // c-tor + Plugins(void); + ~Plugins(void); + + public: // methods + // load one specific plugin + bool load_plugin(const std::string& path); + // load all plugins from a directory + size_t load_plugins(const std::string& path); + + public: // public accessors + const std::vector get_headers(void) { return headers; } + const std::vector get_importers(void) { return importers; } + const std::vector get_functions(void) { return functions; } + + private: // private vars + std::vector headers; + std::vector importers; + std::vector functions; + + }; + +} + +#endif diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 000000000..312e04ca6 --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,181 @@ +#include "sass.hpp" +#include "position.hpp" + +namespace Sass { + + + Offset::Offset(const char chr) + : line(chr == '\n' ? 1 : 0), + column(chr == '\n' ? 0 : 1) + {} + + Offset::Offset(const char* string) + : line(0), column(0) + { + *this = inc(string, string + strlen(string)); + } + + Offset::Offset(const std::string& text) + : line(0), column(0) + { + *this = inc(text.c_str(), text.c_str() + text.size()); + } + + Offset::Offset(const size_t line, const size_t column) + : line(line), column(column) { } + + // init/create instance from const char substring + Offset Offset::init(const char* beg, const char* end) + { + Offset offset(0, 0); + if (end == 0) { + end += strlen(beg); + } + offset.add(beg, end); + return offset; + } + + // increase offset by given string (mostly called by lexer) + // increase line counter and count columns on the last line + Offset Offset::add(const char* begin, const char* end) + { + if (end == 0) return *this; + while (begin < end && *begin) { + if (*begin == '\n') { + ++ line; + // start new line + column = 0; + } else { + // do not count any utf8 continuation bytes + // https://stackoverflow.com/a/9356203/1550314 + // https://en.wikipedia.org/wiki/UTF-8#Description + unsigned char chr = *begin; + // skip over 10xxxxxx + // is 1st bit not set + if ((chr & 128) == 0) { + // regular ascii char + column += 1; + } + // is 2nd bit not set + else if ((chr & 64) == 0) { + // first utf8 byte + column += 1; + } + } + ++ begin; + } + return *this; + } + + // increase offset by given string (mostly called by lexer) + // increase line counter and count columns on the last line + Offset Offset::inc(const char* begin, const char* end) const + { + Offset offset(line, column); + offset.add(begin, end); + return offset; + } + + bool Offset::operator== (const Offset &pos) const + { + return line == pos.line && column == pos.column; + } + + bool Offset::operator!= (const Offset &pos) const + { + return line != pos.line || column != pos.column; + } + + void Offset::operator+= (const Offset &off) + { + *this = Offset(line + off.line, off.line > 0 ? off.column : column + off.column); + } + + Offset Offset::operator+ (const Offset &off) const + { + return Offset(line + off.line, off.line > 0 ? off.column : column + off.column); + } + + Offset Offset::operator- (const Offset &off) const + { + return Offset(line - off.line, off.line == line ? column - off.column : column); + } + + Position::Position(const size_t file) + : Offset(0, 0), file(file) { } + + Position::Position(const size_t file, const Offset& offset) + : Offset(offset), file(file) { } + + Position::Position(const size_t line, const size_t column) + : Offset(line, column), file(-1) { } + + Position::Position(const size_t file, const size_t line, const size_t column) + : Offset(line, column), file(file) { } + + + ParserState::ParserState(const char* path, const char* src, const size_t file) + : Position(file, 0, 0), path(path), src(src), offset(0, 0), token() { } + + ParserState::ParserState(const char* path, const char* src, const Position& position, Offset offset) + : Position(position), path(path), src(src), offset(offset), token() { } + + ParserState::ParserState(const char* path, const char* src, const Token& token, const Position& position, Offset offset) + : Position(position), path(path), src(src), offset(offset), token(token) { } + + Position Position::add(const char* begin, const char* end) + { + Offset::add(begin, end); + return *this; + } + + Position Position::inc(const char* begin, const char* end) const + { + Offset offset(line, column); + offset = offset.inc(begin, end); + return Position(file, offset); + } + + bool Position::operator== (const Position &pos) const + { + return file == pos.file && line == pos.line && column == pos.column; + } + + bool Position::operator!= (const Position &pos) const + { + return file == pos.file || line != pos.line || column != pos.column; + } + + void Position::operator+= (const Offset &off) + { + *this = Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); + } + + const Position Position::operator+ (const Offset &off) const + { + return Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); + } + + const Offset Position::operator- (const Offset &off) const + { + return Offset(line - off.line, off.line == line ? column - off.column : column); + } + + /* not used anymore - remove? + std::ostream& operator<<(std::ostream& strm, const Offset& off) + { + if (off.line == string::npos) strm << "-1:"; else strm << off.line << ":"; + if (off.column == string::npos) strm << "-1"; else strm << off.column; + return strm; + } */ + + /* not used anymore - remove? + std::ostream& operator<<(std::ostream& strm, const Position& pos) + { + if (pos.file != string::npos) strm << pos.file << ":"; + if (pos.line == string::npos) strm << "-1:"; else strm << pos.line << ":"; + if (pos.column == string::npos) strm << "-1"; else strm << pos.column; + return strm; + } */ + +} diff --git a/src/position.hpp b/src/position.hpp new file mode 100644 index 000000000..923be3c59 --- /dev/null +++ b/src/position.hpp @@ -0,0 +1,124 @@ +#ifndef SASS_POSITION_H +#define SASS_POSITION_H + +#include +#include +// #include + +namespace Sass { + + + class Offset { + + public: // c-tor + Offset(const char chr); + Offset(const char* string); + Offset(const std::string& text); + Offset(const size_t line, const size_t column); + + // return new position, incremented by the given string + Offset add(const char* begin, const char* end); + Offset inc(const char* begin, const char* end) const; + + // init/create instance from const char substring + static Offset init(const char* beg, const char* end); + + public: // overload operators for position + void operator+= (const Offset &pos); + bool operator== (const Offset &pos) const; + bool operator!= (const Offset &pos) const; + Offset operator+ (const Offset &off) const; + Offset operator- (const Offset &off) const; + + public: // overload output stream operator + // friend std::ostream& operator<<(std::ostream& strm, const Offset& off); + + public: + Offset off() { return *this; } + + public: + size_t line; + size_t column; + + }; + + class Position : public Offset { + + public: // c-tor + Position(const size_t file); // line(0), column(0) + Position(const size_t file, const Offset& offset); + Position(const size_t line, const size_t column); // file(-1) + Position(const size_t file, const size_t line, const size_t column); + + public: // overload operators for position + void operator+= (const Offset &off); + bool operator== (const Position &pos) const; + bool operator!= (const Position &pos) const; + const Position operator+ (const Offset &off) const; + const Offset operator- (const Offset &off) const; + // return new position, incremented by the given string + Position add(const char* begin, const char* end); + Position inc(const char* begin, const char* end) const; + + public: // overload output stream operator + // friend std::ostream& operator<<(std::ostream& strm, const Position& pos); + + public: + size_t file; + + }; + + // Token type for representing lexed chunks of text + class Token { + public: + const char* prefix; + const char* begin; + const char* end; + + Token() + : prefix(0), begin(0), end(0) { } + Token(const char* b, const char* e) + : prefix(b), begin(b), end(e) { } + Token(const char* str) + : prefix(str), begin(str), end(str + strlen(str)) { } + Token(const char* p, const char* b, const char* e) + : prefix(p), begin(b), end(e) { } + + size_t length() const { return end - begin; } + std::string ws_before() const { return std::string(prefix, begin); } + const std::string to_string() const { return std::string(begin, end); } + std::string time_wspace() const { + std::string str(to_string()); + std::string whitespaces(" \t\f\v\n\r"); + return str.erase(str.find_last_not_of(whitespaces)+1); + } + + operator bool() { return begin && end && begin >= end; } + operator std::string() { return to_string(); } + + bool operator==(Token t) { return to_string() == t.to_string(); } + }; + + class ParserState : public Position { + + public: // c-tor + ParserState(const char* path, const char* src = 0, const size_t file = std::string::npos); + ParserState(const char* path, const char* src, const Position& position, Offset offset = Offset(0, 0)); + ParserState(const char* path, const char* src, const Token& token, const Position& position, Offset offset = Offset(0, 0)); + + public: // down casts + Offset off() { return *this; } + Position pos() { return *this; } + ParserState pstate() { return *this; } + + public: + const char* path; + const char* src; + Offset offset; + Token token; + + }; + +} + +#endif diff --git a/src/prelexer.cpp b/src/prelexer.cpp new file mode 100644 index 000000000..a43b1ee3c --- /dev/null +++ b/src/prelexer.cpp @@ -0,0 +1,1774 @@ +#include "sass.hpp" +#include +#include +#include +#include "util.hpp" +#include "position.hpp" +#include "prelexer.hpp" +#include "constants.hpp" + + +namespace Sass { + // using namespace Lexer; + using namespace Constants; + + namespace Prelexer { + + + /* + + def string_re(open, close) + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + end + end + + # A hash of regular expressions that are used for tokenizing strings. + # + # The key is a `[Symbol, Boolean]` pair. + # The symbol represents which style of quotation to use, + # while the boolean represents whether or not the string + # is following an interpolated segment. + STRING_REGULAR_EXPRESSIONS = { + :double => { + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + false => string_re('"', '"'), + true => string_re('', '"') + }, + :single => { + false => string_re("'", "'"), + true => string_re('', "'") + }, + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a + # non-standard version of http://www.w3.org/TR/css3-conditional/ + :url_prefix => { + false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + :domain => { + false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + } + } + */ + + /* + /#{open} + ( + \\. + | + \# (?!\{) + | + [^#{close}\\#] + )* + (#{close}|#\{) + /m + false => string_re('"', '"'), + true => string_re('', '"') + */ + extern const char string_double_negates[] = "\"\\#"; + const char* re_string_double_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_double_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'"'>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + extern const char string_single_negates[] = "'\\#"; + const char* re_string_single_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_single_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'\''>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + /* + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + */ + const char* re_string_uri_close(const char* src) + { + return sequence < + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < optional < W >, exactly <')'> >, + lookahead < exactly< hash_lbrace > > + > + >, + optional < + sequence < optional < W >, exactly <')'> > + > + >(src); + } + + const char* re_string_uri_open(const char* src) + { + return sequence < + exactly <'u'>, + exactly <'r'>, + exactly <'l'>, + exactly <'('>, + W, + alternatives< + quoted_string, + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < W, exactly <')'> >, + exactly< hash_lbrace > + > + > + > + >(src); + } + + // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. + const char* line_comment(const char* src) + { + return sequence< + exactly < + slash_slash + >, + non_greedy< + any_char, + end_of_line + > + >(src); + } + + // Match a block comment. + const char* block_comment(const char* src) + { + return sequence< + delimited_by< + slash_star, + star_slash, + false + > + >(src); + } + /* not use anymore - remove? + const char* block_comment_prefix(const char* src) { + return exactly(src); + } + // Match either comment. + const char* comment(const char* src) { + return line_comment(src); + } + */ + + // Match zero plus white-space or line_comments + const char* optional_css_whitespace(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_whitespace(const char* src) { + return one_plus< alternatives >(src); + } + // Match optional_css_whitepace plus block_comments + const char* optional_css_comments(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_comments(const char* src) { + return one_plus< alternatives >(src); + } + + // Match one backslash escaped char /\\./ + const char* escape_seq(const char* src) + { + return sequence< + exactly<'\\'>, + alternatives < + minmax_range< + 1, 3, xdigit + >, + any_char + >, + optional < + exactly <' '> + > + >(src); + } + + // Match identifier start + const char* identifier_alpha(const char* src) + { + return alternatives< + unicode_seq, + alpha, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match identifier after start + const char* identifier_alnum(const char* src) + { + return alternatives< + unicode_seq, + alnum, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match CSS identifiers. + const char* strict_identifier(const char* src) + { + return sequence< + one_plus < strict_identifier_alpha >, + zero_plus < strict_identifier_alnum > + // word_boundary not needed + >(src); + } + + // Match CSS identifiers. + const char* identifier(const char* src) + { + return sequence< + zero_plus< exactly<'-'> >, + one_plus < identifier_alpha >, + zero_plus < identifier_alnum > + // word_boundary not needed + >(src); + } + + const char* strict_identifier_alpha(const char* src) + { + return alternatives < + alpha, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + const char* strict_identifier_alnum(const char* src) + { + return alternatives < + alnum, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + // Match a single CSS unit + const char* one_unit(const char* src) + { + return sequence < + optional < exactly <'-'> >, + strict_identifier_alpha, + zero_plus < alternatives< + strict_identifier_alnum, + sequence < + one_plus < exactly<'-'> >, + strict_identifier_alpha + > + > > + >(src); + } + + // Match numerator/denominator CSS units + const char* multiple_units(const char* src) + { + return + sequence < + one_unit, + zero_plus < + sequence < + exactly <'*'>, + one_unit + > + > + >(src); + } + + // Match complex CSS unit identifiers + const char* unit_identifier(const char* src) + { + return sequence < + multiple_units, + optional < + sequence < + exactly <'/'>, + negate < sequence < + exactly < calc_fn_kwd >, + exactly < '(' > + > >, + multiple_units + > > + >(src); + } + + const char* identifier_alnums(const char* src) + { + return one_plus< identifier_alnum >(src); + } + + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src) { + return alternatives < + exactly < '+' >, + sequence < + exactly < '-' >, + optional_css_whitespace, + exactly< '-' > + > + >(src); + } + + // Match interpolant schemas + const char* identifier_schema(const char* src) { + + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + sequence < + optional < + exactly <'$'> + >, + identifier + >, + exactly <'-'> + > + >, + interpolant, + zero_plus < + alternatives < + digits, + sequence < + optional < + exactly <'$'> + >, + identifier + >, + quoted_string, + exactly<'-'> + > + > + > + >, + negate < + exactly<'%'> + > + > (src); + } + + // interpolants can be recursive/nested + const char* interpolant(const char* src) { + return recursive_scopes< exactly, exactly >(src); + } + + // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ + const char* single_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'\''>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '\'' > + > + >, + exactly <'\''> + >(src); + } + + // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ + const char* double_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'"'>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '"' > + > + >, + exactly <'"'> + >(src); + } + + // $re_quoted = /(?:$re_squote|$re_dquote)/ + const char* quoted_string(const char* src) { + // match a quoted string, while skipping interpolants + return alternatives< + single_quoted_string, + double_quoted_string + >(src); + } + + const char* sass_value(const char* src) { + return alternatives < + quoted_string, + identifier, + percentage, + hex, + dimension, + number + >(src); + } + + // this is basically `one_plus < sass_value >` + // takes care to not parse invalid combinations + const char* value_combinations(const char* src) { + // `2px-2px` is invalid combo + bool was_number = false; + const char* pos; + while (src) { + if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { + was_number = false; + src = pos; + } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { + was_number = true; + src = pos; + } else { + break; + } + } + return src; + } + + // must be at least one interpolant + // can be surrounded by sass values + // make sure to never parse (dim)(dim) + // since this wrongly consumes `2px-1px` + // `2px1px` is valid number (unit `px1px`) + const char* value_schema(const char* src) + { + return sequence < + one_plus < + sequence < + optional < value_combinations >, + interpolant, + optional < value_combinations > + > + > + >(src); + } + + // Match CSS '@' keywords. + const char* at_keyword(const char* src) { + return sequence, identifier>(src); + } + + /* + tok(%r{ + ( + \\. + | + (?!url\() + [^"'/\#!;\{\}] # " + | + /(?![\*\/]) + | + \#(?!\{) + | + !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. + )+ + }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || + interpolation(:warn_for_color) + */ + const char* re_almost_any_value_token(const char* src) { + + return alternatives < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + uri_prefix + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + >, + block_comment, + line_comment, + interpolant, + space, + sequence < + exactly<'u'>, + exactly<'r'>, + exactly<'l'>, + exactly<'('>, + zero_plus < + alternatives < + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + > + >, + // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + exactly<')'> + > + >(src); + } + + /* + DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, + :each, :while, :if, :else, :extend, :import, :media, :charset, :content, + :_moz_document, :at_root, :error] + */ + const char* re_special_directive(const char* src) { + return alternatives < + word < mixin_kwd >, + word < include_kwd >, + word < function_kwd >, + word < return_kwd >, + word < debug_kwd >, + word < warn_kwd >, + word < for_kwd >, + word < each_kwd >, + word < while_kwd >, + word < if_kwd >, + word < else_kwd >, + word < extend_kwd >, + word < import_kwd >, + word < media_kwd >, + word < charset_kwd >, + word < content_kwd >, + // exactly < moz_document_kwd >, + word < at_root_kwd >, + word < error_kwd > + >(src); + } + + const char* re_prefixed_directive(const char* src) { + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < alnum >, + exactly <'-'> + > + >, + exactly < supports_kwd > + >(src); + } + + const char* re_reference_combinator(const char* src) { + return sequence < + optional < + sequence < + zero_plus < + exactly <'-'> + >, + identifier, + exactly <'|'> + > + >, + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* static_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + re_reference_combinator, + exactly <'/'> + >(src); + } + + const char* schema_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + optional < + sequence < + css_ip_identifier, + exactly <'|'> + > + >, + css_ip_identifier, + exactly <'/'> + > (src); + } + + const char* kwd_import(const char* src) { + return word(src); + } + + const char* kwd_at_root(const char* src) { + return word(src); + } + + const char* kwd_with_directive(const char* src) { + return word(src); + } + + const char* kwd_without_directive(const char* src) { + return word(src); + } + + const char* kwd_media(const char* src) { + return word(src); + } + + const char* kwd_supports_directive(const char* src) { + return word(src); + } + + const char* kwd_mixin(const char* src) { + return word(src); + } + + const char* kwd_function(const char* src) { + return word(src); + } + + const char* kwd_return_directive(const char* src) { + return word(src); + } + + const char* kwd_include_directive(const char* src) { + return word(src); + } + + const char* kwd_content_directive(const char* src) { + return word(src); + } + + const char* kwd_charset_directive(const char* src) { + return word(src); + } + + const char* kwd_extend(const char* src) { + return word(src); + } + + + const char* kwd_if_directive(const char* src) { + return word(src); + } + + const char* kwd_else_directive(const char* src) { + return word(src); + } + const char* elseif_directive(const char* src) { + return sequence< exactly< else_kwd >, + optional_css_comments, + word< if_after_else_kwd > >(src); + } + + const char* kwd_for_directive(const char* src) { + return word(src); + } + + const char* kwd_from(const char* src) { + return word(src); + } + + const char* kwd_to(const char* src) { + return word(src); + } + + const char* kwd_through(const char* src) { + return word(src); + } + + const char* kwd_each_directive(const char* src) { + return word(src); + } + + const char* kwd_in(const char* src) { + return word(src); + } + + const char* kwd_while_directive(const char* src) { + return word(src); + } + + const char* name(const char* src) { + return one_plus< alternatives< alnum, + exactly<'-'>, + exactly<'_'>, + escape_seq > >(src); + } + + const char* kwd_warn(const char* src) { + return word(src); + } + + const char* kwd_err(const char* src) { + return word(src); + } + + const char* kwd_dbg(const char* src) { + return word(src); + } + + /* not used anymore - remove? + const char* directive(const char* src) { + return sequence< exactly<'@'>, identifier >(src); + } */ + + const char* kwd_null(const char* src) { + return word(src); + } + + const char* css_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* css_ip_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + alternatives < + identifier, + interpolant + > + >(src); + } + + // Match CSS type selectors + const char* namespace_prefix(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_identifier + > + >, + exactly <'|'>, + negate < + exactly <'='> + > + >(src); + } + + // Match CSS type selectors + const char* namespace_schema(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_ip_identifier + > + >, + exactly<'|'>, + negate < + exactly <'='> + > + >(src); + } + + const char* hyphens_and_identifier(const char* src) { + return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); + } + const char* hyphens_and_name(const char* src) { + return sequence< zero_plus< exactly< '-' > >, name >(src); + } + const char* universal(const char* src) { + return sequence< optional, exactly<'*'> >(src); + } + // Match CSS id names. + const char* id_name(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS class names. + const char* class_name(const char* src) { + return sequence, identifier >(src); + } + // Attribute name in an attribute selector. + const char* attribute_name(const char* src) { + return alternatives< sequence< optional, identifier>, + identifier >(src); + } + // match placeholder selectors + const char* placeholder(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS numeric constants. + + const char* op(const char* src) { + return class_char(src); + } + const char* sign(const char* src) { + return class_char(src); + } + const char* unsigned_number(const char* src) { + return alternatives, + exactly<'.'>, + one_plus >, + digits>(src); + } + const char* number(const char* src) { + return sequence< + optional, + unsigned_number, + optional< + sequence< + exactly<'e'>, + optional, + unsigned_number + > + > + >(src); + } + const char* coefficient(const char* src) { + return alternatives< sequence< optional, digits >, + sign >(src); + } + const char* binomial(const char* src) { + return sequence < + optional < sign >, + optional < digits >, + exactly <'n'>, + zero_plus < sequence < + optional_css_whitespace, sign, + optional_css_whitespace, digits + > > + >(src); + } + const char* percentage(const char* src) { + return sequence< number, exactly<'%'> >(src); + } + const char* ampersand(const char* src) { + return exactly<'&'>(src); + } + + /* not used anymore - remove? + const char* em(const char* src) { + return sequence< number, exactly >(src); + } */ + const char* dimension(const char* src) { + return sequence(src); + } + const char* hex(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 4 && len != 7) ? 0 : p; + } + const char* hexa(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 9) ? 0 : p; + } + const char* hex0(const char* src) { + const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 8) ? 0 : p; + } + + /* no longer used - remove? + const char* rgb_prefix(const char* src) { + return word(src); + }*/ + // Match CSS uri specifiers. + + const char* uri_prefix(const char* src) { + return sequence < + exactly < + url_kwd + >, + zero_plus < + sequence < + exactly <'-'>, + one_plus < + alpha + > + > + >, + exactly <'('> + >(src); + } + + // TODO: rename the following two functions + /* no longer used - remove? + const char* uri(const char* src) { + return sequence< exactly, + optional, + quoted_string, + optional, + exactly<')'> >(src); + }*/ + /* no longer used - remove? + const char* url_value(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename + optional< exactly<'/'> > >(src); + }*/ + /* no longer used - remove? + const char* url_schema(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + filename_schema >(src); // optional trailing slash + }*/ + // Match CSS "!important" keyword. + const char* kwd_important(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS "!optional" keyword. + const char* kwd_optional(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!default" keyword. + const char* default_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!global" keyword. + const char* global_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS pseudo-class/element prefixes. + const char* pseudo_prefix(const char* src) { + return sequence< exactly<':'>, optional< exactly<':'> > >(src); + } + // Match CSS function call openers. + const char* functional_schema(const char* src) { + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + identifier, + exactly <'-'> + > + >, + one_plus < + sequence < + interpolant, + alternatives < + digits, + identifier, + exactly<'+'>, + exactly<'-'> + > + > + > + > + >, + negate < + exactly <'%'> + >, + lookahead < + exactly <'('> + > + > (src); + } + + const char* re_nothing(const char* src) { + return src; + } + + const char* re_functional(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + const char* re_pseudo_selector(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + // Match the CSS negation pseudo-class. + const char* pseudo_not(const char* src) { + return word< pseudo_not_fn_kwd >(src); + } + // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. + const char* even(const char* src) { + return word(src); + } + const char* odd(const char* src) { + return word(src); + } + // Match CSS attribute-matching operators. + const char* exact_match(const char* src) { return exactly<'='>(src); } + const char* class_match(const char* src) { return exactly(src); } + const char* dash_match(const char* src) { return exactly(src); } + const char* prefix_match(const char* src) { return exactly(src); } + const char* suffix_match(const char* src) { return exactly(src); } + const char* substring_match(const char* src) { return exactly(src); } + // Match CSS combinators. + /* not used anymore - remove? + const char* adjacent_to(const char* src) { + return sequence< optional_spaces, exactly<'+'> >(src); + } + const char* precedes(const char* src) { + return sequence< optional_spaces, exactly<'~'> >(src); + } + const char* parent_of(const char* src) { + return sequence< optional_spaces, exactly<'>'> >(src); + } + const char* ancestor_of(const char* src) { + return sequence< spaces, negate< exactly<'{'> > >(src); + }*/ + + // Match SCSS variable names. + const char* variable(const char* src) { + return sequence, identifier>(src); + } + + // parse `calc`, `-a-calc` and `--b-c-calc` + // but do not parse `foocalc` or `foo-calc` + const char* calc_fn_call(const char* src) { + return sequence < + optional < sequence < + hyphens, + one_plus < sequence < + strict_identifier, + hyphens + > > + > >, + exactly < calc_fn_kwd >, + word_boundary + >(src); + } + + // Match Sass boolean keywords. + const char* kwd_true(const char* src) { + return word(src); + } + const char* kwd_false(const char* src) { + return word(src); + } + const char* kwd_only(const char* src) { + return keyword < only_kwd >(src); + } + const char* kwd_and(const char* src) { + return keyword < and_kwd >(src); + } + const char* kwd_or(const char* src) { + return keyword < or_kwd >(src); + } + const char* kwd_not(const char* src) { + return keyword < not_kwd >(src); + } + const char* kwd_eq(const char* src) { + return exactly(src); + } + const char* kwd_neq(const char* src) { + return exactly(src); + } + const char* kwd_gt(const char* src) { + return exactly(src); + } + const char* kwd_gte(const char* src) { + return exactly(src); + } + const char* kwd_lt(const char* src) { + return exactly(src); + } + const char* kwd_lte(const char* src) { + return exactly(src); + } + + // match specific IE syntax + const char* ie_progid(const char* src) { + return sequence < + word, + exactly<':'>, + alternatives< identifier_schema, identifier >, + zero_plus< sequence< + exactly<'.'>, + alternatives< identifier_schema, identifier > + > >, + zero_plus < sequence< + exactly<'('>, + optional_css_whitespace, + optional < sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, + zero_plus< sequence< + optional_css_whitespace, + exactly<','>, + optional_css_whitespace, + sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > + > + > > + > >, + optional_css_whitespace, + exactly<')'> + > > + >(src); + } + const char* ie_expression(const char* src) { + return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); + } + const char* ie_property(const char* src) { + return alternatives < ie_expression, ie_progid >(src); + } + + // const char* ie_args(const char* src) { + // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, + // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); + // } + + const char* ie_keyword_arg_property(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier + >(src); + } + const char* ie_keyword_arg_value(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier, + quoted_string, + number, + hex, + hexa, + sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + > + >(src); + } + + const char* ie_keyword_arg(const char* src) { + return sequence < + ie_keyword_arg_property, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + ie_keyword_arg_value + >(src); + } + + // Path matching functions. + /* not used anymore - remove? + const char* folder(const char* src) { + return sequence< zero_plus< any_char_except<'/'> >, + exactly<'/'> >(src); + } + const char* folders(const char* src) { + return zero_plus< folder >(src); + }*/ + /* not used anymore - remove? + const char* chunk(const char* src) { + char inside_str = 0; + const char* p = src; + size_t depth = 0; + while (true) { + if (!*p) { + return 0; + } + else if (!inside_str && (*p == '"' || *p == '\'')) { + inside_str = *p; + } + else if (*p == inside_str && *(p-1) != '\\') { + inside_str = 0; + } + else if (*p == '(' && !inside_str) { + ++depth; + } + else if (*p == ')' && !inside_str) { + if (depth == 0) return p; + else --depth; + } + ++p; + } + // unreachable + return 0; + } + */ + + // follow the CSS spec more closely and see if this helps us scan URLs correctly + /* not used anymore - remove? + const char* NL(const char* src) { + return alternatives< exactly<'\n'>, + sequence< exactly<'\r'>, exactly<'\n'> >, + exactly<'\r'>, + exactly<'\f'> >(src); + }*/ + + const char* H(const char* src) { + return std::isxdigit(*src) ? src+1 : 0; + } + + const char* W(const char* src) { + return zero_plus< alternatives< + space, + exactly< '\t' >, + exactly< '\r' >, + exactly< '\n' >, + exactly< '\f' > + > >(src); + } + + const char* UUNICODE(const char* src) { + return sequence< exactly<'\\'>, + between, + optional< W > + >(src); + } + + const char* NONASCII(const char* src) { + return nonascii(src); + } + + const char* ESCAPE(const char* src) { + return alternatives< + UUNICODE, + sequence< + exactly<'\\'>, + alternatives< + NONASCII, + escapable_character + > + > + >(src); + } + + const char* list_terminator(const char* src) { + return alternatives < + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<']'>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + >(src); + }; + + const char* space_list_terminator(const char* src) { + return alternatives < + exactly<','>, + list_terminator + >(src); + }; + + + // const char* real_uri_prefix(const char* src) { + // return alternatives< + // exactly< url_kwd >, + // exactly< url_prefix_kwd > + // >(src); + // } + + const char* real_uri(const char* src) { + return sequence< + exactly< url_kwd >, + exactly< '(' >, + W, + real_uri_value, + exactly< ')' > + >(src); + } + + const char* real_uri_suffix(const char* src) { + return sequence< W, exactly< ')' > >(src); + } + + const char* real_uri_value(const char* src) { + return + sequence< + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + real_uri_suffix, + exactly< hash_lbrace > + > + > + > + (src); + } + + const char* static_string(const char* src) { + const char* pos = src; + const char * s = quoted_string(pos); + Token t(pos, s); + const unsigned int p = count_interval< interpolant >(t.begin, t.end); + return (p == 0) ? t.end : 0; + } + + const char* unicode_seq(const char* src) { + return sequence < + alternatives < + exactly< 'U' >, + exactly< 'u' > + >, + exactly< '+' >, + padded_token < + 6, xdigit, + exactly < '?' > + > + >(src); + } + + const char* static_component(const char* src) { + return alternatives< identifier, + static_string, + percentage, + hex, + hexa, + exactly<'|'>, + // exactly<'+'>, + sequence < number, unit_identifier >, + number, + sequence< exactly<'!'>, word > + >(src); + } + + const char* static_property(const char* src) { + return + sequence < + zero_plus< + sequence < + optional_css_comments, + alternatives < + exactly<','>, + exactly<'('>, + exactly<')'>, + kwd_optional, + quoted_string, + interpolant, + identifier, + percentage, + dimension, + variable, + alnum, + sequence < + exactly <'\\'>, + any_char + > + > + > + >, + lookahead < + sequence < + optional_css_comments, + alternatives < + exactly <';'>, + exactly <'}'>, + end_of_file + > + > + > + >(src); + } + + const char* static_value(const char* src) { + return sequence< sequence< + static_component, + zero_plus< identifier > + >, + zero_plus < sequence< + alternatives< + sequence< optional_spaces, alternatives< + exactly < '/' >, + exactly < ',' >, + exactly < ' ' > + >, optional_spaces >, + spaces + >, + static_component + > >, + zero_plus < spaces >, + alternatives< exactly<';'>, exactly<'}'> > + >(src); + } + + extern const char css_variable_url_negates[] = "()[]{}\"'#/"; + const char* css_variable_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; + const char* css_variable_top_level_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_top_level_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + const char* parenthese_scope(const char* src) { + return sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + >(src); + } + + const char* re_selector_list(const char* src) { + return alternatives < + // partial bem selector + sequence < + ampersand, + one_plus < + exactly < '-' > + >, + word_boundary, + optional_spaces + >, + // main selector matching + one_plus < + alternatives < + // consume whitespace and comments + spaces, block_comment, line_comment, + // match `/deep/` selector (pass-trough) + // there is no functionality for it yet + schema_reference_combinator, + // match selector ops /[*&%,\[\]]/ + class_char < selector_lookahead_ops >, + // match selector combinators /[>+~]/ + class_char < selector_combinator_ops >, + // match pseudo selectors + sequence < + exactly <'('>, + optional_spaces, + optional , + optional_spaces, + exactly <')'> + >, + // match attribute compare operators + alternatives < + exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match + >, + // main selector match + sequence < + // allow namespace prefix + optional < namespace_schema >, + // modifiers prefixes + alternatives < + sequence < + exactly <'#'>, + // not for interpolation + negate < exactly <'{'> > + >, + // class match + exactly <'.'>, + // single or double colon + sequence < + optional < pseudo_prefix >, + // fix libsass issue 2376 + negate < uri_prefix > + > + >, + // accept hypens in token + one_plus < sequence < + // can start with hyphens + zero_plus < + sequence < + exactly <'-'>, + optional_spaces + > + >, + // now the main token + alternatives < + kwd_optional, + exactly <'*'>, + quoted_string, + interpolant, + identifier, + variable, + percentage, + binomial, + dimension, + alnum + > + > >, + // can also end with hyphens + zero_plus < exactly<'-'> > + > + > + > + >(src); + } + + const char* type_selector(const char* src) { + return sequence< optional, identifier>(src); + } + const char* re_type_selector(const char* src) { + return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); + } + const char* re_static_expression(const char* src) { + return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); + } + + // lexer special_fn: these functions cannot be overloaded + // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) + const char* re_special_fun(const char* src) { + + // match this first as we test prefix hyphens + if (const char* calc = calc_fn_call(src)) { + return calc; + } + + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < + alternatives < + alpha, + exactly <'+'>, + exactly <'-'> + > + > + > + >, + alternatives < + word < expression_kwd >, + sequence < + sequence < + exactly < progid_kwd >, + exactly <':'> + >, + zero_plus < + alternatives < + char_range <'a', 'z'>, + exactly <'.'> + > + > + > + > + >(src); + } + + } +} diff --git a/src/prelexer.hpp b/src/prelexer.hpp new file mode 100644 index 000000000..2d8f83164 --- /dev/null +++ b/src/prelexer.hpp @@ -0,0 +1,484 @@ +#ifndef SASS_PRELEXER_H +#define SASS_PRELEXER_H + +#include +#include "lexer.hpp" + +namespace Sass { + // using namespace Lexer; + namespace Prelexer { + + //#################################### + // KEYWORD "REGEX" MATCHERS + //#################################### + + // Match Sass boolean keywords. + const char* kwd_true(const char* src); + const char* kwd_false(const char* src); + const char* kwd_only(const char* src); + const char* kwd_and(const char* src); + const char* kwd_or(const char* src); + const char* kwd_not(const char* src); + const char* kwd_eq(const char* src); + const char* kwd_neq(const char* src); + const char* kwd_gt(const char* src); + const char* kwd_gte(const char* src); + const char* kwd_lt(const char* src); + const char* kwd_lte(const char* src); + + // Match standard control chars + const char* kwd_at(const char* src); + const char* kwd_dot(const char* src); + const char* kwd_comma(const char* src); + const char* kwd_colon(const char* src); + const char* kwd_slash(const char* src); + const char* kwd_star(const char* src); + const char* kwd_plus(const char* src); + const char* kwd_minus(const char* src); + + //#################################### + // SPECIAL "REGEX" CONSTRUCTS + //#################################### + + // Match a sequence of characters delimited by the supplied chars. + template + const char* delimited_by(const char* src) { + src = exactly(src); + if (!src) return 0; + const char* stop; + while (true) { + if (!*src) return 0; + stop = exactly(src); + if (stop && (!esc || *(src - 1) != '\\')) return stop; + src = stop ? stop : src + 1; + } + } + + // skip to delimiter (mx) inside given range + // this will savely skip over all quoted strings + // recursive skip stuff delimited by start/stop + // first start/opener must be consumed already! + template + const char* skip_over_scopes(const char* src, const char* end) { + + size_t level = 0; + bool in_squote = false; + bool in_dquote = false; + // bool in_braces = false; + + while (*src) { + + // check for abort condition + if (end && src >= end) break; + + // has escaped sequence? + if (*src == '\\') { + ++ src; // skip this (and next) + } + else if (*src == '"') { + in_dquote = ! in_dquote; + } + else if (*src == '\'') { + in_squote = ! in_squote; + } + else if (in_dquote || in_squote) { + // take everything literally + } + + // find another opener inside? + else if (const char* pos = start(src)) { + ++ level; // increase counter + src = pos - 1; // advance position + } + + // look for the closer (maybe final, maybe not) + else if (const char* final = stop(src)) { + // only close one level? + if (level > 0) -- level; + // return position at end of stop + // delimiter may be multiple chars + else return final; + // advance position + src = final - 1; + } + + // next + ++ src; + } + + return 0; + } + + // skip to a skip delimited by parentheses + // uses smart `skip_over_scopes` internally + const char* parenthese_scope(const char* src); + + // skip to delimiter (mx) inside given range + // this will savely skip over all quoted strings + // recursive skip stuff delimited by start/stop + // first start/opener must be consumed already! + template + const char* skip_over_scopes(const char* src) { + return skip_over_scopes(src, 0); + } + + // Match a sequence of characters delimited by the supplied chars. + template + const char* recursive_scopes(const char* src) { + // parse opener + src = start(src); + // abort if not found + if (!src) return 0; + // parse the rest until final closer + return skip_over_scopes(src); + } + + // Match a sequence of characters delimited by the supplied strings. + template + const char* delimited_by(const char* src) { + src = exactly(src); + if (!src) return 0; + const char* stop; + while (true) { + if (!*src) return 0; + stop = exactly(src); + if (stop && (!esc || *(src - 1) != '\\')) return stop; + src = stop ? stop : src + 1; + } + } + + // Tries to match a certain number of times (between the supplied interval). + template + const char* between(const char* src) { + for (size_t i = 0; i < lo; ++i) { + src = mx(src); + if (!src) return 0; + } + for (size_t i = lo; i <= hi; ++i) { + const char* new_src = mx(src); + if (!new_src) return src; + src = new_src; + } + return src; + } + + // equivalent of STRING_REGULAR_EXPRESSIONS + const char* re_string_double_open(const char* src); + const char* re_string_double_close(const char* src); + const char* re_string_single_open(const char* src); + const char* re_string_single_close(const char* src); + const char* re_string_uri_open(const char* src); + const char* re_string_uri_close(const char* src); + + // Match a line comment. + const char* line_comment(const char* src); + + // Match a block comment. + const char* block_comment(const char* src); + // Match either. + const char* comment(const char* src); + // Match double- and single-quoted strings. + const char* double_quoted_string(const char* src); + const char* single_quoted_string(const char* src); + const char* quoted_string(const char* src); + // Match interpolants. + const char* interpolant(const char* src); + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src); + + // Match zero plus white-space or line_comments + const char* optional_css_whitespace(const char* src); + const char* css_whitespace(const char* src); + // Match optional_css_whitepace plus block_comments + const char* optional_css_comments(const char* src); + const char* css_comments(const char* src); + + // Match one backslash escaped char + const char* escape_seq(const char* src); + + // Match CSS css variables. + const char* custom_property_name(const char* src); + // Match a CSS identifier. + const char* identifier(const char* src); + const char* identifier_alpha(const char* src); + const char* identifier_alnum(const char* src); + const char* strict_identifier(const char* src); + const char* strict_identifier_alpha(const char* src); + const char* strict_identifier_alnum(const char* src); + // Match a CSS unit identifier. + const char* one_unit(const char* src); + const char* multiple_units(const char* src); + const char* unit_identifier(const char* src); + // const char* strict_identifier_alnums(const char* src); + // Match reference selector. + const char* re_reference_combinator(const char* src); + const char* static_reference_combinator(const char* src); + const char* schema_reference_combinator(const char* src); + + // Match interpolant schemas + const char* identifier_schema(const char* src); + const char* value_schema(const char* src); + const char* sass_value(const char* src); + // const char* filename(const char* src); + // const char* filename_schema(const char* src); + // const char* url_schema(const char* src); + // const char* url_value(const char* src); + const char* vendor_prefix(const char* src); + + const char* re_special_directive(const char* src); + const char* re_prefixed_directive(const char* src); + const char* re_almost_any_value_token(const char* src); + + // Match CSS '@' keywords. + const char* at_keyword(const char* src); + const char* kwd_import(const char* src); + const char* kwd_at_root(const char* src); + const char* kwd_with_directive(const char* src); + const char* kwd_without_directive(const char* src); + const char* kwd_media(const char* src); + const char* kwd_supports_directive(const char* src); + // const char* keyframes(const char* src); + // const char* keyf(const char* src); + const char* kwd_mixin(const char* src); + const char* kwd_function(const char* src); + const char* kwd_return_directive(const char* src); + const char* kwd_include_directive(const char* src); + const char* kwd_content_directive(const char* src); + const char* kwd_charset_directive(const char* src); + const char* kwd_extend(const char* src); + + const char* unicode_seq(const char* src); + + const char* kwd_if_directive(const char* src); + const char* kwd_else_directive(const char* src); + const char* elseif_directive(const char* src); + + const char* kwd_for_directive(const char* src); + const char* kwd_from(const char* src); + const char* kwd_to(const char* src); + const char* kwd_through(const char* src); + + const char* kwd_each_directive(const char* src); + const char* kwd_in(const char* src); + + const char* kwd_while_directive(const char* src); + + const char* re_nothing(const char* src); + + const char* re_special_fun(const char* src); + + const char* kwd_warn(const char* src); + const char* kwd_err(const char* src); + const char* kwd_dbg(const char* src); + + const char* kwd_null(const char* src); + + const char* re_selector_list(const char* src); + const char* re_type_selector(const char* src); + const char* re_static_expression(const char* src); + + // identifier that can start with hyphens + const char* css_identifier(const char* src); + const char* css_ip_identifier(const char* src); + + // Match CSS type selectors + const char* namespace_schema(const char* src); + const char* namespace_prefix(const char* src); + const char* type_selector(const char* src); + const char* hyphens_and_identifier(const char* src); + const char* hyphens_and_name(const char* src); + const char* universal(const char* src); + // Match CSS id names. + const char* id_name(const char* src); + // Match CSS class names. + const char* class_name(const char* src); + // Attribute name in an attribute selector + const char* attribute_name(const char* src); + // Match placeholder selectors. + const char* placeholder(const char* src); + // Match CSS numeric constants. + const char* op(const char* src); + const char* sign(const char* src); + const char* unsigned_number(const char* src); + const char* number(const char* src); + const char* coefficient(const char* src); + const char* binomial(const char* src); + const char* percentage(const char* src); + const char* ampersand(const char* src); + const char* dimension(const char* src); + const char* hex(const char* src); + const char* hexa(const char* src); + const char* hex0(const char* src); + // const char* rgb_prefix(const char* src); + // Match CSS uri specifiers. + const char* uri_prefix(const char* src); + // Match CSS "!important" keyword. + const char* kwd_important(const char* src); + // Match CSS "!optional" keyword. + const char* kwd_optional(const char* src); + // Match Sass "!default" keyword. + const char* default_flag(const char* src); + const char* global_flag(const char* src); + // Match CSS pseudo-class/element prefixes + const char* pseudo_prefix(const char* src); + // Match CSS function call openers. + const char* re_functional(const char* src); + const char* re_pseudo_selector(const char* src); + const char* functional_schema(const char* src); + const char* pseudo_not(const char* src); + // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. + const char* even(const char* src); + const char* odd(const char* src); + // Match CSS attribute-matching operators. + const char* exact_match(const char* src); + const char* class_match(const char* src); + const char* dash_match(const char* src); + const char* prefix_match(const char* src); + const char* suffix_match(const char* src); + const char* substring_match(const char* src); + // Match CSS combinators. + // const char* adjacent_to(const char* src); + // const char* precedes(const char* src); + // const char* parent_of(const char* src); + // const char* ancestor_of(const char* src); + + // Match SCSS variable names. + const char* variable(const char* src); + const char* calc_fn_call(const char* src); + + // IE stuff + const char* ie_progid(const char* src); + const char* ie_expression(const char* src); + const char* ie_property(const char* src); + const char* ie_keyword_arg(const char* src); + const char* ie_keyword_arg_value(const char* src); + const char* ie_keyword_arg_property(const char* src); + + // characters that terminate parsing of a list + const char* list_terminator(const char* src); + const char* space_list_terminator(const char* src); + + // match url() + const char* H(const char* src); + const char* W(const char* src); + // `UNICODE` makes VS sad + const char* UUNICODE(const char* src); + const char* NONASCII(const char* src); + const char* ESCAPE(const char* src); + const char* real_uri(const char* src); + const char* real_uri_suffix(const char* src); + // const char* real_uri_prefix(const char* src); + const char* real_uri_value(const char* src); + + // Path matching functions. + // const char* folder(const char* src); + // const char* folders(const char* src); + + + const char* static_string(const char* src); + const char* static_component(const char* src); + const char* static_property(const char* src); + const char* static_value(const char* src); + + const char* css_variable_value(const char* src); + const char* css_variable_top_level_value(const char* src); + + // Utility functions for finding and counting characters in a string. + template + const char* find_first(const char* src) { + while (*src && *src != c) ++src; + return *src ? src : 0; + } + template + const char* find_first(const char* src) { + while (*src && !mx(src)) ++src; + return *src ? src : 0; + } + template + const char* find_first_in_interval(const char* beg, const char* end) { + bool esc = false; + while ((beg < end) && *beg) { + if (esc) esc = false; + else if (*beg == '\\') esc = true; + else if (mx(beg)) return beg; + ++beg; + } + return 0; + } + template + const char* find_first_in_interval(const char* beg, const char* end) { + bool esc = false; + while ((beg < end) && *beg) { + if (esc) esc = false; + else if (*beg == '\\') esc = true; + else if (const char* pos = skip(beg)) beg = pos; + else if (mx(beg)) return beg; + ++beg; + } + return 0; + } + template + unsigned int count_interval(const char* beg, const char* end) { + unsigned int counter = 0; + bool esc = false; + while (beg < end && *beg) { + const char* p; + if (esc) { + esc = false; + ++beg; + } else if (*beg == '\\') { + esc = true; + ++beg; + } else if ((p = mx(beg))) { + ++counter; + beg = p; + } + else { + ++beg; + } + } + return counter; + } + + template + const char* padded_token(const char* src) + { + size_t got = 0; + const char* pos = src; + while (got < size) { + if (!mx(pos)) break; + ++ pos; ++ got; + } + while (got < size) { + if (!pad(pos)) break; + ++ pos; ++ got; + } + return got ? pos : 0; + } + + template + const char* minmax_range(const char* src) + { + size_t got = 0; + const char* pos = src; + while (got < max) { + if (!mx(pos)) break; + ++ pos; ++ got; + } + if (got < min) return 0; + if (got > max) return 0; + return pos; + } + + template + const char* char_range(const char* src) + { + if (*src < min) return 0; + if (*src > max) return 0; + return src + 1; + } + + } +} + +#endif diff --git a/src/remove_placeholders.cpp b/src/remove_placeholders.cpp new file mode 100644 index 000000000..15cddace2 --- /dev/null +++ b/src/remove_placeholders.cpp @@ -0,0 +1,84 @@ +#include "sass.hpp" +#include "remove_placeholders.hpp" +#include "context.hpp" +#include "inspect.hpp" +#include + +namespace Sass { + + Remove_Placeholders::Remove_Placeholders() + { } + + void Remove_Placeholders::operator()(Block_Ptr b) { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr st = b->at(i); + st->perform(this); + } + } + + Selector_List_Ptr Remove_Placeholders::remove_placeholders(Selector_List_Ptr sl) + { + Selector_List_Ptr new_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); + + for (size_t i = 0, L = sl->length(); i < L; ++i) { + if (!sl->at(i)->contains_placeholder()) { + new_sl->append(sl->at(i)); + } + } + + return new_sl; + + } + + + void Remove_Placeholders::operator()(Ruleset_Ptr r) { + // Create a new selector group without placeholders + Selector_List_Obj sl = Cast(r->selector()); + + if (sl) { + // Set the new placeholder selector list + r->selector(remove_placeholders(sl)); + // Remove placeholders in wrapped selectors + for (Complex_Selector_Obj cs : sl->elements()) { + while (cs) { + if (cs->head()) { + for (Simple_Selector_Obj& ss : cs->head()->elements()) { + if (Wrapped_Selector_Ptr ws = Cast(ss)) { + if (Selector_List_Ptr wsl = Cast(ws->selector())) { + Selector_List_Ptr clean = remove_placeholders(wsl); + // also clean superflous parent selectors + // probably not really the correct place + clean->remove_parent_selectors(); + ws->selector(clean); + } + } + } + } + cs = cs->tail(); + } + } + } + + // Iterate into child blocks + Block_Obj b = r->block(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->at(i)) { + Statement_Obj st = b->at(i); + st->perform(this); + } + } + } + + void Remove_Placeholders::operator()(Media_Block_Ptr m) { + operator()(m->block()); + } + void Remove_Placeholders::operator()(Supports_Block_Ptr m) { + operator()(m->block()); + } + + void Remove_Placeholders::operator()(Directive_Ptr a) { + if (a->block()) a->block()->perform(this); + } + +} diff --git a/src/remove_placeholders.hpp b/src/remove_placeholders.hpp new file mode 100644 index 000000000..c13b63134 --- /dev/null +++ b/src/remove_placeholders.hpp @@ -0,0 +1,35 @@ +#ifndef SASS_REMOVE_PLACEHOLDERS_H +#define SASS_REMOVE_PLACEHOLDERS_H + +#pragma once + +#include "ast.hpp" +#include "operation.hpp" + +namespace Sass { + + + class Remove_Placeholders : public Operation_CRTP { + + void fallback_impl(AST_Node_Ptr n) {} + + public: + Selector_List_Ptr remove_placeholders(Selector_List_Ptr); + + public: + Remove_Placeholders(); + ~Remove_Placeholders() { } + + void operator()(Block_Ptr); + void operator()(Ruleset_Ptr); + void operator()(Media_Block_Ptr); + void operator()(Supports_Block_Ptr); + void operator()(Directive_Ptr); + + template + void fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/sass.cpp b/src/sass.cpp new file mode 100644 index 000000000..72edd7ced --- /dev/null +++ b/src/sass.cpp @@ -0,0 +1,151 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "sass.h" +#include "file.hpp" +#include "util.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // helper to convert string list to vector + std::vector list2vec(struct string_list* cur) + { + std::vector list; + while (cur) { + list.push_back(cur->string); + cur = cur->next; + } + return list; + } + +} + +extern "C" { + using namespace Sass; + + // Allocate libsass heap memory + // Don't forget string termination! + void* ADDCALL sass_alloc_memory(size_t size) + { + void* ptr = malloc(size); + if (ptr == NULL) { + std::cerr << "Out of memory.\n"; + exit(EXIT_FAILURE); + } + return ptr; + } + + char* ADDCALL sass_copy_c_string(const char* str) + { + size_t len = strlen(str) + 1; + char* cpy = (char*) sass_alloc_memory(len); + std::memcpy(cpy, str, len); + return cpy; + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_memory(void* ptr) + { + if (ptr) free (ptr); + } + + // caller must free the returned memory + char* ADDCALL sass_string_quote (const char *str, const char quote_mark) + { + std::string quoted = quote(str, quote_mark); + return sass_copy_c_string(quoted.c_str()); + } + + // caller must free the returned memory + char* ADDCALL sass_string_unquote (const char *str) + { + std::string unquoted = unquote(str); + return sass_copy_c_string(unquoted.c_str()); + } + + char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_include(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_file(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + // this has the original resolve logic for sass include + char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_include(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_file(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Get compiled libsass version + const char* ADDCALL libsass_version(void) + { + return LIBSASS_VERSION; + } + + // Get compiled libsass version + const char* ADDCALL libsass_language_version(void) + { + return LIBSASS_LANGUAGE_VERSION; + } + +} + +namespace Sass { + + // helper to aid dreaded MSVC debug mode + char* sass_copy_string(std::string str) + { + // In MSVC the following can lead to segfault: + // sass_copy_c_string(stream.str().c_str()); + // Reason is that the string returned by str() is disposed before + // sass_copy_c_string is invoked. The string is actually a stack + // object, so indeed nobody is holding on to it. So it seems + // perfectly fair to release it right away. So the const char* + // by c_str will point to invalid memory. I'm not sure if this is + // the behavior for all compiler, but I'm pretty sure we would + // have gotten more issues reported if that would be the case. + // Wrapping it in a functions seems the cleanest approach as the + // function must hold on to the stack variable until it's done. + return sass_copy_c_string(str.c_str()); + } + +} \ No newline at end of file diff --git a/src/sass.hpp b/src/sass.hpp new file mode 100644 index 000000000..f0550490c --- /dev/null +++ b/src/sass.hpp @@ -0,0 +1,139 @@ +// must be the first include in all compile units +#ifndef SASS_SASS_H +#define SASS_SASS_H + +// undefine extensions macro to tell sys includes +// that we do not want any macros to be exported +// mainly fixes an issue on SmartOS (SEC macro) +#undef __EXTENSIONS__ + +#ifdef _MSC_VER +#pragma warning(disable : 4005) +#endif + +// aplies to MSVC and MinGW +#ifdef _WIN32 +// we do not want the ERROR macro +# define NOGDI +// we do not want the min/max macro +# define NOMINMAX +// we do not want the IN/OUT macro +# define _NO_W32_PSEUDO_MODIFIERS +#endif + + +// should we be case insensitive +// when dealing with files or paths +#ifndef FS_CASE_SENSITIVE +# ifdef _WIN32 +# define FS_CASE_SENSITIVE 0 +# else +# define FS_CASE_SENSITIVE 1 +# endif +#endif + +// path separation char +#ifndef PATH_SEP +# ifdef _WIN32 +# define PATH_SEP ';' +# else +# define PATH_SEP ':' +# endif +#endif + + +// include C-API header +#include "sass/base.h" + +// For C++ helper +#include + +// output behaviours +namespace Sass { + + // create some C++ aliases for the most used options + const static Sass_Output_Style NESTED = SASS_STYLE_NESTED; + const static Sass_Output_Style COMPACT = SASS_STYLE_COMPACT; + const static Sass_Output_Style EXPANDED = SASS_STYLE_EXPANDED; + const static Sass_Output_Style COMPRESSED = SASS_STYLE_COMPRESSED; + // only used internal to trigger ruby inspect behavior + const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; + const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; + + // helper to aid dreaded MSVC debug mode + // see implementation for more details + char* sass_copy_string(std::string str); + +} + +// input behaviours +enum Sass_Input_Style { + SASS_CONTEXT_NULL, + SASS_CONTEXT_FILE, + SASS_CONTEXT_DATA, + SASS_CONTEXT_FOLDER +}; + +// simple linked list +struct string_list { + string_list* next; + char* string; +}; + +// sass config options structure +struct Sass_Inspect_Options { + + // Output style for the generated css code + // A value from above SASS_STYLE_* constants + enum Sass_Output_Style output_style; + + // Precision for fractional numbers + int precision; + + // Do not compress colors in selectors + bool in_selector; + + // initialization list (constructor with defaults) + Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, + int precision = 5, bool in_selector = false) + : output_style(style), precision(precision), in_selector(in_selector) + { } + +}; + +// sass config options structure +struct Sass_Output_Options : Sass_Inspect_Options { + + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + + // Emit comments in the generated CSS indicating + // the corresponding source line. + bool source_comments; + + // initialization list (constructor with defaults) + Sass_Output_Options(struct Sass_Inspect_Options opt, + const char* indent = " ", + const char* linefeed = "\n", + bool source_comments = false) + : Sass_Inspect_Options(opt), + indent(indent), linefeed(linefeed), + source_comments(source_comments) + { } + + // initialization list (constructor with defaults) + Sass_Output_Options(Sass_Output_Style style = Sass::NESTED, + int precision = 5, + const char* indent = " ", + const char* linefeed = "\n", + bool source_comments = false) + : Sass_Inspect_Options(style, precision), + indent(indent), linefeed(linefeed), + source_comments(source_comments) + { } + +}; + +#endif diff --git a/src/sass2scss.cpp b/src/sass2scss.cpp new file mode 100644 index 000000000..56333b38e --- /dev/null +++ b/src/sass2scss.cpp @@ -0,0 +1,864 @@ +/** + * sass2scss + * Licensed under the MIT License + * Copyright (c) Marcel Greter + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +// include library +#include +#include +#include +#include +#include +#include +#include + +///* +// +// src comments: comments in sass syntax (staring with //) +// css comments: multiline comments in css syntax (starting with /*) +// +// KEEP_COMMENT: keep src comments in the resulting css code +// STRIP_COMMENT: strip out all comments (either src or css) +// CONVERT_COMMENT: convert all src comments to css comments +// +//*/ + +// our own header +#include "sass2scss.h" + +// add namespace for c++ +namespace Sass +{ + + // return the actual prettify value from options + #define PRETTIFY(converter) (converter.options - (converter.options & 248)) + // query the options integer to check if the option is enables + #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) + #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) + #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) + + // some makros to access the indentation stack + #define INDENT(converter) (converter.indents.top()) + + // some makros to query comment parser status + #define IS_PARSING(converter) (converter.comment == "") + #define IS_COMMENT(converter) (converter.comment != "") + #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) + #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) + + // pretty printer helper function + static std::string closer (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " }" : + PRETTIFY(converter) <= 1 ? " }" : + "\n" + INDENT(converter) + "}"; + } + + // pretty printer helper function + static std::string opener (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " { " : + PRETTIFY(converter) <= 2 ? " {" : + "\n" + INDENT(converter) + "{"; + } + + // check if the given string is a pseudo selector + // needed to differentiate from sass property syntax + static bool isPseudoSelector (std::string& sel) + { + + size_t len = sel.length(); + if (len < 1) return false; + size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); + if (pos != std::string::npos) sel.erase(pos, std::string::npos); + size_t i = sel.length(); + while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } + + // CSS Level 1 - Recommendation + if (sel == ":link") return true; + if (sel == ":visited") return true; + if (sel == ":active") return true; + + // CSS Level 2 (Revision 1) - Recommendation + if (sel == ":lang") return true; + if (sel == ":first-child") return true; + if (sel == ":hover") return true; + if (sel == ":focus") return true; + // disabled - also valid properties + // if (sel == ":left") return true; + // if (sel == ":right") return true; + if (sel == ":first") return true; + + // Selectors Level 3 - Recommendation + if (sel == ":target") return true; + if (sel == ":root") return true; + if (sel == ":nth-child") return true; + if (sel == ":nth-last-of-child") return true; + if (sel == ":nth-of-type") return true; + if (sel == ":nth-last-of-type") return true; + if (sel == ":last-child") return true; + if (sel == ":first-of-type") return true; + if (sel == ":last-of-type") return true; + if (sel == ":only-child") return true; + if (sel == ":only-of-type") return true; + if (sel == ":empty") return true; + if (sel == ":not") return true; + + // CSS Basic User Interface Module Level 3 - Working Draft + if (sel == ":default") return true; + if (sel == ":valid") return true; + if (sel == ":invalid") return true; + if (sel == ":in-range") return true; + if (sel == ":out-of-range") return true; + if (sel == ":required") return true; + if (sel == ":optional") return true; + if (sel == ":read-only") return true; + if (sel == ":read-write") return true; + if (sel == ":dir") return true; + if (sel == ":enabled") return true; + if (sel == ":disabled") return true; + if (sel == ":checked") return true; + if (sel == ":indeterminate") return true; + if (sel == ":nth-last-child") return true; + + // Selectors Level 4 - Working Draft + if (sel == ":any-link") return true; + if (sel == ":local-link") return true; + if (sel == ":scope") return true; + if (sel == ":active-drop-target") return true; + if (sel == ":valid-drop-target") return true; + if (sel == ":invalid-drop-target") return true; + if (sel == ":current") return true; + if (sel == ":past") return true; + if (sel == ":future") return true; + if (sel == ":placeholder-shown") return true; + if (sel == ":user-error") return true; + if (sel == ":blank") return true; + if (sel == ":nth-match") return true; + if (sel == ":nth-last-match") return true; + if (sel == ":nth-column") return true; + if (sel == ":nth-last-column") return true; + if (sel == ":matches") return true; + + // Fullscreen API - Living Standard + if (sel == ":fullscreen") return true; + + // not a pseudo selector + return false; + + } + + // check if there is some char data + // will ignore everything in comments + static bool hasCharData (std::string& sass) + { + + size_t col_pos = 0; + + while (true) + { + + // try to find some meaningfull char + col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); + + // there was no meaningfull char found + if (col_pos == std::string::npos) return false; + + // found a multiline comment opener + if (sass.substr(col_pos, 2) == "/*") + { + // find the multiline comment closer + col_pos = sass.find("*/", col_pos); + // maybe we did not find the closer here + if (col_pos == std::string::npos) return false; + // skip closer + col_pos += 2; + } + else + { + return true; + } + + } + + } + // EO hasCharData + + // find src comment opener + // correctly skips quoted strings + static size_t findCommentOpener (std::string& sass) + { + + size_t col_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + size_t brackets = 0; + + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*()", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + if (character == '(') + { + if (!quoted && !apoed) brackets ++; + } + else if (character == ')') + { + if (!quoted && !apoed) brackets --; + } + else if (character == '\"') + { + // invert quote bool + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + // invert quote bool + if (!quoted && !comment) apoed = !apoed; + } + else if (col_pos > 0 && character == '/') + { + if (sass.at(col_pos - 1) == '*') + { + comment = false; + } + // next needs to be a slash too + else if (sass.at(col_pos - 1) == '/') + { + // only found if not in single or double quote, bracket or comment + if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; + } + } + else if (character == '\\') + { + // skip next char if in quote + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (col_pos > 0 && character == '*') + { + // opening a multiline comment + if (sass.at(col_pos - 1) == '/') + { + // we are now in a comment + if (!quoted && !apoed) comment = true; + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + return col_pos; + + } + // EO findCommentOpener + + // remove multiline comments from sass string + // correctly skips quoted strings + static std::string removeMultilineComment (std::string &sass) + { + + std::string clean = ""; + size_t col_pos = 0; + size_t open_pos = 0; + size_t close_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + + // process sass til string end + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + // found quoted string delimiter + if (character == '\"') + { + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + if (!quoted && !comment) apoed = !apoed; + } + // found possible comment closer + else if (character == '/') + { + // look back to see if it is actually a closer + if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') + { + close_pos = col_pos + 1; comment = false; + } + } + else if (character == '\\') + { + // skip escaped char + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (character == '*') + { + // look back to see if it is actually an opener + if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') + { + comment = true; open_pos = col_pos - 1; + clean += sass.substr(close_pos, open_pos - close_pos); + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + // add final parts (add half open comment text) + if (comment) clean += sass.substr(open_pos); + else clean += sass.substr(close_pos); + + // return string + return clean; + + } + // EO removeMultilineComment + + // right trim a given string + std::string rtrim(const std::string &sass) + { + std::string trimmed = sass; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) + { trimmed.erase(pos_ws + 1); } + else { trimmed.clear(); } + return trimmed; + } + // EO rtrim + + // flush whitespace and print additional text, but + // only print additional chars and buffer whitespace + std::string flush (std::string& sass, converter& converter) + { + + // return flushed + std::string scss = ""; + + // print whitespace buffer + scss += PRETTIFY(converter) > 0 ? + converter.whitespace : ""; + // reset whitespace buffer + converter.whitespace = ""; + + // remove possible newlines from string + size_t pos_right = sass.find_last_not_of("\n\r"); + if (pos_right == std::string::npos) return scss; + + // get the linefeeds from the string + std::string lfs = sass.substr(pos_right + 1); + sass = sass.substr(0, pos_right + 1); + + // find some source comment opener + size_t comment_pos = findCommentOpener(sass); + // check if there was a source comment + if (comment_pos != std::string::npos) + { + // convert comment (but only outside other coments) + if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) + { + // convert to multiline comment + sass.at(comment_pos + 1) = '*'; + // add comment node to the whitespace + sass += " */"; + } + // not at line start + if (comment_pos > 0) + { + // also include whitespace before the actual comment opener + size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); + comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; + } + if (!STRIP_COMMENT(converter)) + { + // add comment node to the whitespace + converter.whitespace += sass.substr(comment_pos); + } + else + { + // sass = removeMultilineComments(sass); + } + // update the actual sass code + sass = sass.substr(0, comment_pos); + } + + // add newline as getline discharged it + converter.whitespace += lfs + "\n"; + + // maybe remove any leading whitespace + if (PRETTIFY(converter) == 0) + { + // remove leading whitespace and update string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + if (pos_left != std::string::npos) sass = sass.substr(pos_left); + } + + // add flushed data + scss += sass; + + // return string + return scss; + + } + // EO flush + + // process a line of the sass text + std::string process (std::string& sass, converter& converter) + { + + // resulting string + std::string scss = ""; + + // strip multi line comments + if (STRIP_COMMENT(converter)) + { + sass = removeMultilineComment(sass); + } + + // right trim input + sass = rtrim(sass); + + // get postion of first meaningfull character in string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + + // special case for final run + if (converter.end_of_file) pos_left = 0; + + // maybe has only whitespace + if (pos_left == std::string::npos) + { + // just add complete whitespace + converter.whitespace += sass + "\n"; + } + // have meaningfull first char + else + { + + // extract and store indentation string + std::string indent = sass.substr(0, pos_left); + + // check if current line starts a comment + std::string open = sass.substr(pos_left, 2); + + // line has less or same indentation + // finalize previous open parser context + if (indent.length() <= INDENT(converter).length()) + { + + // close multilinie comment + if (IS_CSS_COMMENT(converter)) + { + // check if comments will be stripped anyway + if (!STRIP_COMMENT(converter)) scss += " */"; + } + // close src comment comment + else if (IS_SRC_COMMENT(converter)) + { + // add a newline to avoid closer on same line + // this would put the bracket in the comment node + // no longer needed since we parse them correctly + // if (KEEP_COMMENT(converter)) scss += "\n"; + } + // close css properties + else if (converter.property) + { + // add closer unless in concat mode + if (!converter.comma) + { + // if there was no colon we have a selector + // looks like there were no inner properties + if (converter.selector) scss += " {}"; + // add final semicolon + else if (!converter.semicolon) scss += ";"; + } + } + + // reset comment state + converter.comment = ""; + + } + + // make sure we close every "higher" block + while (indent.length() < INDENT(converter).length()) + { + // pop stacked context + converter.indents.pop(); + // print close bracket + if (IS_PARSING(converter)) + { scss += closer(converter); } + else { scss += " */"; } + // reset comment state + converter.comment = ""; + } + + // reset converter state + converter.selector = false; + + // looks like some undocumented behavior ... + // https://github.com/mgreter/sass2scss/issues/29 + if (sass.substr(pos_left, 1) == "\\") { + converter.selector = true; + sass[pos_left] = ' '; + } + + // check if we have sass property syntax + if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") + { + + // default to a selector + // change back if property found + converter.selector = true; + // get postion of first whitespace char + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + // assertion check for valid result + if (pos_wspace != std::string::npos) + { + // get the possible pseudo selector + std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); + // get position of the first real property value char + // pseudo selectors get this far, but have no actual value + size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); + // assertion check for valid result + if (pos_value != std::string::npos) + { + // only process if not (fallowed by a semicolon or is a pseudo selector) + if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) + { + // create new string by interchanging the colon sign for property and value + sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); + // try to find a colon in the current line, but only ... + size_t pos_colon = sass.find_first_not_of(":", pos_left); + // assertion for valid result + if (pos_colon != std::string::npos) + { + // ... after the first word (skip begining colons) + pos_colon = sass.find_first_of(":", pos_colon); + // it is a selector if there was no colon found + converter.selector = pos_colon == std::string::npos; + } + } + } + } + + // check if we have a BEM property (one colon and no selector) + if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; + } + + } + + // terminate some statements immediately + else if ( + sass.substr(pos_left, 5) == "@warn" || + sass.substr(pos_left, 6) == "@debug" || + sass.substr(pos_left, 6) == "@error" || + sass.substr(pos_left, 8) == "@charset" || + sass.substr(pos_left, 10) == "@namespace" + ) { sass = indent + sass.substr(pos_left); } + // replace some specific sass shorthand directives (if not fallowed by a white space character) + else if (sass.substr(pos_left, 1) == "=") + { sass = indent + "@mixin " + sass.substr(pos_left + 1); } + else if (sass.substr(pos_left, 1) == "+") + { + // must be followed by a mixin call (no whitespace afterwards or at ending directly) + if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { + sass = indent + "@include " + sass.substr(pos_left + 1); + } + } + + // add quotes for import if needed + else if (sass.substr(pos_left, 7) == "@import") + { + // get positions for the actual import url + size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); + size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); + // leave proper urls untouched + if (sass.substr(pos_quote, 4) != "url(") + { + // check if the url appears to be already quoted + if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'") + { + // get position of the last char on the line + size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + // assertion check for valid result + if (pos_end != std::string::npos) + { + // add quotes around the full line after the import statement + sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\""; + } + } + } + + } + else if ( + sass.substr(pos_left, 7) != "@return" && + sass.substr(pos_left, 7) != "@extend" && + sass.substr(pos_left, 8) != "@include" && + sass.substr(pos_left, 8) != "@content" + ) { + + // probably a selector anyway + converter.selector = true; + // try to find first colon in the current line + size_t pos_colon = sass.find_first_of(":", pos_left); + // assertion that we have a colon + if (pos_colon != std::string::npos) + { + // it is not a selector if we have a space after a colon + if (sass[pos_colon+1] == ' ') converter.selector = false; + if (sass[pos_colon+1] == ' ') converter.selector = false; + } + + } + + // current line has more indentation + if (indent.length() >= INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // has meaningfull chars + if (hasCharData(sass)) + { + // is probably a property + // also true for selectors + converter.property = true; + } + } + } + // current line has more indentation + if (indent.length() > INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // had meaningfull chars + if (converter.property) + { + // print block opener + scss += opener(converter); + // push new stack context + converter.indents.push(""); + // store block indentation + INDENT(converter) = indent; + } + } + // is and will be a src comment + else if (!IS_CSS_COMMENT(converter)) + { + // scss does not allow multiline src comments + // therefore add forward slashes to all lines + sass.at(INDENT(converter).length()+0) = '/'; + // there is an edge case here if indentation + // is minimal (will overwrite the fist char) + sass.at(INDENT(converter).length()+1) = '/'; + // could code around that, but I dont' think + // this will ever be the cause for any trouble + } + } + + // line is opening a new comment + if (open == "/*" || open == "//") + { + // reset the property state + converter.property = false; + // close previous comment + if (IS_CSS_COMMENT(converter) && open != "") + { + if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; + } + // force single line comments + // into a correct css comment + if (CONVERT_COMMENT(converter)) + { + if (IS_PARSING(converter)) + { sass.at(pos_left + 1) = '*'; } + } + // set comment flag + converter.comment = open; + + } + + // flush data only under certain conditions + if (!( + // strip css and src comments if option is set + (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || + // strip src comment even if strip option is not set + // but only if the keep src comment option is not set + (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) + )) + { + // flush data and buffer whitespace + scss += flush(sass, converter); + } + + // get postion of last meaningfull char + size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + + // check for invalid result + if (pos_right != std::string::npos) + { + + // get the last meaningfull char + std::string close = sass.substr(pos_right, 1); + + // check if next line should be concatenated (list mode) + converter.comma = IS_PARSING(converter) && close == ","; + converter.semicolon = IS_PARSING(converter) && close == ";"; + + // check if we have more than + // one meaningfull char + if (pos_right > 0) + { + + // get the last two chars from string + std::string close = sass.substr(pos_right - 1, 2); + // update parser status for expicitly closed comment + if (close == "*/") converter.comment = ""; + + } + + } + // EO have meaningfull chars from end + + } + // EO have meaningfull chars from start + + // return scss + return scss; + + } + // EO process + + // read line with either CR, LF or CR LF format + // http://stackoverflow.com/a/6089413/1550314 + static std::istream& safeGetline(std::istream& is, std::string& t) + { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf* sb = is.rdbuf(); + + for(;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if(sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if(t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char)c; + } + } + } + + // the main converter function for c++ + char* sass2scss (const std::string& sass, const int options) + { + + // local variables + std::string line; + std::string scss = ""; + std::stringstream stream(sass); + + // create converter variable + converter converter; + // initialise all options + converter.comma = false; + converter.property = false; + converter.selector = false; + converter.semicolon = false; + converter.end_of_file = false; + converter.comment = ""; + converter.whitespace = ""; + converter.indents.push(""); + converter.options = options; + + // read line by line and process them + while(safeGetline(stream, line) && !stream.eof()) + { scss += process(line, converter); } + + // create mutable string + std::string closer = ""; + // set the end of file flag + converter.end_of_file = true; + // process to close all open blocks + scss += process(closer, converter); + + // allocate new memory on the heap + // caller has to free it after use + char * cstr = (char*) malloc (scss.length() + 1); + // create a copy of the string + strcpy (cstr, scss.c_str()); + // return pointer + return &cstr[0]; + + } + // EO sass2scss + +} +// EO namespace + +// implement for c +extern "C" +{ + + char* ADDCALL sass2scss (const char* sass, const int options) + { + return Sass::sass2scss(sass, options); + } + + // Get compiled sass2scss version + const char* ADDCALL sass2scss_version(void) { + return SASS2SCSS_VERSION; + } + +} diff --git a/src/sass_context.cpp b/src/sass_context.cpp new file mode 100644 index 000000000..afadc66e1 --- /dev/null +++ b/src/sass_context.cpp @@ -0,0 +1,769 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include + +#include "sass.h" +#include "ast.hpp" +#include "file.hpp" +#include "json.hpp" +#include "util.hpp" +#include "context.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" +#include "ast_fwd_decl.hpp" +#include "error_handling.hpp" + +#define LFEED "\n" + +// C++ helper +namespace Sass { + // see sass_copy_c_string(std::string str) + static inline JsonNode* json_mkstream(const std::stringstream& stream) + { + // hold on to string on stack! + std::string str(stream.str()); + return json_mkstring(str.c_str()); + } + + static int handle_error(Sass_Context* c_ctx) { + try { + throw; + } + catch (Exception::Base& e) { + std::stringstream msg_stream; + std::string cwd(Sass::File::get_cwd()); + std::string msg_prefix(e.errtype()); + bool got_newline = false; + msg_stream << msg_prefix << ": "; + const char* msg = e.what(); + while (msg && *msg) { + if (*msg == '\r') { + got_newline = true; + } + else if (*msg == '\n') { + got_newline = true; + } + else if (got_newline) { + msg_stream << std::string(msg_prefix.size() + 2, ' '); + got_newline = false; + } + msg_stream << *msg; + ++msg; + } + if (!got_newline) msg_stream << "\n"; + + if (e.traces.empty()) { + // we normally should have some traces, still here as a fallback + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << std::string(msg_prefix.size() + 2, ' '); + msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; + } + else { + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << traces_to_string(e.traces, " "); + } + + // now create the code trace (ToDo: maybe have util functions?) + if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { + size_t lines = e.pstate.line; + const char* line_beg = e.pstate.src; + // scan through src until target line + // move line_beg pointer to line start + while (line_beg && *line_beg && lines != 0) { + if (*line_beg == '\n') --lines; + utf8::unchecked::next(line_beg); + } + const char* line_end = line_beg; + // move line_end before next newline character + while (line_end && *line_end && *line_end != '\n') { + if (*line_end == '\n') break; + if (*line_end == '\r') break; + utf8::unchecked::next(line_end); + } + if (line_end && *line_end != 0) ++ line_end; + size_t line_len = line_end - line_beg; + size_t move_in = 0; size_t shorten = 0; + size_t left_chars = 42; size_t max_chars = 76; + // reported excerpt should not exceed `max_chars` chars + if (e.pstate.column > line_len) left_chars = e.pstate.column; + if (e.pstate.column > left_chars) move_in = e.pstate.column - left_chars; + if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; + utf8::advance(line_beg, move_in, line_end); + utf8::retreat(line_end, shorten, line_beg); + std::string sanitized; std::string marker(e.pstate.column - move_in, '-'); + utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); + msg_stream << ">> " << sanitized << "\n"; + msg_stream << " " << marker << "^\n"; + } + + JsonNode* json_err = json_mkobject(); + json_append_member(json_err, "status", json_mknumber(1)); + json_append_member(json_err, "file", json_mkstring(e.pstate.path)); + json_append_member(json_err, "line", json_mknumber((double)(e.pstate.line + 1))); + json_append_member(json_err, "column", json_mknumber((double)(e.pstate.column + 1))); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 1; + c_ctx->error_file = sass_copy_c_string(e.pstate.path); + c_ctx->error_line = e.pstate.line + 1; + c_ctx->error_column = e.pstate.column + 1; + c_ctx->error_src = e.pstate.src; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::bad_alloc& ba) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(2)); + json_append_member(json_err, "message", json_mkstring(ba.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(ba.what()); + c_ctx->error_status = 2; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::exception& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(3)); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 3; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::string& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e.c_str())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.c_str()); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (const char* e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e)); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (...) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unknown error occurred" << std::endl; + json_append_member(json_err, "status", json_mknumber(5)); + json_append_member(json_err, "message", json_mkstring("unknown")); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string("unknown"); + c_ctx->error_status = 5; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + return c_ctx->error_status; + } + + // allow one error handler to throw another error + // this can happen with invalid utf8 and json lib + static int handle_errors(Sass_Context* c_ctx) { + try { return handle_error(c_ctx); } + catch (...) { return handle_error(c_ctx); } + } + + static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() + { + + // assert valid pointer + if (compiler == 0) return 0; + // The cpp context must be set by now + Context* cpp_ctx = compiler->cpp_ctx; + Sass_Context* c_ctx = compiler->c_ctx; + // We will take care to wire up the rest + compiler->cpp_ctx->c_compiler = compiler; + compiler->state = SASS_COMPILER_PARSED; + + try { + + // get input/output path from options + std::string input_path = safe_str(c_ctx->input_path); + std::string output_path = safe_str(c_ctx->output_path); + + // maybe skip some entries of included files + // we do not include stdin for data contexts + bool skip = c_ctx->type == SASS_CONTEXT_DATA; + + // dispatch parse call + Block_Obj root(cpp_ctx->parse()); + // abort on errors + if (!root) return 0; + + // skip all prefixed files? (ToDo: check srcmap) + // IMO source-maps should point to headers already + // therefore don't skip it for now. re-enable or + // remove completely once this is tested + size_t headers = cpp_ctx->head_imports; + + // copy the included files on to the context (dont forget to free later) + if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) + throw(std::bad_alloc()); + + // return parsed block + return root; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + +} + +extern "C" { + using namespace Sass; + + static void sass_clear_options (struct Sass_Options* options); + static void sass_reset_options (struct Sass_Options* options); + static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { + // do not overwrite ourself + if (to == from) return; + // free assigned memory + sass_clear_options(to); + // move memory + *to = *from; + // Reset pointers on source + sass_reset_options(from); + } + + #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } + #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } + #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ + { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } + #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) + + #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ + type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } + #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ + type sass_context_take_##option (struct Sass_Context* ctx) \ + { type foo = ctx->option; ctx->option = 0; return foo; } + + + // generic compilation function (not exported, use file/data compile instead) + static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() + { + try { + // register our custom functions + if (c_ctx->c_functions) { + auto this_func_data = c_ctx->c_functions; + while (this_func_data && *this_func_data) { + cpp_ctx->add_c_function(*this_func_data); + ++this_func_data; + } + } + + // register our custom headers + if (c_ctx->c_headers) { + auto this_head_data = c_ctx->c_headers; + while (this_head_data && *this_head_data) { + cpp_ctx->add_c_header(*this_head_data); + ++this_head_data; + } + } + + // register our custom importers + if (c_ctx->c_importers) { + auto this_imp_data = c_ctx->c_importers; + while (this_imp_data && *this_imp_data) { + cpp_ctx->add_c_importer(*this_imp_data); + ++this_imp_data; + } + } + + // reset error status + c_ctx->error_json = 0; + c_ctx->error_text = 0; + c_ctx->error_message = 0; + c_ctx->error_status = 0; + // reset error position + c_ctx->error_src = 0; + c_ctx->error_file = 0; + c_ctx->error_line = std::string::npos; + c_ctx->error_column = std::string::npos; + + // allocate a new compiler instance + void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); + if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } + Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; + compiler->state = SASS_COMPILER_CREATED; + + // store in sass compiler + compiler->c_ctx = c_ctx; + compiler->cpp_ctx = cpp_ctx; + cpp_ctx->c_compiler = compiler; + + // use to parse block + return compiler; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + + // generic compilation function (not exported, use file/data compile instead) + static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) + { + + // prepare sass compiler with context and options + Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); + + try { + // call each compiler step + sass_compiler_parse(compiler); + sass_compiler_execute(compiler); + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + sass_delete_compiler(compiler); + + return c_ctx->error_status; + } + + inline void init_options (struct Sass_Options* options) + { + options->precision = 5; + options->indent = " "; + options->linefeed = LFEED; + } + + Sass_Options* ADDCALL sass_make_options (void) + { + struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); + if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } + init_options(options); + return options; + } + + Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) + { + SharedObj::setTaint(true); // needed for static colors + struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_FILE; + init_options(ctx); + try { + if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } + if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } + sass_option_set_input_path(ctx, input_path); + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) + { + struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_DATA; + init_options(ctx); + try { + if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } + if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } + ctx->source_string = source_string; + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 0; + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_prepare_context(data_ctx, cpp_ctx); + } + + struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 0; + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_prepare_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 1; + if (data_ctx->error_status) + return data_ctx->error_status; + try { + if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } + // empty source string is a valid case, even if not really usefull (different than with file context) + // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } + } + catch (...) { return handle_errors(data_ctx) | 1; } + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_compile_context(data_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 1; + if (file_ctx->error_status) + return file_ctx->error_status; + try { + if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } + if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } + } + catch (...) { return handle_errors(file_ctx) | 1; } + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_compile_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_PARSED) return 0; + if (compiler->state != SASS_COMPILER_CREATED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + // parse the context we have set up (file or data) + compiler->root = sass_parse_block(compiler); + // success + return 0; + } + + int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_EXECUTED) return 0; + if (compiler->state != SASS_COMPILER_PARSED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->root.isNull()) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + compiler->state = SASS_COMPILER_EXECUTED; + Context* cpp_ctx = compiler->cpp_ctx; + Block_Obj root = compiler->root; + // compile the parsed root block + try { compiler->c_ctx->output_string = cpp_ctx->render(root); } + // pass catched errors to generic error handler + catch (...) { return handle_errors(compiler->c_ctx) | 1; } + // generate source map json and store on context + compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); + // success + return 0; + } + + // helper function, not exported, only accessible locally + static void sass_reset_options (struct Sass_Options* options) + { + // free pointer before + // or copy/move them + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + } + + // helper function, not exported, only accessible locally + static void sass_clear_options (struct Sass_Options* options) + { + if (options == 0) return; + // Deallocate custom functions, headers and importes + sass_delete_function_list(options->c_functions); + sass_delete_importer_list(options->c_importers); + sass_delete_importer_list(options->c_headers); + // Deallocate inc paths + if (options->plugin_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->plugin_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Deallocate inc paths + if (options->include_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->include_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Free options strings + free(options->input_path); + free(options->output_path); + free(options->plugin_path); + free(options->include_path); + free(options->source_map_file); + free(options->source_map_root); + // Reset our pointers + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + } + + // helper function, not exported, only accessible locally + // sass_free_context is also defined in old sass_interface + static void sass_clear_context (struct Sass_Context* ctx) + { + if (ctx == 0) return; + // release the allocated memory (mostly via sass_copy_c_string) + if (ctx->output_string) free(ctx->output_string); + if (ctx->source_map_string) free(ctx->source_map_string); + if (ctx->error_message) free(ctx->error_message); + if (ctx->error_text) free(ctx->error_text); + if (ctx->error_json) free(ctx->error_json); + if (ctx->error_file) free(ctx->error_file); + free_string_array(ctx->included_files); + // play safe and reset properties + ctx->output_string = 0; + ctx->source_map_string = 0; + ctx->error_message = 0; + ctx->error_text = 0; + ctx->error_json = 0; + ctx->error_file = 0; + ctx->included_files = 0; + // debug leaked memory + #ifdef DEBUG_SHARED_PTR + SharedObj::dumpMemLeaks(); + #endif + // now clear the options + sass_clear_options(ctx); + } + + void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) + { + if (compiler == 0) { + return; + } + Context* cpp_ctx = compiler->cpp_ctx; + if (cpp_ctx) delete(cpp_ctx); + compiler->cpp_ctx = NULL; + compiler->c_ctx = NULL; + compiler->root = NULL; + free(compiler); + } + + void ADDCALL sass_delete_options (struct Sass_Options* options) + { + sass_clear_options(options); free(options); + } + + // Deallocate all associated memory with file context + void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) + { + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + // Deallocate all associated memory with data context + void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) + { + // clean the source string if it was not passed + // we reset this member once we start parsing + if (ctx->source_string) free(ctx->source_string); + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + + // Getters for sass context from specific implementations + struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } + + // Getters for context options from Sass_Context + struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } + void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + + // Getters for Sass_Compiler options (get conected sass context) + enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } + struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + // Getters for Sass_Compiler options (query import stack) + size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } + Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } + Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } + // Getters for Sass_Compiler options (query function stack) + size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } + + // Calculate the size of the stored null terminated array + size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) + { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } + + // Create getter and setters for options + IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); + IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); + + // Create getter and setters for context + IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); + IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); + IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); + IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); + + // Take ownership of memory (value on context is set to 0) + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); + IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); + + // Push function for include paths (no manipulation support for now) + void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) + { + + struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (include_path == 0) return; + include_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->include_paths; + if (!options->include_paths) { + options->include_paths = include_path; + } else { + while (last->next) + last = last->next; + last->next = include_path; + } + + } + + // Push function for include paths (no manipulation support for now) + size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) + { + size_t len = 0; + struct string_list* cur = options->include_paths; + while (cur) { len ++; cur = cur->next; } + return len; + } + + // Push function for include paths (no manipulation support for now) + const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) + { + struct string_list* cur = options->include_paths; + while (i) { i--; cur = cur->next; } + return cur->string; + } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) + { + + struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (plugin_path == 0) return; + plugin_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->plugin_paths; + if (!options->plugin_paths) { + options->plugin_paths = plugin_path; + } else { + while (last->next) + last = last->next; + last->next = plugin_path; + } + + } + +} diff --git a/src/sass_context.hpp b/src/sass_context.hpp new file mode 100644 index 000000000..8ae1fb12c --- /dev/null +++ b/src/sass_context.hpp @@ -0,0 +1,129 @@ +#ifndef SASS_SASS_CONTEXT_H +#define SASS_SASS_CONTEXT_H + +#include "sass/base.h" +#include "sass/context.h" +#include "ast_fwd_decl.hpp" + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. LibSass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Maybe use array interface instead? + char* include_path; + char* plugin_path; + + // Include paths (linked string list) + struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; + + // Path to source map file + // Enables source map generation + // Used to create sourceMappingUrl + char* source_map_file; + + // Directly inserted in source maps + char* source_map_root; + + // Custom functions that can be called from sccs code + Sass_Function_List c_functions; + + // List of custom importers + Sass_Importer_List c_importers; + + // List of custom headers + Sass_Importer_List c_headers; + +}; + + +// base for all contexts +struct Sass_Context : Sass_Options +{ + + // store context type info + enum Sass_Input_Style type; + + // generated output data + char* output_string; + + // generated source map json + char* source_map_string; + + // error status + int error_status; + char* error_json; + char* error_text; + char* error_message; + // error position + char* error_file; + size_t error_line; + size_t error_column; + const char* error_src; + + // report imported files + char** included_files; + +}; + +// struct for file compilation +struct Sass_File_Context : Sass_Context { + + // no additional fields required + // input_path is already on options + +}; + +// struct for data compilation +struct Sass_Data_Context : Sass_Context { + + // provided source string + char* source_string; + char* srcmap_string; + +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + Sass::Context* cpp_ctx; + // Sass::Block + Sass::Block_Obj root; +}; + +#endif \ No newline at end of file diff --git a/src/sass_functions.cpp b/src/sass_functions.cpp new file mode 100644 index 000000000..bfbf25838 --- /dev/null +++ b/src/sass_functions.cpp @@ -0,0 +1,207 @@ +#include "sass.hpp" +#include +#include "util.hpp" +#include "context.hpp" +#include "values.hpp" +#include "sass/functions.h" +#include "sass_functions.hpp" + +extern "C" { + using namespace Sass; + + Sass_Function_List ADDCALL sass_make_function_list(size_t length) + { + return (Sass_Function_List) calloc(length + 1, sizeof(Sass_Function_Entry)); + } + + Sass_Function_Entry ADDCALL sass_make_function(const char* signature, Sass_Function_Fn function, void* cookie) + { + Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); + if (cb == 0) return 0; + cb->signature = sass_copy_c_string(signature); + cb->function = function; + cb->cookie = cookie; + return cb; + } + + void ADDCALL sass_delete_function(Sass_Function_Entry entry) + { + free(entry->signature); + free(entry); + } + + // Deallocator for the allocated memory + void ADDCALL sass_delete_function_list(Sass_Function_List list) + { + Sass_Function_List it = list; + if (list == 0) return; + while(*list) { + sass_delete_function(*list); + ++list; + } + free(it); + } + + // Setters and getters for callbacks on function lists + Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; } + void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; } + + const char* ADDCALL sass_function_get_signature(Sass_Function_Entry cb) { return cb->signature; } + Sass_Function_Fn ADDCALL sass_function_get_function(Sass_Function_Entry cb) { return cb->function; } + void* ADDCALL sass_function_get_cookie(Sass_Function_Entry cb) { return cb->cookie; } + + Sass_Importer_Entry ADDCALL sass_make_importer(Sass_Importer_Fn importer, double priority, void* cookie) + { + Sass_Importer_Entry cb = (Sass_Importer_Entry) calloc(1, sizeof(Sass_Importer)); + if (cb == 0) return 0; + cb->importer = importer; + cb->priority = priority; + cb->cookie = cookie; + return cb; + } + + Sass_Importer_Fn ADDCALL sass_importer_get_function(Sass_Importer_Entry cb) { return cb->importer; } + double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb) { return cb->priority; } + void* ADDCALL sass_importer_get_cookie(Sass_Importer_Entry cb) { return cb->cookie; } + + // Just in case we have some stray import structs + void ADDCALL sass_delete_importer (Sass_Importer_Entry cb) + { + free(cb); + } + + // Creator for sass custom importer function list + Sass_Importer_List ADDCALL sass_make_importer_list(size_t length) + { + return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry)); + } + + // Deallocator for the allocated memory + void ADDCALL sass_delete_importer_list(Sass_Importer_List list) + { + Sass_Importer_List it = list; + if (list == 0) return; + while(*list) { + sass_delete_importer(*list); + ++list; + } + free(it); + } + + Sass_Importer_Entry ADDCALL sass_importer_get_list_entry(Sass_Importer_List list, size_t idx) { return list[idx]; } + void ADDCALL sass_importer_set_list_entry(Sass_Importer_List list, size_t idx, Sass_Importer_Entry cb) { list[idx] = cb; } + + // Creator for sass custom importer return argument list + Sass_Import_List ADDCALL sass_make_import_list(size_t length) + { + return (Sass_Import**) calloc(length + 1, sizeof(Sass_Import*)); + } + + // Creator for a single import entry returned by the custom importer inside the list + // We take ownership of the memory for source and srcmap (freed when context is destroyd) + Sass_Import_Entry ADDCALL sass_make_import(const char* imp_path, const char* abs_path, char* source, char* srcmap) + { + Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import)); + if (v == 0) return 0; + v->imp_path = imp_path ? sass_copy_c_string(imp_path) : 0; + v->abs_path = abs_path ? sass_copy_c_string(abs_path) : 0; + v->source = source; + v->srcmap = srcmap; + v->error = 0; + v->line = -1; + v->column = -1; + return v; + } + + // Older style, but somehow still valid - keep around or deprecate? + Sass_Import_Entry ADDCALL sass_make_import_entry(const char* path, char* source, char* srcmap) + { + return sass_make_import(path, path, source, srcmap); + } + + // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting) + Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* error, size_t line, size_t col) + { + if (import == 0) return 0; + if (import->error) free(import->error); + import->error = error ? sass_copy_c_string(error) : 0; + import->line = line ? line : -1; + import->column = col ? col : -1; + return import; + } + + // Setters and getters for entries on the import list + void ADDCALL sass_import_set_list_entry(Sass_Import_List list, size_t idx, Sass_Import_Entry entry) { list[idx] = entry; } + Sass_Import_Entry ADDCALL sass_import_get_list_entry(Sass_Import_List list, size_t idx) { return list[idx]; } + + // Deallocator for the allocated memory + void ADDCALL sass_delete_import_list(Sass_Import_List list) + { + Sass_Import_List it = list; + if (list == 0) return; + while(*list) { + sass_delete_import(*list); + ++list; + } + free(it); + } + + // Just in case we have some stray import structs + void ADDCALL sass_delete_import(Sass_Import_Entry import) + { + free(import->imp_path); + free(import->abs_path); + free(import->source); + free(import->srcmap); + free(import->error); + free(import); + } + + // Getter for callee entry + const char* ADDCALL sass_callee_get_name(Sass_Callee_Entry entry) { return entry->name; } + const char* ADDCALL sass_callee_get_path(Sass_Callee_Entry entry) { return entry->path; } + size_t ADDCALL sass_callee_get_line(Sass_Callee_Entry entry) { return entry->line; } + size_t ADDCALL sass_callee_get_column(Sass_Callee_Entry entry) { return entry->column; } + enum Sass_Callee_Type ADDCALL sass_callee_get_type(Sass_Callee_Entry entry) { return entry->type; } + Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry entry) { return &entry->env; } + + // Getters and Setters for environments (lexical, local and global) + union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame env, const char* name) { + Expression_Ptr ex = Cast((*env->frame)[name]); + return ex != NULL ? ast_node_to_sass_value(ex) : NULL; + } + void ADDCALL sass_env_set_lexical (Sass_Env_Frame env, const char* name, union Sass_Value* val) { + (*env->frame)[name] = sass_value_to_ast_node(val); + } + union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame env, const char* name) { + Expression_Ptr ex = Cast(env->frame->get_local(name)); + return ex != NULL ? ast_node_to_sass_value(ex) : NULL; + } + void ADDCALL sass_env_set_local (Sass_Env_Frame env, const char* name, union Sass_Value* val) { + env->frame->set_local(name, sass_value_to_ast_node(val)); + } + union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame env, const char* name) { + Expression_Ptr ex = Cast(env->frame->get_global(name)); + return ex != NULL ? ast_node_to_sass_value(ex) : NULL; + } + void ADDCALL sass_env_set_global (Sass_Env_Frame env, const char* name, union Sass_Value* val) { + env->frame->set_global(name, sass_value_to_ast_node(val)); + } + + // Getter for import entry + const char* ADDCALL sass_import_get_imp_path(Sass_Import_Entry entry) { return entry->imp_path; } + const char* ADDCALL sass_import_get_abs_path(Sass_Import_Entry entry) { return entry->abs_path; } + const char* ADDCALL sass_import_get_source(Sass_Import_Entry entry) { return entry->source; } + const char* ADDCALL sass_import_get_srcmap(Sass_Import_Entry entry) { return entry->srcmap; } + + // Getter for import error entry + size_t ADDCALL sass_import_get_error_line(Sass_Import_Entry entry) { return entry->line; } + size_t ADDCALL sass_import_get_error_column(Sass_Import_Entry entry) { return entry->column; } + const char* ADDCALL sass_import_get_error_message(Sass_Import_Entry entry) { return entry->error; } + + // Explicit functions to take ownership of the memory + // Resets our own property since we do not know if it is still alive + char* ADDCALL sass_import_take_source(Sass_Import_Entry entry) { char* ptr = entry->source; entry->source = 0; return ptr; } + char* ADDCALL sass_import_take_srcmap(Sass_Import_Entry entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; } + +} diff --git a/src/sass_functions.hpp b/src/sass_functions.hpp new file mode 100644 index 000000000..3b646d67e --- /dev/null +++ b/src/sass_functions.hpp @@ -0,0 +1,50 @@ +#ifndef SASS_SASS_FUNCTIONS_H +#define SASS_SASS_FUNCTIONS_H + +#include "sass.h" +#include "environment.hpp" +#include "functions.hpp" + +// Struct to hold custom function callback +struct Sass_Function { + char* signature; + Sass_Function_Fn function; + void* cookie; +}; + +// External import entry +struct Sass_Import { + char* imp_path; // path as found in the import statement + char *abs_path; // path after importer has resolved it + char* source; + char* srcmap; + // error handling + char* error; + size_t line; + size_t column; +}; + +// External environments +struct Sass_Env { + // links to parent frames + Sass::Env* frame; +}; + +// External call entry +struct Sass_Callee { + const char* name; + const char* path; + size_t line; + size_t column; + enum Sass_Callee_Type type; + struct Sass_Env env; +}; + +// Struct to hold importer callback +struct Sass_Importer { + Sass_Importer_Fn importer; + double priority; + void* cookie; +}; + +#endif \ No newline at end of file diff --git a/src/sass_util.cpp b/src/sass_util.cpp new file mode 100644 index 000000000..3aef2bc72 --- /dev/null +++ b/src/sass_util.cpp @@ -0,0 +1,149 @@ +#include "sass.hpp" +#include "node.hpp" + +namespace Sass { + + + /* + # This is the equivalent of ruby's Sass::Util.paths. + # + # Return an array of all possible paths through the given arrays. + # + # @param arrs [NodeCollection>] + # @return [NodeCollection>] + # + # @example + # paths([[1, 2], [3, 4], [5]]) #=> + # # [[1, 3, 5], + # # [2, 3, 5], + # # [1, 4, 5], + # # [2, 4, 5]] + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def paths(arrs) + // I changed the inject and maps to an iterative approach to make it easier to implement in C++ + loopStart = [[]] + + for arr in arrs do + permutations = [] + for e in arr do + for path in loopStart do + permutations.push(path + [e]) + end + end + loopStart = permutations + end + end + */ + Node paths(const Node& arrs) { + + Node loopStart = Node::createCollection(); + loopStart.collection()->push_back(Node::createCollection()); + + for (NodeDeque::iterator arrsIter = arrs.collection()->begin(), arrsEndIter = arrs.collection()->end(); + arrsIter != arrsEndIter; ++arrsIter) { + + Node& arr = *arrsIter; + + Node permutations = Node::createCollection(); + + for (NodeDeque::iterator arrIter = arr.collection()->begin(), arrIterEnd = arr.collection()->end(); + arrIter != arrIterEnd; ++arrIter) { + + Node& e = *arrIter; + + for (NodeDeque::iterator loopStartIter = loopStart.collection()->begin(), loopStartIterEnd = loopStart.collection()->end(); + loopStartIter != loopStartIterEnd; ++loopStartIter) { + + Node& path = *loopStartIter; + + Node newPermutation = Node::createCollection(); + newPermutation.got_line_feed = arr.got_line_feed; + newPermutation.plus(path); + newPermutation.collection()->push_back(e); + + permutations.collection()->push_back(newPermutation); + } + } + + loopStart = permutations; + } + + return loopStart; + } + + + /* + This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. + Sass::Util.flatten requires the number of levels to flatten, while + [].flatten doesn't and will flatten the entire array. This function + supports both. + + # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened + # + # @param arr [NodeCollection] The array to flatten + # @param n [int] The number of levels to flatten + # @return [NodeCollection] The flattened array + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def flatten(arr, n = -1) + if n != -1 and n == 0 then + return arr + end + + flattened = [] + + for e in arr do + if e.is_a?(Array) then + flattened.concat(flatten(e, n - 1)) + else + flattened << e + end + end + + return flattened + end + */ + Node flatten(Node& arr, int n) { + if (n != -1 && n == 0) { + return arr; + } + + Node flattened = Node::createCollection(); + if (arr.got_line_feed) flattened.got_line_feed = true; + + for (NodeDeque::iterator iter = arr.collection()->begin(), iterEnd = arr.collection()->end(); + iter != iterEnd; iter++) { + Node& e = *iter; + + // e has the lf set + if (e.isCollection()) { + + // e.collection().got_line_feed = e.got_line_feed; + Node recurseFlattened = flatten(e, n - 1); + + if(e.got_line_feed) { + flattened.got_line_feed = e.got_line_feed; + recurseFlattened.got_line_feed = e.got_line_feed; + } + + for(auto i : (*recurseFlattened.collection())) { + if (recurseFlattened.got_line_feed) { + + i.got_line_feed = true; + } + flattened.collection()->push_back(i); + } + + } else { + flattened.collection()->push_back(e); + } + } + + return flattened; + } +} diff --git a/src/sass_util.hpp b/src/sass_util.hpp new file mode 100644 index 000000000..816da5fd8 --- /dev/null +++ b/src/sass_util.hpp @@ -0,0 +1,256 @@ +#ifndef SASS_SASS_UTIL_H +#define SASS_SASS_UTIL_H + +#include "ast.hpp" +#include "node.hpp" +#include "debug.hpp" + +namespace Sass { + + + + + /* + This is for ports of functions in the Sass:Util module. + */ + + + /* + # Return a Node collection of all possible paths through the given Node collection of Node collections. + # + # @param arrs [NodeCollection>] + # @return [NodeCollection>] + # + # @example + # paths([[1, 2], [3, 4], [5]]) #=> + # # [[1, 3, 5], + # # [2, 3, 5], + # # [1, 4, 5], + # # [2, 4, 5]] + */ + Node paths(const Node& arrs); + + + /* + This class is a default implementation of a Node comparator that can be passed to the lcs function below. + It uses operator== for equality comparision. It then returns one if the Nodes are equal. + */ + class DefaultLcsComparator { + public: + bool operator()(const Node& one, const Node& two, Node& out) const { + // TODO: Is this the correct C++ interpretation? + // block ||= proc {|a, b| a == b && a} + if (one == two) { + out = one; + return true; + } + + return false; + } + }; + + + typedef std::vector > LCSTable; + + + /* + This is the equivalent of ruby's Sass::Util.lcs_backtrace. + + # Computes a single longest common subsequence for arrays x and y. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS + */ + template + Node lcs_backtrace(const LCSTable& c, const Node& x, const Node& y, int i, int j, const ComparatorType& comparator) { + DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) + + if (i == 0 || j == 0) { + DEBUG_PRINTLN(LCS, "RETURNING EMPTY") + return Node::createCollection(); + } + + NodeDeque& xChildren = *(x.collection()); + NodeDeque& yChildren = *(y.collection()); + + Node compareOut = Node::createNil(); + if (comparator(xChildren[i], yChildren[j], compareOut)) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") + Node result = lcs_backtrace(c, x, y, i - 1, j - 1, comparator); + result.collection()->push_back(compareOut); + return result; + } + + if (c[i][j - 1] > c[i - 1][j]) { + DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") + return lcs_backtrace(c, x, y, i, j - 1, comparator); + } + + DEBUG_PRINTLN(LCS, "FINAL RETURN") + return lcs_backtrace(c, x, y, i - 1, j, comparator); + } + + + /* + This is the equivalent of ruby's Sass::Util.lcs_table. + + # Calculates the memoization table for the Least Common Subsequence algorithm. + # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS + */ + template + void lcs_table(const Node& x, const Node& y, const ComparatorType& comparator, LCSTable& out) { + DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) + + NodeDeque& xChildren = *(x.collection()); + NodeDeque& yChildren = *(y.collection()); + + LCSTable c(xChildren.size(), std::vector(yChildren.size())); + + // These shouldn't be necessary since the vector will be initialized to 0 already. + // x.size.times {|i| c[i][0] = 0} + // y.size.times {|j| c[0][j] = 0} + + for (size_t i = 1; i < xChildren.size(); i++) { + for (size_t j = 1; j < yChildren.size(); j++) { + Node compareOut = Node::createNil(); + + if (comparator(xChildren[i], yChildren[j], compareOut)) { + c[i][j] = c[i - 1][j - 1] + 1; + } else { + c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); + } + } + } + + out = c; + } + + + /* + This is the equivalent of ruby's Sass::Util.lcs. + + # Computes a single longest common subsequence for `x` and `y`. + # If there are more than one longest common subsequences, + # the one returned is that which starts first in `x`. + + # @param x [NodeCollection] + # @param y [NodeCollection] + # @comparator An equality check between elements of `x` and `y`. + # @return [NodeCollection] The LCS + + http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + */ + template + Node lcs(Node& x, Node& y, const ComparatorType& comparator) { + DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) + + Node newX = Node::createCollection(); + newX.collection()->push_back(Node::createNil()); + newX.plus(x); + + Node newY = Node::createCollection(); + newY.collection()->push_back(Node::createNil()); + newY.plus(y); + + LCSTable table; + lcs_table(newX, newY, comparator, table); + + return lcs_backtrace(table, newX, newY, static_cast(newX.collection()->size()) - 1, static_cast(newY.collection()->size()) - 1, comparator); + } + + + /* + This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. + Sass::Util.flatten requires the number of levels to flatten, while + [].flatten doesn't and will flatten the entire array. This function + supports both. + + # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened + # + # @param arr [NodeCollection] The array to flatten + # @param n [int] The number of levels to flatten + # @return [NodeCollection] The flattened array + */ + Node flatten(Node& arr, int n = -1); + + + /* + This is the equivalent of ruby's Sass::Util.group_by_to_a. + + # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed + # order. Unlike [#hash_to_a], the resulting order isn't sorted key order; + # instead, it's the same order as `#group_by` has under Ruby 1.9 (key + # appearance order). + # + # @param enum [Enumerable] + # @return [Array<[Object, Array]>] An array of pairs. + + TODO: update @param and @return once I know what those are. + + The following is the modified version of the ruby code that was more portable to C++. You + should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. + + def group_by_to_a(enum, &block) + order = {} + + arr = [] + + grouped = {} + + for e in enum do + key = block[e] + unless order.include?(key) + order[key] = order.size + end + + if not grouped.has_key?(key) then + grouped[key] = [e] + else + grouped[key].push(e) + end + end + + grouped.each do |key, vals| + arr[order[key]] = [key, vals] + end + + arr + end + + */ + template + void group_by_to_a(std::vector& enumeration, KeyFunctorType& keyFunc, std::vector > >& arr /*out*/) { + + std::map order; + + std::map > grouped; + + for (typename std::vector::iterator enumIter = enumeration.begin(), enumIterEnd = enumeration.end(); enumIter != enumIterEnd; enumIter++) { + EnumType& e = *enumIter; + + KeyType key = keyFunc(e); + + if (grouped.find(key->hash()) == grouped.end()) { + order.insert(std::make_pair((unsigned int)order.size(), key)); + + std::vector newCollection; + newCollection.push_back(e); + grouped.insert(std::make_pair(key->hash(), newCollection)); + } else { + std::vector& collection = grouped.at(key->hash()); + collection.push_back(e); + } + } + + for (unsigned int index = 0; index < order.size(); index++) { + KeyType& key = order.at(index); + std::vector& values = grouped.at(key->hash()); + + std::pair > grouping = std::make_pair(key, values); + + arr.push_back(grouping); + } + } + + +} + +#endif diff --git a/src/sass_values.cpp b/src/sass_values.cpp new file mode 100644 index 000000000..34c591a24 --- /dev/null +++ b/src/sass_values.cpp @@ -0,0 +1,357 @@ +#include "sass.hpp" +#include +#include +#include "util.hpp" +#include "eval.hpp" +#include "values.hpp" +#include "operators.hpp" +#include "sass/values.h" +#include "sass_values.hpp" + +extern "C" { + using namespace Sass; + + // Return the sass tag for a generic sass value + enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; } + + // Check value for specified type + bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; } + bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; } + bool ADDCALL sass_value_is_string(const union Sass_Value* v) { return v->unknown.tag == SASS_STRING; } + bool ADDCALL sass_value_is_boolean(const union Sass_Value* v) { return v->unknown.tag == SASS_BOOLEAN; } + bool ADDCALL sass_value_is_color(const union Sass_Value* v) { return v->unknown.tag == SASS_COLOR; } + bool ADDCALL sass_value_is_list(const union Sass_Value* v) { return v->unknown.tag == SASS_LIST; } + bool ADDCALL sass_value_is_map(const union Sass_Value* v) { return v->unknown.tag == SASS_MAP; } + bool ADDCALL sass_value_is_error(const union Sass_Value* v) { return v->unknown.tag == SASS_ERROR; } + bool ADDCALL sass_value_is_warning(const union Sass_Value* v) { return v->unknown.tag == SASS_WARNING; } + + // Getters and setters for Sass_Number + double ADDCALL sass_number_get_value(const union Sass_Value* v) { return v->number.value; } + void ADDCALL sass_number_set_value(union Sass_Value* v, double value) { v->number.value = value; } + const char* ADDCALL sass_number_get_unit(const union Sass_Value* v) { return v->number.unit; } + void ADDCALL sass_number_set_unit(union Sass_Value* v, char* unit) { v->number.unit = unit; } + + // Getters and setters for Sass_String + const char* ADDCALL sass_string_get_value(const union Sass_Value* v) { return v->string.value; } + void ADDCALL sass_string_set_value(union Sass_Value* v, char* value) { v->string.value = value; } + bool ADDCALL sass_string_is_quoted(const union Sass_Value* v) { return v->string.quoted; } + void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted) { v->string.quoted = quoted; } + + // Getters and setters for Sass_Boolean + bool ADDCALL sass_boolean_get_value(const union Sass_Value* v) { return v->boolean.value; } + void ADDCALL sass_boolean_set_value(union Sass_Value* v, bool value) { v->boolean.value = value; } + + // Getters and setters for Sass_Color + double ADDCALL sass_color_get_r(const union Sass_Value* v) { return v->color.r; } + void ADDCALL sass_color_set_r(union Sass_Value* v, double r) { v->color.r = r; } + double ADDCALL sass_color_get_g(const union Sass_Value* v) { return v->color.g; } + void ADDCALL sass_color_set_g(union Sass_Value* v, double g) { v->color.g = g; } + double ADDCALL sass_color_get_b(const union Sass_Value* v) { return v->color.b; } + void ADDCALL sass_color_set_b(union Sass_Value* v, double b) { v->color.b = b; } + double ADDCALL sass_color_get_a(const union Sass_Value* v) { return v->color.a; } + void ADDCALL sass_color_set_a(union Sass_Value* v, double a) { v->color.a = a; } + + // Getters and setters for Sass_List + size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; } + enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; } + void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; } + bool ADDCALL sass_list_get_is_bracketed(const union Sass_Value* v) { return v->list.is_bracketed; } + void ADDCALL sass_list_set_is_bracketed(union Sass_Value* v, bool is_bracketed) { v->list.is_bracketed = is_bracketed; } + // Getters and setters for Sass_List values + union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; } + void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; } + + // Getters and setters for Sass_Map + size_t ADDCALL sass_map_get_length(const union Sass_Value* v) { return v->map.length; } + // Getters and setters for Sass_List keys and values + union Sass_Value* ADDCALL sass_map_get_key(const union Sass_Value* v, size_t i) { return v->map.pairs[i].key; } + union Sass_Value* ADDCALL sass_map_get_value(const union Sass_Value* v, size_t i) { return v->map.pairs[i].value; } + void ADDCALL sass_map_set_key(union Sass_Value* v, size_t i, union Sass_Value* key) { v->map.pairs[i].key = key; } + void ADDCALL sass_map_set_value(union Sass_Value* v, size_t i, union Sass_Value* val) { v->map.pairs[i].value = val; } + + // Getters and setters for Sass_Error + char* ADDCALL sass_error_get_message(const union Sass_Value* v) { return v->error.message; }; + void ADDCALL sass_error_set_message(union Sass_Value* v, char* msg) { v->error.message = msg; }; + + // Getters and setters for Sass_Warning + char* ADDCALL sass_warning_get_message(const union Sass_Value* v) { return v->warning.message; }; + void ADDCALL sass_warning_set_message(union Sass_Value* v, char* msg) { v->warning.message = msg; }; + + // Creator functions for all value types + + union Sass_Value* ADDCALL sass_make_boolean(bool val) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->boolean.tag = SASS_BOOLEAN; + v->boolean.value = val; + return v; + } + + union Sass_Value* ADDCALL sass_make_number(double val, const char* unit) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->number.tag = SASS_NUMBER; + v->number.value = val; + v->number.unit = unit ? sass_copy_c_string(unit) : 0; + if (v->number.unit == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_color(double r, double g, double b, double a) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->color.tag = SASS_COLOR; + v->color.r = r; + v->color.g = g; + v->color.b = b; + v->color.a = a; + return v; + } + + union Sass_Value* ADDCALL sass_make_string(const char* val) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->string.quoted = false; + v->string.tag = SASS_STRING; + v->string.value = val ? sass_copy_c_string(val) : 0; + if (v->string.value == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_qstring(const char* val) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->string.quoted = true; + v->string.tag = SASS_STRING; + v->string.value = val ? sass_copy_c_string(val) : 0; + if (v->string.value == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep, bool is_bracketed) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->list.tag = SASS_LIST; + v->list.length = len; + v->list.separator = sep; + v->list.is_bracketed = is_bracketed; + v->list.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value*)); + if (v->list.values == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_map(size_t len) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->map.tag = SASS_MAP; + v->map.length = len; + v->map.pairs = (struct Sass_MapPair*) calloc(len, sizeof(struct Sass_MapPair)); + if (v->map.pairs == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_null(void) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->null.tag = SASS_NULL; + return v; + } + + union Sass_Value* ADDCALL sass_make_error(const char* msg) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->error.tag = SASS_ERROR; + v->error.message = msg ? sass_copy_c_string(msg) : 0; + if (v->error.message == 0) { free(v); return 0; } + return v; + } + + union Sass_Value* ADDCALL sass_make_warning(const char* msg) + { + union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); + if (v == 0) return 0; + v->warning.tag = SASS_WARNING; + v->warning.message = msg ? sass_copy_c_string(msg) : 0; + if (v->warning.message == 0) { free(v); return 0; } + return v; + } + + // will free all associated sass values + void ADDCALL sass_delete_value(union Sass_Value* val) { + + size_t i; + if (val == 0) return; + switch(val->unknown.tag) { + case SASS_NULL: { + } break; + case SASS_BOOLEAN: { + } break; + case SASS_NUMBER: { + free(val->number.unit); + } break; + case SASS_COLOR: { + } break; + case SASS_STRING: { + free(val->string.value); + } break; + case SASS_LIST: { + for (i=0; ilist.length; i++) { + sass_delete_value(val->list.values[i]); + } + free(val->list.values); + } break; + case SASS_MAP: { + for (i=0; imap.length; i++) { + sass_delete_value(val->map.pairs[i].key); + sass_delete_value(val->map.pairs[i].value); + } + free(val->map.pairs); + } break; + case SASS_ERROR: { + free(val->error.message); + } break; + case SASS_WARNING: { + free(val->error.message); + } break; + default: break; + } + + free(val); + + } + + // Make a deep cloned copy of the given sass value + union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val) + { + + size_t i; + if (val == 0) return 0; + switch(val->unknown.tag) { + case SASS_NULL: { + return sass_make_null(); + } + case SASS_BOOLEAN: { + return sass_make_boolean(val->boolean.value); + } + case SASS_NUMBER: { + return sass_make_number(val->number.value, val->number.unit); + } + case SASS_COLOR: { + return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a); + } + case SASS_STRING: { + return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); + } + case SASS_LIST: { + union Sass_Value* list = sass_make_list(val->list.length, val->list.separator, val->list.is_bracketed); + for (i = 0; i < list->list.length; i++) { + list->list.values[i] = sass_clone_value(val->list.values[i]); + } + return list; + } + case SASS_MAP: { + union Sass_Value* map = sass_make_map(val->map.length); + for (i = 0; i < val->map.length; i++) { + map->map.pairs[i].key = sass_clone_value(val->map.pairs[i].key); + map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value); + } + return map; + } + case SASS_ERROR: { + return sass_make_error(val->error.message); + } + case SASS_WARNING: { + return sass_make_warning(val->warning.message); + } + default: break; + } + + return 0; + + } + + union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* v, bool compressed, int precision) + { + Value_Obj val = sass_value_to_ast_node(v); + Sass_Inspect_Options options(compressed ? COMPRESSED : NESTED, precision); + std::string str(val->to_string(options)); + return sass_make_qstring(str.c_str()); + } + + union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) + { + + Sass::Value_Ptr rv; + + try { + + Value_Obj lhs = sass_value_to_ast_node(a); + Value_Obj rhs = sass_value_to_ast_node(b); + struct Sass_Inspect_Options options(NESTED, 5); + + // see if it's a relational expression + switch(op) { + case Sass_OP::EQ: return sass_make_boolean(Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return sass_make_boolean(Operators::neq(lhs, rhs)); + case Sass_OP::GT: return sass_make_boolean(Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return sass_make_boolean(Operators::gte(lhs, rhs)); + case Sass_OP::LT: return sass_make_boolean(Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return sass_make_boolean(Operators::lte(lhs, rhs)); + case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); + case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); + default: break; + } + + if (sass_value_is_number(a) && sass_value_is_number(b)) { + Number_Ptr_Const l_n = Cast(lhs); + Number_Ptr_Const r_n = Cast(rhs); + rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); + } + else if (sass_value_is_number(a) && sass_value_is_color(a)) { + Number_Ptr_Const l_n = Cast(lhs); + Color_Ptr_Const r_c = Cast(rhs); + rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); + } + else if (sass_value_is_color(a) && sass_value_is_number(b)) { + Color_Ptr_Const l_c = Cast(lhs); + Number_Ptr_Const r_n = Cast(rhs); + rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); + } + else if (sass_value_is_color(a) && sass_value_is_color(b)) { + Color_Ptr_Const l_c = Cast(lhs); + Color_Ptr_Const r_c = Cast(rhs); + rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate()); + } + else /* convert other stuff to string and apply operation */ { + Value_Ptr l_v = Cast(lhs); + Value_Ptr r_v = Cast(rhs); + rv = Operators::op_strings(op, *l_v, *r_v, options, l_v->pstate()); + } + + // ToDo: maybe we should should return null value? + if (!rv) return sass_make_error("invalid return value"); + + // convert result back to ast node + return ast_node_to_sass_value(rv); + + } + + // simply pass the error message back to the caller for now + catch (Exception::InvalidSass& e) { return sass_make_error(e.what()); } + catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } + catch (std::exception& e) { return sass_make_error(e.what()); } + catch (std::string& e) { return sass_make_error(e.c_str()); } + catch (const char* e) { return sass_make_error(e); } + catch (...) { return sass_make_error("unknown"); } + } + +} diff --git a/src/sass_values.hpp b/src/sass_values.hpp new file mode 100644 index 000000000..9aa5cdb33 --- /dev/null +++ b/src/sass_values.hpp @@ -0,0 +1,82 @@ +#ifndef SASS_SASS_VALUES_H +#define SASS_SASS_VALUES_H + +#include "sass.h" + +struct Sass_Unknown { + enum Sass_Tag tag; +}; + +struct Sass_Boolean { + enum Sass_Tag tag; + bool value; +}; + +struct Sass_Number { + enum Sass_Tag tag; + double value; + char* unit; +}; + +struct Sass_Color { + enum Sass_Tag tag; + double r; + double g; + double b; + double a; +}; + +struct Sass_String { + enum Sass_Tag tag; + bool quoted; + char* value; +}; + +struct Sass_List { + enum Sass_Tag tag; + enum Sass_Separator separator; + bool is_bracketed; + size_t length; + // null terminated "array" + union Sass_Value** values; +}; + +struct Sass_Map { + enum Sass_Tag tag; + size_t length; + struct Sass_MapPair* pairs; +}; + +struct Sass_Null { + enum Sass_Tag tag; +}; + +struct Sass_Error { + enum Sass_Tag tag; + char* message; +}; + +struct Sass_Warning { + enum Sass_Tag tag; + char* message; +}; + +union Sass_Value { + struct Sass_Unknown unknown; + struct Sass_Boolean boolean; + struct Sass_Number number; + struct Sass_Color color; + struct Sass_String string; + struct Sass_List list; + struct Sass_Map map; + struct Sass_Null null; + struct Sass_Error error; + struct Sass_Warning warning; +}; + +struct Sass_MapPair { + union Sass_Value* key; + union Sass_Value* value; +}; + +#endif diff --git a/src/source_map.cpp b/src/source_map.cpp new file mode 100644 index 000000000..c171a3f68 --- /dev/null +++ b/src/source_map.cpp @@ -0,0 +1,195 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "ast.hpp" +#include "json.hpp" +#include "context.hpp" +#include "position.hpp" +#include "source_map.hpp" + +namespace Sass { + SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin") { } + SourceMap::SourceMap(const std::string& file) : current_position(0, 0, 0), file(file) { } + + std::string SourceMap::render_srcmap(Context &ctx) { + + const bool include_sources = ctx.c_options.source_map_contents; + const std::vector links = ctx.srcmap_links; + const std::vector& sources(ctx.resources); + + JsonNode* json_srcmap = json_mkobject(); + + json_append_member(json_srcmap, "version", json_mknumber(3)); + + const char *file_name = file.c_str(); + JsonNode *json_file_name = json_mkstring(file_name); + json_append_member(json_srcmap, "file", json_file_name); + + // pass-through sourceRoot option + if (!ctx.source_map_root.empty()) { + JsonNode* root = json_mkstring(ctx.source_map_root.c_str()); + json_append_member(json_srcmap, "sourceRoot", root); + } + + JsonNode *json_sources = json_mkarray(); + for (size_t i = 0; i < source_index.size(); ++i) { + std::string source(links[source_index[i]]); + if (ctx.c_options.source_map_file_urls) { + source = File::rel2abs(source); + // check for windows abs path + if (source[0] == '/') { + // ends up with three slashes + source = "file://" + source; + } else { + // needs an additional slash + source = "file:///" + source; + } + } + const char* source_name = source.c_str(); + JsonNode *json_source_name = json_mkstring(source_name); + json_append_element(json_sources, json_source_name); + } + json_append_member(json_srcmap, "sources", json_sources); + + if (include_sources && source_index.size()) { + JsonNode *json_contents = json_mkarray(); + for (size_t i = 0; i < source_index.size(); ++i) { + const Resource& resource(sources[source_index[i]]); + JsonNode *json_content = json_mkstring(resource.contents); + json_append_element(json_contents, json_content); + } + json_append_member(json_srcmap, "sourcesContent", json_contents); + } + + JsonNode *json_names = json_mkarray(); + // so far we have no implementation for names + // no problem as we do not alter any identifiers + json_append_member(json_srcmap, "names", json_names); + + std::string mappings = serialize_mappings(); + JsonNode *json_mappings = json_mkstring(mappings.c_str()); + json_append_member(json_srcmap, "mappings", json_mappings); + + char *str = json_stringify(json_srcmap, "\t"); + std::string result = std::string(str); + free(str); + json_delete(json_srcmap); + return result; + } + + std::string SourceMap::serialize_mappings() { + std::string result = ""; + + size_t previous_generated_line = 0; + size_t previous_generated_column = 0; + size_t previous_original_line = 0; + size_t previous_original_column = 0; + size_t previous_original_file = 0; + for (size_t i = 0; i < mappings.size(); ++i) { + const size_t generated_line = mappings[i].generated_position.line; + const size_t generated_column = mappings[i].generated_position.column; + const size_t original_line = mappings[i].original_position.line; + const size_t original_column = mappings[i].original_position.column; + const size_t original_file = mappings[i].original_position.file; + + if (generated_line != previous_generated_line) { + previous_generated_column = 0; + if (generated_line > previous_generated_line) { + result += std::string(generated_line - previous_generated_line, ';'); + previous_generated_line = generated_line; + } + } + else if (i > 0) { + result += ","; + } + + // generated column + result += base64vlq.encode(static_cast(generated_column) - static_cast(previous_generated_column)); + previous_generated_column = generated_column; + // file + result += base64vlq.encode(static_cast(original_file) - static_cast(previous_original_file)); + previous_original_file = original_file; + // source line + result += base64vlq.encode(static_cast(original_line) - static_cast(previous_original_line)); + previous_original_line = original_line; + // source column + result += base64vlq.encode(static_cast(original_column) - static_cast(previous_original_column)); + previous_original_column = original_column; + } + + return result; + } + + void SourceMap::prepend(const OutputBuffer& out) + { + Offset size(out.smap.current_position); + for (Mapping mapping : out.smap.mappings) { + if (mapping.generated_position.line > size.line) { + throw(std::runtime_error("prepend sourcemap has illegal line")); + } + if (mapping.generated_position.line == size.line) { + if (mapping.generated_position.column > size.column) { + throw(std::runtime_error("prepend sourcemap has illegal column")); + } + } + } + // adjust the buffer offset + prepend(Offset(out.buffer)); + // now add the new mappings + VECTOR_UNSHIFT(mappings, out.smap.mappings); + } + + void SourceMap::append(const OutputBuffer& out) + { + append(Offset(out.buffer)); + } + + void SourceMap::prepend(const Offset& offset) + { + if (offset.line != 0 || offset.column != 0) { + for (Mapping& mapping : mappings) { + // move stuff on the first old line + if (mapping.generated_position.line == 0) { + mapping.generated_position.column += offset.column; + } + // make place for the new lines + mapping.generated_position.line += offset.line; + } + } + if (current_position.line == 0) { + current_position.column += offset.column; + } + current_position.line += offset.line; + } + + void SourceMap::append(const Offset& offset) + { + current_position += offset; + } + + void SourceMap::add_open_mapping(const AST_Node_Ptr node) + { + mappings.push_back(Mapping(node->pstate(), current_position)); + } + + void SourceMap::add_close_mapping(const AST_Node_Ptr node) + { + mappings.push_back(Mapping(node->pstate() + node->pstate().offset, current_position)); + } + + ParserState SourceMap::remap(const ParserState& pstate) { + for (size_t i = 0; i < mappings.size(); ++i) { + if ( + mappings[i].generated_position.file == pstate.file && + mappings[i].generated_position.line == pstate.line && + mappings[i].generated_position.column == pstate.column + ) return ParserState(pstate.path, pstate.src, mappings[i].original_position, pstate.offset); + } + return ParserState(pstate.path, pstate.src, Position(-1, -1, -1), Offset(0, 0)); + + } + +} diff --git a/src/source_map.hpp b/src/source_map.hpp new file mode 100644 index 000000000..07785640f --- /dev/null +++ b/src/source_map.hpp @@ -0,0 +1,62 @@ +#ifndef SASS_SOURCE_MAP_H +#define SASS_SOURCE_MAP_H + +#include +#include + +#include "ast_fwd_decl.hpp" +#include "base64vlq.hpp" +#include "position.hpp" +#include "mapping.hpp" + +#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end()) +#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end()) + +namespace Sass { + + class Context; + class OutputBuffer; + + class SourceMap { + + public: + std::vector source_index; + SourceMap(); + SourceMap(const std::string& file); + + void append(const Offset& offset); + void prepend(const Offset& offset); + void append(const OutputBuffer& out); + void prepend(const OutputBuffer& out); + void add_open_mapping(const AST_Node_Ptr node); + void add_close_mapping(const AST_Node_Ptr node); + + std::string render_srcmap(Context &ctx); + ParserState remap(const ParserState& pstate); + + private: + + std::string serialize_mappings(); + + std::vector mappings; + Position current_position; +public: + std::string file; +private: + Base64VLQ base64vlq; + }; + + class OutputBuffer { + public: + OutputBuffer(void) + : buffer(""), + smap() + { } + public: + std::string buffer; + SourceMap smap; + }; + +} + +#endif diff --git a/src/subset_map.cpp b/src/subset_map.cpp new file mode 100644 index 000000000..24513e498 --- /dev/null +++ b/src/subset_map.cpp @@ -0,0 +1,55 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "subset_map.hpp" + +namespace Sass { + + void Subset_Map::put(const Compound_Selector_Obj& sel, const SubSetMapPair& value) + { + if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty"); + size_t index = values_.size(); + values_.push_back(value); + for (size_t i = 0, S = sel->length(); i < S; ++i) + { + hash_[(*sel)[i]].push_back(std::make_pair(sel, index)); + } + } + + std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) + { + SimpleSelectorDict dict(sel->begin(), sel->end()); // XXX Set + std::vector indices; + for (size_t i = 0, S = sel->length(); i < S; ++i) { + if (!hash_.count((*sel)[i])) { + continue; + } + const std::vector >& subsets = hash_[(*sel)[i]]; + for (const std::pair& item : subsets) { + bool include = true; + for (const Simple_Selector_Obj& it : item.first->elements()) { + auto found = dict.find(it); + if (found == dict.end()) { + include = false; + break; + } + } + if (include) indices.push_back(item.second); + } + } + sort(indices.begin(), indices.end()); + std::vector::iterator indices_end = unique(indices.begin(), indices.end()); + indices.resize(distance(indices.begin(), indices_end)); + + std::vector results; + for (size_t i = 0, S = indices.size(); i < S; ++i) { + results.push_back(values_[indices[i]]); + } + return results; + } + + std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) + { + return get_kv(sel); + } + +} \ No newline at end of file diff --git a/src/subset_map.hpp b/src/subset_map.hpp new file mode 100644 index 000000000..5c091e685 --- /dev/null +++ b/src/subset_map.hpp @@ -0,0 +1,76 @@ +#ifndef SASS_SUBSET_MAP_H +#define SASS_SUBSET_MAP_H + +#include +#include +#include +#include +#include + +#include "ast_fwd_decl.hpp" + + +// #include +// #include +// template +// std::string vector_to_string(std::vector v) +// { +// std::stringstream buffer; +// buffer << "["; + +// if (!v.empty()) +// { buffer << v[0]; } +// else +// { buffer << "]"; } + +// if (v.size() == 1) +// { buffer << "]"; } +// else +// { +// for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; +// buffer << "]"; +// } + +// return buffer.str(); +// } + +// template +// std::string set_to_string(set v) +// { +// std::stringstream buffer; +// buffer << "["; +// typename std::set::iterator i = v.begin(); +// if (!v.empty()) +// { buffer << *i; } +// else +// { buffer << "]"; } + +// if (v.size() == 1) +// { buffer << "]"; } +// else +// { +// for (++i; i != v.end(); ++i) buffer << ", " << *i; +// buffer << "]"; +// } + +// return buffer.str(); +// } + +namespace Sass { + + class Subset_Map { + private: + std::vector values_; + std::map >, OrderNodes > hash_; + public: + void put(const Compound_Selector_Obj& sel, const SubSetMapPair& value); + std::vector get_kv(const Compound_Selector_Obj& s); + std::vector get_v(const Compound_Selector_Obj& s); + bool empty() { return values_.empty(); } + void clear() { values_.clear(); hash_.clear(); } + const std::vector values(void) { return values_; } + }; + +} + +#endif diff --git a/src/support/libsass.pc.in b/src/support/libsass.pc.in new file mode 100644 index 000000000..d201bfaaf --- /dev/null +++ b/src/support/libsass.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libsass +URL: https://github.com/sass/libsass +Description: A C implementation of a Sass compiler +Version: @VERSION@ +Libs: -L${libdir} -lsass +Cflags: -I${includedir} diff --git a/src/to_c.cpp b/src/to_c.cpp new file mode 100644 index 000000000..8a6ea8d51 --- /dev/null +++ b/src/to_c.cpp @@ -0,0 +1,74 @@ +#include "sass.hpp" +#include "to_c.hpp" +#include "ast.hpp" + +namespace Sass { + + union Sass_Value* To_C::fallback_impl(AST_Node_Ptr n) + { return sass_make_error("unknown type for C-API"); } + + union Sass_Value* To_C::operator()(Boolean_Ptr b) + { return sass_make_boolean(b->value()); } + + union Sass_Value* To_C::operator()(Number_Ptr n) + { return sass_make_number(n->value(), n->unit().c_str()); } + + union Sass_Value* To_C::operator()(Custom_Warning_Ptr w) + { return sass_make_warning(w->message().c_str()); } + + union Sass_Value* To_C::operator()(Custom_Error_Ptr e) + { return sass_make_error(e->message().c_str()); } + + union Sass_Value* To_C::operator()(Color_Ptr c) + { return sass_make_color(c->r(), c->g(), c->b(), c->a()); } + + union Sass_Value* To_C::operator()(String_Constant_Ptr s) + { + if (s->quote_mark()) { + return sass_make_qstring(s->value().c_str()); + } else { + return sass_make_string(s->value().c_str()); + } + } + + union Sass_Value* To_C::operator()(String_Quoted_Ptr s) + { return sass_make_qstring(s->value().c_str()); } + + union Sass_Value* To_C::operator()(List_Ptr l) + { + union Sass_Value* v = sass_make_list(l->length(), l->separator(), l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + sass_list_set_value(v, i, (*l)[i]->perform(this)); + } + return v; + } + + union Sass_Value* To_C::operator()(Map_Ptr m) + { + union Sass_Value* v = sass_make_map(m->length()); + int i = 0; + for (auto key : m->keys()) { + sass_map_set_key(v, i, key->perform(this)); + sass_map_set_value(v, i, m->at(key)->perform(this)); + i++; + } + return v; + } + + union Sass_Value* To_C::operator()(Arguments_Ptr a) + { + union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA, false); + for (size_t i = 0, L = a->length(); i < L; ++i) { + sass_list_set_value(v, i, (*a)[i]->perform(this)); + } + return v; + } + + union Sass_Value* To_C::operator()(Argument_Ptr a) + { return a->value()->perform(this); } + + // not strictly necessary because of the fallback + union Sass_Value* To_C::operator()(Null_Ptr n) + { return sass_make_null(); } + +}; diff --git a/src/to_c.hpp b/src/to_c.hpp new file mode 100644 index 000000000..a5331e3bf --- /dev/null +++ b/src/to_c.hpp @@ -0,0 +1,39 @@ +#ifndef SASS_TO_C_H +#define SASS_TO_C_H + +#include "ast_fwd_decl.hpp" +#include "operation.hpp" +#include "sass/values.h" + +namespace Sass { + + class To_C : public Operation_CRTP { + // override this to define a catch-all + union Sass_Value* fallback_impl(AST_Node_Ptr n); + + public: + + To_C() { } + ~To_C() { } + + union Sass_Value* operator()(Boolean_Ptr); + union Sass_Value* operator()(Number_Ptr); + union Sass_Value* operator()(Color_Ptr); + union Sass_Value* operator()(String_Constant_Ptr); + union Sass_Value* operator()(String_Quoted_Ptr); + union Sass_Value* operator()(Custom_Warning_Ptr); + union Sass_Value* operator()(Custom_Error_Ptr); + union Sass_Value* operator()(List_Ptr); + union Sass_Value* operator()(Map_Ptr); + union Sass_Value* operator()(Null_Ptr); + union Sass_Value* operator()(Arguments_Ptr); + union Sass_Value* operator()(Argument_Ptr); + + // dispatch to fallback implementation + union Sass_Value* fallback(AST_Node_Ptr x) + { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/to_value.cpp b/src/to_value.cpp new file mode 100644 index 000000000..3912c5510 --- /dev/null +++ b/src/to_value.cpp @@ -0,0 +1,112 @@ +#include "sass.hpp" +#include "ast.hpp" +#include "to_value.hpp" + +namespace Sass { + + Value_Ptr To_Value::fallback_impl(AST_Node_Ptr n) + { + // throw a runtime error if this happens + // we want a well defined set of possible nodes + throw std::runtime_error("invalid node for to_value"); + } + + // Custom_Error is a valid value + Value_Ptr To_Value::operator()(Custom_Error_Ptr e) + { + return e; + } + + // Custom_Warning is a valid value + Value_Ptr To_Value::operator()(Custom_Warning_Ptr w) + { + return w; + } + + // Boolean is a valid value + Value_Ptr To_Value::operator()(Boolean_Ptr b) + { + return b; + } + + // Number is a valid value + Value_Ptr To_Value::operator()(Number_Ptr n) + { + return n; + } + + // Color is a valid value + Value_Ptr To_Value::operator()(Color_Ptr c) + { + return c; + } + + // String_Constant is a valid value + Value_Ptr To_Value::operator()(String_Constant_Ptr s) + { + return s; + } + + // String_Quoted is a valid value + Value_Ptr To_Value::operator()(String_Quoted_Ptr s) + { + return s; + } + + // List is a valid value + Value_Ptr To_Value::operator()(List_Ptr l) + { + List_Obj ll = SASS_MEMORY_NEW(List, + l->pstate(), + l->length(), + l->separator(), + l->is_arglist(), + l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + ll->append((*l)[i]->perform(this)); + } + return ll.detach(); + } + + // Map is a valid value + Value_Ptr To_Value::operator()(Map_Ptr m) + { + return m; + } + + // Null is a valid value + Value_Ptr To_Value::operator()(Null_Ptr n) + { + return n; + } + + // Function is a valid value + Value_Ptr To_Value::operator()(Function_Ptr n) + { + return n; + } + + // Argument returns its value + Value_Ptr To_Value::operator()(Argument_Ptr arg) + { + if (!arg->name().empty()) return 0; + return arg->value()->perform(this); + } + + // Selector_List is converted to a string + Value_Ptr To_Value::operator()(Selector_List_Ptr s) + { + return SASS_MEMORY_NEW(String_Quoted, + s->pstate(), + s->to_string(ctx.c_options)); + } + + // Binary_Expression is converted to a string + Value_Ptr To_Value::operator()(Binary_Expression_Ptr s) + { + return SASS_MEMORY_NEW(String_Quoted, + s->pstate(), + s->to_string(ctx.c_options)); + } + +}; diff --git a/src/to_value.hpp b/src/to_value.hpp new file mode 100644 index 000000000..8f64128c4 --- /dev/null +++ b/src/to_value.hpp @@ -0,0 +1,50 @@ +#ifndef SASS_TO_VALUE_H +#define SASS_TO_VALUE_H + +#include "operation.hpp" +#include "sass/values.h" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + class To_Value : public Operation_CRTP { + + Value_Ptr fallback_impl(AST_Node_Ptr n); + + private: + + Context& ctx; + + public: + + To_Value(Context& ctx) + : ctx(ctx) + { } + ~To_Value() { } + using Operation::operator(); + + Value_Ptr operator()(Argument_Ptr); + Value_Ptr operator()(Boolean_Ptr); + Value_Ptr operator()(Number_Ptr); + Value_Ptr operator()(Color_Ptr); + Value_Ptr operator()(String_Constant_Ptr); + Value_Ptr operator()(String_Quoted_Ptr); + Value_Ptr operator()(Custom_Warning_Ptr); + Value_Ptr operator()(Custom_Error_Ptr); + Value_Ptr operator()(List_Ptr); + Value_Ptr operator()(Map_Ptr); + Value_Ptr operator()(Null_Ptr); + Value_Ptr operator()(Function_Ptr); + + // convert to string via `To_String` + Value_Ptr operator()(Selector_List_Ptr); + Value_Ptr operator()(Binary_Expression_Ptr); + + // fallback throws error + template + Value_Ptr fallback(U x) { return fallback_impl(x); } + }; + +} + +#endif diff --git a/src/units.cpp b/src/units.cpp new file mode 100644 index 000000000..779f1d2b4 --- /dev/null +++ b/src/units.cpp @@ -0,0 +1,501 @@ +#include "sass.hpp" +#include +#include "units.hpp" +#include "error_handling.hpp" + +namespace Sass { + + /* the conversion matrix can be readed the following way */ + /* if you go down, the factor is for the numerator (multiply) */ + /* if you go right, the factor is for the denominator (divide) */ + /* and yes, we actually use both, not sure why, but why not!? */ + + const double size_conversion_factors[6][6] = + { + /* in cm pc mm pt px */ + /* in */ { 1, 2.54, 6, 25.4, 72, 96, }, + /* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54 }, + /* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0 }, + /* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4 }, + /* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0 }, + /* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, } + }; + + const double angle_conversion_factors[4][4] = + { + /* deg grad rad turn */ + /* deg */ { 1, 40.0/36.0, PI/180.0, 1.0/360.0 }, + /* grad */ { 36.0/40.0, 1, PI/200.0, 1.0/400.0 }, + /* rad */ { 180.0/PI, 200.0/PI, 1, 0.5/PI }, + /* turn */ { 360.0, 400.0, 2.0*PI, 1 } + }; + + const double time_conversion_factors[2][2] = + { + /* s ms */ + /* s */ { 1, 1000.0 }, + /* ms */ { 1/1000.0, 1 } + }; + const double frequency_conversion_factors[2][2] = + { + /* Hz kHz */ + /* Hz */ { 1, 1/1000.0 }, + /* kHz */ { 1000.0, 1 } + }; + const double resolution_conversion_factors[3][3] = + { + /* dpi dpcm dppx */ + /* dpi */ { 1, 1/2.54, 1/96.0 }, + /* dpcm */ { 2.54, 1, 2.54/96 }, + /* dppx */ { 96, 96/2.54, 1 } + }; + + UnitClass get_unit_type(UnitType unit) + { + switch (unit & 0xFF00) + { + case UnitClass::LENGTH: return UnitClass::LENGTH; + case UnitClass::ANGLE: return UnitClass::ANGLE; + case UnitClass::TIME: return UnitClass::TIME; + case UnitClass::FREQUENCY: return UnitClass::FREQUENCY; + case UnitClass::RESOLUTION: return UnitClass::RESOLUTION; + default: return UnitClass::INCOMMENSURABLE; + } + }; + + std::string get_unit_class(UnitType unit) + { + switch (unit & 0xFF00) + { + case UnitClass::LENGTH: return "LENGTH"; + case UnitClass::ANGLE: return "ANGLE"; + case UnitClass::TIME: return "TIME"; + case UnitClass::FREQUENCY: return "FREQUENCY"; + case UnitClass::RESOLUTION: return "RESOLUTION"; + default: return "INCOMMENSURABLE"; + } + }; + + UnitType get_main_unit(const UnitClass unit) + { + switch (unit) + { + case UnitClass::LENGTH: return UnitType::PX; + case UnitClass::ANGLE: return UnitType::DEG; + case UnitClass::TIME: return UnitType::SEC; + case UnitClass::FREQUENCY: return UnitType::HERTZ; + case UnitClass::RESOLUTION: return UnitType::DPI; + default: return UnitType::UNKNOWN; + } + }; + + UnitType string_to_unit(const std::string& s) + { + // size units + if (s == "px") return UnitType::PX; + else if (s == "pt") return UnitType::PT; + else if (s == "pc") return UnitType::PC; + else if (s == "mm") return UnitType::MM; + else if (s == "cm") return UnitType::CM; + else if (s == "in") return UnitType::IN; + // angle units + else if (s == "deg") return UnitType::DEG; + else if (s == "grad") return UnitType::GRAD; + else if (s == "rad") return UnitType::RAD; + else if (s == "turn") return UnitType::TURN; + // time units + else if (s == "s") return UnitType::SEC; + else if (s == "ms") return UnitType::MSEC; + // frequency units + else if (s == "Hz") return UnitType::HERTZ; + else if (s == "kHz") return UnitType::KHERTZ; + // resolutions units + else if (s == "dpi") return UnitType::DPI; + else if (s == "dpcm") return UnitType::DPCM; + else if (s == "dppx") return UnitType::DPPX; + // for unknown units + else return UnitType::UNKNOWN; + } + + const char* unit_to_string(UnitType unit) + { + switch (unit) { + // size units + case UnitType::PX: return "px"; + case UnitType::PT: return "pt"; + case UnitType::PC: return "pc"; + case UnitType::MM: return "mm"; + case UnitType::CM: return "cm"; + case UnitType::IN: return "in"; + // angle units + case UnitType::DEG: return "deg"; + case UnitType::GRAD: return "grad"; + case UnitType::RAD: return "rad"; + case UnitType::TURN: return "turn"; + // time units + case UnitType::SEC: return "s"; + case UnitType::MSEC: return "ms"; + // frequency units + case UnitType::HERTZ: return "Hz"; + case UnitType::KHERTZ: return "kHz"; + // resolutions units + case UnitType::DPI: return "dpi"; + case UnitType::DPCM: return "dpcm"; + case UnitType::DPPX: return "dppx"; + // for unknown units + default: return ""; + } + } + + std::string unit_to_class(const std::string& s) + { + if (s == "px") return "LENGTH"; + else if (s == "pt") return "LENGTH"; + else if (s == "pc") return "LENGTH"; + else if (s == "mm") return "LENGTH"; + else if (s == "cm") return "LENGTH"; + else if (s == "in") return "LENGTH"; + // angle units + else if (s == "deg") return "ANGLE"; + else if (s == "grad") return "ANGLE"; + else if (s == "rad") return "ANGLE"; + else if (s == "turn") return "ANGLE"; + // time units + else if (s == "s") return "TIME"; + else if (s == "ms") return "TIME"; + // frequency units + else if (s == "Hz") return "FREQUENCY"; + else if (s == "kHz") return "FREQUENCY"; + // resolutions units + else if (s == "dpi") return "RESOLUTION"; + else if (s == "dpcm") return "RESOLUTION"; + else if (s == "dppx") return "RESOLUTION"; + // for unknown units + return "CUSTOM:" + s; + } + + // throws incompatibleUnits exceptions + double conversion_factor(const std::string& s1, const std::string& s2) + { + // assert for same units + if (s1 == s2) return 1; + // get unit enum from string + UnitType u1 = string_to_unit(s1); + UnitType u2 = string_to_unit(s2); + // query unit group types + UnitClass t1 = get_unit_type(u1); + UnitClass t2 = get_unit_type(u2); + // return the conversion factor + return conversion_factor(u1, u2, t1, t2); + } + + // throws incompatibleUnits exceptions + double conversion_factor(UnitType u1, UnitType u2, UnitClass t1, UnitClass t2) + { + // can't convert between groups + if (t1 != t2) return 0; + // get absolute offset + // used for array acces + size_t i1 = u1 - t1; + size_t i2 = u2 - t2; + // process known units + switch (t1) { + case LENGTH: + return size_conversion_factors[i1][i2]; + case ANGLE: + return angle_conversion_factors[i1][i2]; + case TIME: + return time_conversion_factors[i1][i2]; + case FREQUENCY: + return frequency_conversion_factors[i1][i2]; + case RESOLUTION: + return resolution_conversion_factors[i1][i2]; + case INCOMMENSURABLE: + return 0; + } + // fallback + return 0; + } + + double convert_units(const std::string& lhs, const std::string& rhs, int& lhsexp, int& rhsexp) + { + double f = 0; + // do not convert same ones + if (lhs == rhs) return 0; + // skip already canceled out unit + if (lhsexp == 0) return 0; + if (rhsexp == 0) return 0; + // check if it can be converted + UnitType ulhs = string_to_unit(lhs); + UnitType urhs = string_to_unit(rhs); + // skip units we cannot convert + if (ulhs == UNKNOWN) return 0; + if (urhs == UNKNOWN) return 0; + // query unit group types + UnitClass clhs = get_unit_type(ulhs); + UnitClass crhs = get_unit_type(urhs); + // skip units we cannot convert + if (clhs != crhs) return 0; + // if right denominator is bigger than lhs, we want to keep it in rhs unit + if (rhsexp < 0 && lhsexp > 0 && - rhsexp > lhsexp) { + // get the conversion factor for units + f = conversion_factor(urhs, ulhs, clhs, crhs); + // left hand side has been consumned + f = std::pow(f, lhsexp); + rhsexp += lhsexp; + lhsexp = 0; + } + else { + // get the conversion factor for units + f = conversion_factor(ulhs, urhs, clhs, crhs); + // right hand side has been consumned + f = std::pow(f, rhsexp); + lhsexp += rhsexp; + rhsexp = 0; + } + return f; + } + + bool Units::operator< (const Units& rhs) const + { + return (numerators < rhs.numerators) && + (denominators < rhs.denominators); + } + bool Units::operator== (const Units& rhs) const + { + return (numerators == rhs.numerators) && + (denominators == rhs.denominators); + } + + double Units::normalize() + { + + size_t iL = numerators.size(); + size_t nL = denominators.size(); + + // the final conversion factor + double factor = 1; + + for (size_t i = 0; i < iL; i++) { + std::string &lhs = numerators[i]; + UnitType ulhs = string_to_unit(lhs); + if (ulhs == UNKNOWN) continue; + UnitClass clhs = get_unit_type(ulhs); + UnitType umain = get_main_unit(clhs); + if (ulhs == umain) continue; + double f(conversion_factor(umain, ulhs, clhs, clhs)); + if (f == 0) throw std::runtime_error("INVALID"); + numerators[i] = unit_to_string(umain); + factor /= f; + } + + for (size_t n = 0; n < nL; n++) { + std::string &rhs = denominators[n]; + UnitType urhs = string_to_unit(rhs); + if (urhs == UNKNOWN) continue; + UnitClass crhs = get_unit_type(urhs); + UnitType umain = get_main_unit(crhs); + if (urhs == umain) continue; + double f(conversion_factor(umain, urhs, crhs, crhs)); + if (f == 0) throw std::runtime_error("INVALID"); + denominators[n] = unit_to_string(umain); + factor /= f; + } + + std::sort (numerators.begin(), numerators.end()); + std::sort (denominators.begin(), denominators.end()); + + // return for conversion + return factor; + } + + double Units::reduce() + { + + size_t iL = numerators.size(); + size_t nL = denominators.size(); + + // have less than two units? + if (iL + nL < 2) return 1; + + // first make sure same units cancel each other out + // it seems that a map table will fit nicely to do this + // we basically construct exponents for each unit + // has the advantage that they will be pre-sorted + std::map exponents; + + // initialize by summing up occurences in unit vectors + // this will already cancel out equivalent units (e.q. px/px) + for (size_t i = 0; i < iL; i ++) exponents[numerators[i]] += 1; + for (size_t n = 0; n < nL; n ++) exponents[denominators[n]] -= 1; + + // the final conversion factor + double factor = 1; + + // convert between compatible units + for (size_t i = 0; i < iL; i++) { + for (size_t n = 0; n < nL; n++) { + std::string &lhs = numerators[i], &rhs = denominators[n]; + int &lhsexp = exponents[lhs], &rhsexp = exponents[rhs]; + double f(convert_units(lhs, rhs, lhsexp, rhsexp)); + if (f == 0) continue; + factor /= f; + } + } + + // now we can build up the new unit arrays + numerators.clear(); + denominators.clear(); + + // recreate sorted units vectors + for (auto exp : exponents) { + int &exponent = exp.second; + while (exponent > 0 && exponent --) + numerators.push_back(exp.first); + while (exponent < 0 && exponent ++) + denominators.push_back(exp.first); + } + + // return for conversion + return factor; + + } + + std::string Units::unit() const + { + std::string u; + size_t iL = numerators.size(); + size_t nL = denominators.size(); + for (size_t i = 0; i < iL; i += 1) { + if (i) u += '*'; + u += numerators[i]; + } + if (nL != 0) u += '/'; + for (size_t n = 0; n < nL; n += 1) { + if (n) u += '*'; + u += denominators[n]; + } + return u; + } + + bool Units::is_unitless() const + { + return numerators.empty() && + denominators.empty(); + } + + bool Units::is_valid_css_unit() const + { + return numerators.size() <= 1 && + denominators.size() == 0; + } + + // this does not cover all cases (multiple prefered units) + double Units::convert_factor(const Units& r) const + { + + std::vector miss_nums(0); + std::vector miss_dens(0); + // create copy since we need these for state keeping + std::vector r_nums(r.numerators); + std::vector r_dens(r.denominators); + + auto l_num_it = numerators.begin(); + auto l_num_end = numerators.end(); + + bool l_unitless = is_unitless(); + auto r_unitless = r.is_unitless(); + + // overall conversion + double factor = 1; + + // process all left numerators + while (l_num_it != l_num_end) + { + // get and increment afterwards + const std::string l_num = *(l_num_it ++); + + auto r_num_it = r_nums.begin(), r_num_end = r_nums.end(); + + bool found = false; + // search for compatible numerator + while (r_num_it != r_num_end) + { + // get and increment afterwards + const std::string r_num = *(r_num_it); + // get possible conversion factor for units + double conversion = conversion_factor(l_num, r_num); + // skip incompatible numerator + if (conversion == 0) { + ++ r_num_it; + continue; + } + // apply to global factor + factor *= conversion; + // remove item from vector + r_nums.erase(r_num_it); + // found numerator + found = true; + break; + } + // maybe we did not find any + // left numerator is leftover + if (!found) miss_nums.push_back(l_num); + } + + auto l_den_it = denominators.begin(); + auto l_den_end = denominators.end(); + + // process all left denominators + while (l_den_it != l_den_end) + { + // get and increment afterwards + const std::string l_den = *(l_den_it ++); + + auto r_den_it = r_dens.begin(); + auto r_den_end = r_dens.end(); + + bool found = false; + // search for compatible denominator + while (r_den_it != r_den_end) + { + // get and increment afterwards + const std::string r_den = *(r_den_it); + // get possible converstion factor for units + double conversion = conversion_factor(l_den, r_den); + // skip incompatible denominator + if (conversion == 0) { + ++ r_den_it; + continue; + } + // apply to global factor + factor /= conversion; + // remove item from vector + r_dens.erase(r_den_it); + // found denominator + found = true; + break; + } + // maybe we did not find any + // left denominator is leftover + if (!found) miss_dens.push_back(l_den); + } + + // check left-overs (ToDo: might cancel out?) + if (miss_nums.size() > 0 && !r_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + else if (miss_dens.size() > 0 && !r_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + else if (r_nums.size() > 0 && !l_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + else if (r_dens.size() > 0 && !l_unitless) { + throw Exception::IncompatibleUnits(r, *this); + } + + return factor; + } + +} diff --git a/src/units.hpp b/src/units.hpp new file mode 100644 index 000000000..306f5349b --- /dev/null +++ b/src/units.hpp @@ -0,0 +1,109 @@ +#ifndef SASS_UNITS_H +#define SASS_UNITS_H + +#include +#include +#include +#include + +namespace Sass { + + const double PI = std::acos(-1); + + enum UnitClass { + LENGTH = 0x000, + ANGLE = 0x100, + TIME = 0x200, + FREQUENCY = 0x300, + RESOLUTION = 0x400, + INCOMMENSURABLE = 0x500 + }; + + enum UnitType { + + // size units + IN = UnitClass::LENGTH, + CM, + PC, + MM, + PT, + PX, + + // angle units + DEG = ANGLE, + GRAD, + RAD, + TURN, + + // time units + SEC = TIME, + MSEC, + + // frequency units + HERTZ = FREQUENCY, + KHERTZ, + + // resolutions units + DPI = RESOLUTION, + DPCM, + DPPX, + + // for unknown units + UNKNOWN = INCOMMENSURABLE + + }; + + class Units { + public: + std::vector numerators; + std::vector denominators; + public: + // default constructor + Units() : + numerators(), + denominators() + { } + // copy constructor + Units(const Units* ptr) : + numerators(ptr->numerators), + denominators(ptr->denominators) + { } + // convert to string + std::string unit() const; + // get if units are empty + bool is_unitless() const; + // return if valid for css + bool is_valid_css_unit() const; + // reduce units for output + // returns conversion factor + double reduce(); + // normalize units for compare + // returns conversion factor + double normalize(); + // compare operations + bool operator< (const Units& rhs) const; + bool operator== (const Units& rhs) const; + // factor to convert into given units + double convert_factor(const Units&) const; + }; + + extern const double size_conversion_factors[6][6]; + extern const double angle_conversion_factors[4][4]; + extern const double time_conversion_factors[2][2]; + extern const double frequency_conversion_factors[2][2]; + extern const double resolution_conversion_factors[3][3]; + + UnitType get_main_unit(const UnitClass unit); + enum Sass::UnitType string_to_unit(const std::string&); + const char* unit_to_string(Sass::UnitType unit); + enum Sass::UnitClass get_unit_type(Sass::UnitType unit); + std::string get_unit_class(Sass::UnitType unit); + std::string unit_to_class(const std::string&); + // throws incompatibleUnits exceptions + double conversion_factor(const std::string&, const std::string&); + double conversion_factor(UnitType, UnitType, UnitClass, UnitClass); + double convert_units(const std::string&, const std::string&, int&, int&); + +} + +#endif diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 000000000..82b13f59f --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,34 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "utf8/checked.h" +#include "utf8/unchecked.h" + +#endif // header guard diff --git a/src/utf8/checked.h b/src/utf8/checked.h new file mode 100644 index 000000000..693aee964 --- /dev/null +++ b/src/utf8/checked.h @@ -0,0 +1,334 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t cp) : cp(cp) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + void retreat (octet_iterator& it, distance_type n, octet_iterator start) + { + for (distance_type i = 0; i < n; ++i) + utf8::prior(it, start); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start != end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start != end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end) : + it(octet_it), range_start(range_start), range_end(range_end) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/src/utf8/core.h b/src/utf8/core.h new file mode 100644 index 000000000..f85081f8f --- /dev/null +++ b/src/utf8/core.h @@ -0,0 +1,329 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/src/utf8/unchecked.h b/src/utf8/unchecked.h new file mode 100644 index 000000000..01bdd076a --- /dev/null +++ b/src/utf8/unchecked.h @@ -0,0 +1,235 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" + +namespace utf8 +{ + namespace unchecked + { + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + uint32_t next(octet_iterator& it) + { + uint32_t cp = utf8::internal::mask8(*it); + typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); + switch (length) { + case 1: + break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } + + template + uint32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } + + template + uint32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + + // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) + template + inline uint32_t previous(octet_iterator& it) + { + return utf8::unchecked::prior(it); + } + + template + void advance (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::next(it); + } + + template + void retreat (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::prior(it); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it): it(octet_it) {} + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + + } // namespace utf8::unchecked +} // namespace utf8 + + +#endif // header guard + diff --git a/src/utf8_string.cpp b/src/utf8_string.cpp new file mode 100644 index 000000000..19425521c --- /dev/null +++ b/src/utf8_string.cpp @@ -0,0 +1,102 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "utf8.h" + +namespace Sass { + namespace UTF_8 { + using std::string; + + // naming conventions: + // offset: raw byte offset (0 based) + // position: code point offset (0 based) + // index: code point offset (1 based or negative) + + // function that will count the number of code points (utf-8 characters) from the given beginning to the given end + size_t code_point_count(const string& str, size_t start, size_t end) { + return utf8::distance(str.begin() + start, str.begin() + end); + } + + size_t code_point_count(const string& str) { + return utf8::distance(str.begin(), str.end()); + } + + // function that will return the byte offset at a code point position + size_t offset_at_position(const string& str, size_t position) { + string::const_iterator it = str.begin(); + utf8::advance(it, position, str.end()); + return std::distance(str.begin(), it); + } + + // function that returns number of bytes in a character at offset + size_t code_point_size_at_offset(const string& str, size_t offset) { + // get iterator from string and forward by offset + string::const_iterator stop = str.begin() + offset; + // check if beyond boundary + if (stop == str.end()) return 0; + // advance by one code point + utf8::advance(stop, 1, str.end()); + // calculate offset for code point + return stop - str.begin() - offset; + } + + // function that will return a normalized index, given a crazy one + size_t normalize_index(int index, size_t len) { + long signed_len = static_cast(len); + // assuming the index is 1-based + // we are returning a 0-based index + if (index > 0 && index <= signed_len) { + // positive and within string length + return index-1; + } + else if (index > signed_len) { + // positive and past string length + return len; + } + else if (index == 0) { + return 0; + } + else if (std::abs((double)index) <= signed_len) { + // negative and within string length + return index + signed_len; + } + else { + // negative and past string length + return 0; + } + } + + #ifdef _WIN32 + + // utf16 functions + using std::wstring; + + // convert from utf16/wide string to utf8 string + string convert_from_utf16(const wstring& utf16) + { + string utf8; + // pre-allocate expected memory + utf8.reserve(sizeof(utf16)/2); + utf8::utf16to8(utf16.begin(), utf16.end(), + back_inserter(utf8)); + return utf8; + } + + // convert from utf8 string to utf16/wide string + wstring convert_to_utf16(const string& utf8) + { + wstring utf16; + // pre-allocate expected memory + utf16.reserve(code_point_count(utf8)*2); + utf8::utf8to16(utf8.begin(), utf8.end(), + back_inserter(utf16)); + return utf16; + } + + #endif + + } +} diff --git a/src/utf8_string.hpp b/src/utf8_string.hpp new file mode 100644 index 000000000..5e879bec3 --- /dev/null +++ b/src/utf8_string.hpp @@ -0,0 +1,37 @@ +#ifndef SASS_UTF8_STRING_H +#define SASS_UTF8_STRING_H + +#include +#include "utf8.h" + +namespace Sass { + namespace UTF_8 { + + // naming conventions: + // offset: raw byte offset (0 based) + // position: code point offset (0 based) + // index: code point offset (1 based or negative) + + // function that will count the number of code points (utf-8 characters) from the beginning to the given end + size_t code_point_count(const std::string& str, size_t start, size_t end); + size_t code_point_count(const std::string& str); + + // function that will return the byte offset of a code point in a + size_t offset_at_position(const std::string& str, size_t position); + + // function that returns number of bytes in a character in a string + size_t code_point_size_at_offset(const std::string& str, size_t offset); + + // function that will return a normalized index, given a crazy one + size_t normalize_index(int index, size_t len); + + #ifdef _WIN32 + // functions to handle unicode paths on windows + std::string convert_from_utf16(const std::wstring& wstr); + std::wstring convert_to_utf16(const std::string& str); + #endif + + } +} + +#endif diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 000000000..60f69ab76 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,733 @@ +#include "sass.hpp" +#include "sass.h" +#include "ast.hpp" +#include "util.hpp" +#include "lexer.hpp" +#include "prelexer.hpp" +#include "constants.hpp" +#include "utf8/checked.h" + +#include +#include +#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) +#include +#endif + +namespace Sass { + + double round(double val, size_t precision) + { + // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets + // See https://github.com/sass/node-sass/issues/1854 for details + // FIXME: Remove this workaround when we switch to VS2015+ + #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) + static std::once_flag flag; + std::call_once(flag, []() { _set_FMA3_enable(0); }); + #endif + + // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 + if (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); + else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); + // work around some compiler issue + // cygwin has it not defined in std + using namespace std; + return ::round(val); + } + + /* Locale unspecific atof function. */ + double sass_strtod(const char *str) + { + char separator = *(localeconv()->decimal_point); + if(separator != '.'){ + // The current locale specifies another + // separator. convert the separator to the + // one understood by the locale if needed + const char *found = strchr(str, '.'); + if(found != NULL){ + // substitution is required. perform the substitution on a copy + // of the string. This is slower but it is thread safe. + char *copy = sass_copy_c_string(str); + *(copy + (found - str)) = separator; + double res = strtod(copy, NULL); + free(copy); + return res; + } + } + + return strtod(str, NULL); + } + + // helper for safe access to c_ctx + const char* safe_str (const char* str, const char* alt) { + return str == NULL ? alt : str; + } + + void free_string_array(char ** arr) { + if(!arr) + return; + + char **it = arr; + while (it && (*it)) { + free(*it); + ++it; + } + + free(arr); + } + + char **copy_strings(const std::vector& strings, char*** array, int skip) { + int num = static_cast(strings.size()) - skip; + char** arr = (char**) calloc(num + 1, sizeof(char*)); + if (arr == 0) + return *array = (char **)NULL; + + for(int i = 0; i < num; i++) { + arr[i] = (char*) malloc(sizeof(char) * (strings[i + skip].size() + 1)); + if (arr[i] == 0) { + free_string_array(arr); + return *array = (char **)NULL; + } + std::copy(strings[i + skip].begin(), strings[i + skip].end(), arr[i]); + arr[i][strings[i + skip].size()] = '\0'; + } + + arr[num] = 0; + return *array = arr; + } + + // read css string (handle multiline DELIM) + std::string read_css_string(const std::string& str, bool css) + { + if (!css) return str; + std::string out(""); + bool esc = false; + for (auto i : str) { + if (i == '\\') { + esc = ! esc; + } else if (esc && i == '\r') { + continue; + } else if (esc && i == '\n') { + out.resize (out.size () - 1); + esc = false; + continue; + } else { + esc = false; + } + out.push_back(i); + } + // happens when parsing does not correctly skip + // over escaped sequences for ie. interpolations + // one example: foo\#{interpolate} + // if (esc) out += '\\'; + return out; + } + + // double escape all escape sequences + // keep unescaped quotes and backslashes + std::string evacuate_escapes(const std::string& str) + { + std::string out(""); + bool esc = false; + for (auto i : str) { + if (i == '\\' && !esc) { + out += '\\'; + out += '\\'; + esc = true; + } else if (esc && i == '"') { + out += '\\'; + out += i; + esc = false; + } else if (esc && i == '\'') { + out += '\\'; + out += i; + esc = false; + } else if (esc && i == '\\') { + out += '\\'; + out += i; + esc = false; + } else { + esc = false; + out += i; + } + } + // happens when parsing does not correctly skip + // over escaped sequences for ie. interpolations + // one example: foo\#{interpolate} + // if (esc) out += '\\'; + return out; + } + + // bell characters are replaced with spaces + void newline_to_space(std::string& str) + { + std::replace(str.begin(), str.end(), '\n', ' '); + } + + // bell characters are replaced with spaces + // also eats spaces after line-feeds (ltrim) + std::string string_to_output(const std::string& str) + { + std::string out(""); + bool lf = false; + for (auto i : str) { + if (i == '\n') { + out += ' '; + lf = true; + } else if (!(lf && isspace(i))) { + out += i; + lf = false; + } + } + return out; + } + + std::string escape_string(const std::string& str) + { + std::string out(""); + for (auto i : str) { + if (i == '\n') { + out += "\\n"; + } else if (i == '\r') { + out += "\\r"; + } else if (i == '\t') { + out += "\\t"; + } else { + out += i; + } + } + return out; + } + + std::string comment_to_string(const std::string& text) + { + std::string str = ""; + size_t has = 0; + char prev = 0; + bool clean = false; + for (auto i : text) { + if (clean) { + if (i == '\n') { has = 0; } + else if (i == '\r') { has = 0; } + else if (i == '\t') { ++ has; } + else if (i == ' ') { ++ has; } + else if (i == '*') {} + else { + clean = false; + str += ' '; + if (prev == '*' && i == '/') str += "*/"; + else str += i; + } + } else if (i == '\n') { + clean = true; + } else if (i == '\r') { + clean = true; + } else { + str += i; + } + prev = i; + } + if (has) return str; + else return text; + } + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string cotains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + char detect_best_quotemark(const char* s, char qm) + { + // ensure valid fallback quote_mark + char quote_mark = qm && qm != '*' ? qm : '"'; + while (*s) { + // force double quotes as soon + // as one single quote is found + if (*s == '\'') { return '"'; } + // a single does not force quote_mark + // maybe we see a double quote later + else if (*s == '"') { quote_mark = '\''; } + ++ s; + } + return quote_mark; + } + + std::string read_hex_escapes(const std::string& s) + { + + std::string result; + bool skipped = false; + + for (size_t i = 0, L = s.length(); i < L; ++i) { + + // implement the same strange ruby sass behavior + // an escape sequence can also mean a unicode char + if (s[i] == '\\' && !skipped) { + + // remember + skipped = true; + + // escape length + size_t len = 1; + + // parse as many sequence chars as possible + // ToDo: Check if ruby aborts after possible max + while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + + if (len > 1) { + + // convert the extracted hex string to code point value + // ToDo: Maybe we could do this without creating a substring + uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); + + if (s[i + len] == ' ') ++ len; + + // assert invalid code points + if (cp == 0) cp = 0xFFFD; + // replace bell character + // if (cp == '\n') cp = 32; + + // use a very simple approach to convert via utf8 lib + // maybe there is a more elegant way; maybe we shoud + // convert the whole output from string to a stream!? + // allocate memory for utf8 char and convert to utf8 + unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); + for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(u[m]); + + // skip some more chars? + i += len - 1; skipped = false; + + } + + else { + + skipped = false; + + result.push_back(s[i]); + + } + + } + + else { + + result.push_back(s[i]); + + } + + } + + return result; + + } + + std::string unquote(const std::string& s, char* qd, bool keep_utf8_sequences, bool strict) + { + + // not enough room for quotes + // no possibility to unquote + if (s.length() < 2) return s; + + char q; + bool skipped = false; + + // this is no guarantee that the unquoting will work + // what about whitespace before/after the quote_mark? + if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; + else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; + else return s; + + std::string unq; + unq.reserve(s.length()-2); + + for (size_t i = 1, L = s.length() - 1; i < L; ++i) { + + // implement the same strange ruby sass behavior + // an escape sequence can also mean a unicode char + if (s[i] == '\\' && !skipped) { + // remember + skipped = true; + + // skip it + // ++ i; + + // if (i == L) break; + + // escape length + size_t len = 1; + + // parse as many sequence chars as possible + // ToDo: Check if ruby aborts after possible max + while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + + // hex string? + if (keep_utf8_sequences) { + unq.push_back(s[i]); + } else if (len > 1) { + + // convert the extracted hex string to code point value + // ToDo: Maybe we could do this without creating a substring + uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); + + if (s[i + len] == ' ') ++ len; + + // assert invalid code points + if (cp == 0) cp = 0xFFFD; + // replace bell character + // if (cp == '\n') cp = 32; + + // use a very simple approach to convert via utf8 lib + // maybe there is a more elegant way; maybe we shoud + // convert the whole output from string to a stream!? + // allocate memory for utf8 char and convert to utf8 + unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); + for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(u[m]); + + // skip some more chars? + i += len - 1; skipped = false; + + } + + + } + // check for unexpected delimiter + // be strict and throw error back + // else if (!skipped && q == s[i]) { + // // don't be that strict + // return s; + // // this basically always means an internal error and not users fault + // error("Unescaped delimiter in string to unquote found. [" + s + "]", ParserState("[UNQUOTE]")); + // } + else { + if (strict && !skipped) { + if (s[i] == q) return s; + } + skipped = false; + unq.push_back(s[i]); + } + + } + if (skipped) { return s; } + if (qd) *qd = q; + return unq; + + } + + std::string quote(const std::string& s, char q) + { + + // autodetect with fallback to given quote + q = detect_best_quotemark(s.c_str(), q); + + // return an empty quoted string + if (s.empty()) return std::string(2, q ? q : '"'); + + std::string quoted; + quoted.reserve(s.length()+2); + quoted.push_back(q); + + const char* it = s.c_str(); + const char* end = it + strlen(it) + 1; + while (*it && it < end) { + const char* now = it; + + if (*it == q) { + quoted.push_back('\\'); + } else if (*it == '\\') { + quoted.push_back('\\'); + } + + int cp = utf8::next(it, end); + + // in case of \r, check if the next in sequence + // is \n and then advance the iterator and skip \r + if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { + cp = utf8::next(it, end); + } + + if (cp == '\n') { + quoted.push_back('\\'); + quoted.push_back('a'); + // we hope we can remove this flag once we figure out + // why ruby sass has these different output behaviors + // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") + using namespace Prelexer; + if (alternatives < + Prelexer::char_range<'a', 'f'>, + Prelexer::char_range<'A', 'F'>, + Prelexer::char_range<'0', '9'>, + space + >(it) != NULL) { + quoted.push_back(' '); + } + } else if (cp < 127) { + quoted.push_back((char) cp); + } else { + while (now < it) { + quoted.push_back(*now); + ++ now; + } + } + } + + quoted.push_back(q); + return quoted; + } + + bool is_hex_doublet(double n) + { + return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || + n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || + n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || + n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; + } + + bool is_color_doublet(double r, double g, double b) + { + return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); + } + + bool peek_linefeed(const char* start) + { + using namespace Prelexer; + using namespace Constants; + return sequence < + zero_plus < + alternatives < + exactly <' '>, + exactly <'\t'>, + line_comment, + block_comment, + delimited_by < + slash_star, + star_slash, + false + > + > + >, + re_linebreak + >(start) != 0; + } + + namespace Util { + using std::string; + + std::string rtrim(const std::string &str) { + std::string trimmed = str; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) + { trimmed.erase(pos_ws + 1); } + else { trimmed.clear(); } + return trimmed; + } + + std::string normalize_underscores(const std::string& str) { + std::string normalized = str; + for(size_t i = 0, L = normalized.length(); i < L; ++i) { + if(normalized[i] == '_') { + normalized[i] = '-'; + } + } + return normalized; + } + + std::string normalize_decimals(const std::string& str) { + std::string prefix = "0"; + std::string normalized = str; + + return normalized[0] == '.' ? normalized.insert(0, prefix) : normalized; + } + + bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style) { + if (r == NULL) { + return false; + } + + Block_Obj b = r->block(); + + Selector_List_Ptr sl = Cast(r->selector()); + bool hasSelectors = sl ? sl->length() > 0 : false; + + if (!hasSelectors) { + return false; + } + + bool hasDeclarations = false; + bool hasPrintableChildBlocks = false; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (Cast(stm)) { + return true; + } else if (Declaration_Ptr d = Cast(stm)) { + return isPrintable(d, style); + } else if (Has_Block_Ptr p = Cast(stm)) { + Block_Obj pChildBlock = p->block(); + if (isPrintable(pChildBlock, style)) { + hasPrintableChildBlocks = true; + } + } else if (Comment_Ptr c = Cast(stm)) { + // keep for uncompressed + if (style != COMPRESSED) { + hasDeclarations = true; + } + // output style compressed + if (c->is_important()) { + hasDeclarations = c->is_important(); + } + } else { + hasDeclarations = true; + } + + if (hasDeclarations || hasPrintableChildBlocks) { + return true; + } + } + + return false; + } + + bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style) + { + return ! s->value().empty(); + } + + bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style) + { + return true; + } + + bool isPrintable(Declaration_Ptr d, Sass_Output_Style style) + { + Expression_Obj val = d->value(); + if (String_Quoted_Obj sq = Cast(val)) return isPrintable(sq.ptr(), style); + if (String_Constant_Obj sc = Cast(val)) return isPrintable(sc.ptr(), style); + return true; + } + + bool isPrintable(Supports_Block_Ptr f, Sass_Output_Style style) { + if (f == NULL) { + return false; + } + + Block_Obj b = f->block(); + + bool hasDeclarations = false; + bool hasPrintableChildBlocks = false; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (Cast(stm) || Cast(stm)) { + hasDeclarations = true; + } + else if (Has_Block_Ptr b = Cast(stm)) { + Block_Obj pChildBlock = b->block(); + if (!b->is_invisible()) { + if (isPrintable(pChildBlock, style)) { + hasPrintableChildBlocks = true; + } + } + } + + if (hasDeclarations || hasPrintableChildBlocks) { + return true; + } + } + + return false; + } + + bool isPrintable(Media_Block_Ptr m, Sass_Output_Style style) + { + if (m == 0) return false; + Block_Obj b = m->block(); + if (b == 0) return false; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (Cast(stm)) return true; + else if (Cast(stm)) return true; + else if (Comment_Ptr c = Cast(stm)) { + if (isPrintable(c, style)) { + return true; + } + } + else if (Ruleset_Ptr r = Cast(stm)) { + if (isPrintable(r, style)) { + return true; + } + } + else if (Supports_Block_Ptr f = Cast(stm)) { + if (isPrintable(f, style)) { + return true; + } + } + else if (Media_Block_Ptr mb = Cast(stm)) { + if (isPrintable(mb, style)) { + return true; + } + } + else if (Has_Block_Ptr b = Cast(stm)) { + if (isPrintable(b->block(), style)) { + return true; + } + } + } + return false; + } + + bool isPrintable(Comment_Ptr c, Sass_Output_Style style) + { + // keep for uncompressed + if (style != COMPRESSED) { + return true; + } + // output style compressed + if (c->is_important()) { + return true; + } + // not printable + return false; + }; + + bool isPrintable(Block_Obj b, Sass_Output_Style style) { + if (!b) { + return false; + } + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj stm = b->at(i); + if (Cast(stm) || Cast(stm)) { + return true; + } + else if (Comment_Ptr c = Cast(stm)) { + if (isPrintable(c, style)) { + return true; + } + } + else if (Ruleset_Ptr r = Cast(stm)) { + if (isPrintable(r, style)) { + return true; + } + } + else if (Supports_Block_Ptr f = Cast(stm)) { + if (isPrintable(f, style)) { + return true; + } + } + else if (Media_Block_Ptr m = Cast(stm)) { + if (isPrintable(m, style)) { + return true; + } + } + else if (Has_Block_Ptr b = Cast(stm)) { + if (isPrintable(b->block(), style)) { + return true; + } + } + } + + return false; + } + + bool isAscii(const char chr) { + return unsigned(chr) < 128; + } + + } +} diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 000000000..f23475fe0 --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,56 @@ +#ifndef SASS_UTIL_H +#define SASS_UTIL_H + +#include +#include +#include +#include "sass.hpp" +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#define SASS_ASSERT(cond, msg) assert(cond && msg) + +namespace Sass { + + double round(double val, size_t precision = 0); + double sass_strtod(const char* str); + const char* safe_str(const char *, const char* = ""); + void free_string_array(char **); + char **copy_strings(const std::vector&, char ***, int = 0); + std::string read_css_string(const std::string& str, bool css = true); + std::string evacuate_escapes(const std::string& str); + std::string string_to_output(const std::string& str); + std::string comment_to_string(const std::string& text); + std::string read_hex_escapes(const std::string& str); + std::string escape_string(const std::string& str); + void newline_to_space(std::string& str); + + std::string quote(const std::string&, char q = 0); + std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); + char detect_best_quotemark(const char* s, char qm = '"'); + + bool is_hex_doublet(double n); + bool is_color_doublet(double r, double g, double b); + + bool peek_linefeed(const char* start); + + namespace Util { + + std::string rtrim(const std::string& str); + + std::string normalize_underscores(const std::string& str); + std::string normalize_decimals(const std::string& str); + + bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); + bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); + bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); + bool isAscii(const char chr); + + } +} +#endif diff --git a/src/values.cpp b/src/values.cpp new file mode 100644 index 000000000..0f2fd48d7 --- /dev/null +++ b/src/values.cpp @@ -0,0 +1,131 @@ +#include "sass.hpp" +#include "sass.h" +#include "values.hpp" + +#include + +namespace Sass { + + // convert value from C++ side to C-API + union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val) + { + if (val->concrete_type() == Expression::NUMBER) + { + Number_Ptr_Const res = Cast(val); + return sass_make_number(res->value(), res->unit().c_str()); + } + else if (val->concrete_type() == Expression::COLOR) + { + Color_Ptr_Const col = Cast(val); + return sass_make_color(col->r(), col->g(), col->b(), col->a()); + } + else if (val->concrete_type() == Expression::LIST) + { + List_Ptr_Const l = Cast(val); + union Sass_Value* list = sass_make_list(l->size(), l->separator(), l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + Expression_Obj obj = l->at(i); + auto val = ast_node_to_sass_value(obj); + sass_list_set_value(list, i, val); + } + return list; + } + else if (val->concrete_type() == Expression::MAP) + { + Map_Ptr_Const m = Cast(val); + union Sass_Value* map = sass_make_map(m->length()); + size_t i = 0; for (Expression_Obj key : m->keys()) { + sass_map_set_key(map, i, ast_node_to_sass_value(key)); + sass_map_set_value(map, i, ast_node_to_sass_value(m->at(key))); + ++ i; + } + return map; + } + else if (val->concrete_type() == Expression::NULL_VAL) + { + return sass_make_null(); + } + else if (val->concrete_type() == Expression::BOOLEAN) + { + Boolean_Ptr_Const res = Cast(val); + return sass_make_boolean(res->value()); + } + else if (val->concrete_type() == Expression::STRING) + { + if (String_Quoted_Ptr_Const qstr = Cast(val)) + { + return sass_make_qstring(qstr->value().c_str()); + } + else if (String_Constant_Ptr_Const cstr = Cast(val)) + { + return sass_make_string(cstr->value().c_str()); + } + } + return sass_make_error("unknown sass value type"); + } + + // convert value from C-API to C++ side + Value_Ptr sass_value_to_ast_node (const union Sass_Value* val) + { + switch (sass_value_get_tag(val)) { + case SASS_NUMBER: + return SASS_MEMORY_NEW(Number, + ParserState("[C-VALUE]"), + sass_number_get_value(val), + sass_number_get_unit(val)); + case SASS_BOOLEAN: + return SASS_MEMORY_NEW(Boolean, + ParserState("[C-VALUE]"), + sass_boolean_get_value(val)); + case SASS_COLOR: + return SASS_MEMORY_NEW(Color, + ParserState("[C-VALUE]"), + sass_color_get_r(val), + sass_color_get_g(val), + sass_color_get_b(val), + sass_color_get_a(val)); + case SASS_STRING: + if (sass_string_is_quoted(val)) { + return SASS_MEMORY_NEW(String_Quoted, + ParserState("[C-VALUE]"), + sass_string_get_value(val)); + } + return SASS_MEMORY_NEW(String_Constant, + ParserState("[C-VALUE]"), + sass_string_get_value(val)); + case SASS_LIST: { + List_Ptr l = SASS_MEMORY_NEW(List, + ParserState("[C-VALUE]"), + sass_list_get_length(val), + sass_list_get_separator(val)); + for (size_t i = 0, L = sass_list_get_length(val); i < L; ++i) { + l->append(sass_value_to_ast_node(sass_list_get_value(val, i))); + } + l->is_bracketed(sass_list_get_is_bracketed(val)); + return l; + } + case SASS_MAP: { + Map_Ptr m = SASS_MEMORY_NEW(Map, ParserState("[C-VALUE]")); + for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) { + *m << std::make_pair( + sass_value_to_ast_node(sass_map_get_key(val, i)), + sass_value_to_ast_node(sass_map_get_value(val, i))); + } + return m; + } + case SASS_NULL: + return SASS_MEMORY_NEW(Null, ParserState("[C-VALUE]")); + case SASS_ERROR: + return SASS_MEMORY_NEW(Custom_Error, + ParserState("[C-VALUE]"), + sass_error_get_message(val)); + case SASS_WARNING: + return SASS_MEMORY_NEW(Custom_Warning, + ParserState("[C-VALUE]"), + sass_warning_get_message(val)); + default: break; + } + return 0; + } + +} diff --git a/src/values.hpp b/src/values.hpp new file mode 100644 index 000000000..f78ca1281 --- /dev/null +++ b/src/values.hpp @@ -0,0 +1,12 @@ +#ifndef SASS_VALUES_H +#define SASS_VALUES_H + +#include "ast.hpp" + +namespace Sass { + + union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val); + Value_Ptr sass_value_to_ast_node (const union Sass_Value* val); + +} +#endif diff --git a/test/test_node.cpp b/test/test_node.cpp new file mode 100644 index 000000000..905dc1899 --- /dev/null +++ b/test/test_node.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include "node.hpp" +#include "parser.hpp" + + +#define STATIC_ARRAY_SIZE(array) (sizeof((array))/sizeof((array[0]))) + + +namespace Sass { + + Context ctx = Context::Data(); + + const char* const ROUNDTRIP_TESTS[] = { + NULL, + "~", + "CMPD", + "~ CMPD", + "CMPD >", + "> > CMPD", + "CMPD ~ ~", + "> + CMPD1.CMPD2 > ~", + "> + CMPD1.CMPD2 CMPD3.CMPD4 > ~", + "+ CMPD1 CMPD2 ~ CMPD3 + CMPD4 > CMPD5 > ~" + }; + + + + static Complex_Selector* createComplexSelector(std::string src) { + std::string temp(src); + temp += ";"; + return (*Parser::from_c_str(temp.c_str(), ctx, "", Position()).parse_selector_list())[0]; + } + + + void roundtripTest(const char* toTest) { + + // Create the initial selector + + Complex_Selector* pOrigSelector = NULL; + if (toTest) { + pOrigSelector = createComplexSelector(toTest); + } + + std::string expected(pOrigSelector ? pOrigSelector->to_string() : "NULL"); + + + // Roundtrip the selector into a node and back + + Node node = complexSelectorToNode(pOrigSelector, ctx); + + std::stringstream nodeStringStream; + nodeStringStream << node; + std::string nodeString = nodeStringStream.str(); + cout << "ASNODE: " << node << endl; + + Complex_Selector* pNewSelector = nodeToComplexSelector(node, ctx); + + // Show the result + + std::string result(pNewSelector ? pNewSelector->to_string() : "NULL"); + + cout << "SELECTOR: " << expected << endl; + cout << "NEW SELECTOR: " << result << endl; + + + // Test that they are equal using the equality operator + + assert( (!pOrigSelector && !pNewSelector ) || (pOrigSelector && pNewSelector) ); + if (pOrigSelector) { + assert( *pOrigSelector == *pNewSelector ); + } + + + // Test that they are equal by comparing the string versions of the selectors + + assert(expected == result); + + } + + + int main() { + for (int index = 0; index < STATIC_ARRAY_SIZE(ROUNDTRIP_TESTS); index++) { + const char* const toTest = ROUNDTRIP_TESTS[index]; + cout << "\nINPUT STRING: " << (toTest ? toTest : "NULL") << endl; + roundtripTest(toTest); + } + + cout << "\nTesting Done.\n"; + } + + +} diff --git a/test/test_paths.cpp b/test/test_paths.cpp new file mode 100644 index 000000000..bfcf8ec6d --- /dev/null +++ b/test/test_paths.cpp @@ -0,0 +1,28 @@ +#include +#include "../paths.hpp" + +using namespace Sass; + +template +std::vector& operator<<(std::vector& v, const T& e) +{ + v.push_back(e); + return v; +} + +int main() +{ + std::vector v1, v2, v3; + v1 << 1 << 2; + v2 << 3; + v3 << 4 << 5 << 6; + + std::vector > ss; + ss << v1 << v2 << v3; + + std::vector > ps = paths(ss); + for (size_t i = 0, S = ps.size(); i < S; ++i) { + std::cout << vector_to_string(ps[i]) << std::endl; + } + return 0; +} diff --git a/test/test_selector_difference.cpp b/test/test_selector_difference.cpp new file mode 100644 index 000000000..e2880c0b0 --- /dev/null +++ b/test/test_selector_difference.cpp @@ -0,0 +1,25 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include +#include + +using namespace Sass; + +Context ctx = Context::Data(); + +Compound_Selector* selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } + +void diff(std::string s, std::string t) +{ + std::cout << s << " - " << t << " = " << selector(s + ";")->minus(selector(t + ";"), ctx)->to_string() << std::endl; +} + +int main() +{ + diff(".a.b.c", ".c.b"); + diff(".a.b.c", ".fludge.b"); + + return 0; +} diff --git a/test/test_specificity.cpp b/test/test_specificity.cpp new file mode 100644 index 000000000..ba9bbfc46 --- /dev/null +++ b/test/test_specificity.cpp @@ -0,0 +1,25 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include +#include + +using namespace Sass; + +Context ctx = Context::Data(); + +Selector* selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_selector_list(); } + +void spec(std::string sel) +{ std::cout << sel << "\t::\t" << selector(sel + ";")->specificity() << std::endl; } + +int main() +{ + spec("foo bar hux"); + spec(".foo .bar hux"); + spec("#foo .bar[hux='mux']"); + spec("a b c d e f"); + + return 0; +} diff --git a/test/test_subset_map.cpp b/test/test_subset_map.cpp new file mode 100644 index 000000000..37945143f --- /dev/null +++ b/test/test_subset_map.cpp @@ -0,0 +1,472 @@ +#include +#include +#include +#include "../subset_map.hpp" + +Subset_Map ssm; + +string toString(std::vector v); +string toString(std::vector>> v); +void assertEqual(string std::sExpected, std::string sResult); + +void setup() { + ssm.clear(); + + //@ssm[Set[1, 2]] = "Foo" + std::vector s1; + s1.push_back("1"); + s1.push_back("2"); + ssm.put(s1, "Foo"); + + //@ssm[Set["fizz", "fazz"]] = "Bar" + std::vector s2; + s2.push_back("fizz"); + s2.push_back("fazz"); + ssm.put(s2, "Bar"); + + //@ssm[Set[:foo, :bar]] = "Baz" + std::vector s3; + s3.push_back(":foo"); + s3.push_back(":bar"); + ssm.put(s3, "Baz"); + + //@ssm[Set[:foo, :bar, :baz]] = "Bang" + std::vector s4; + s4.push_back(":foo"); + s4.push_back(":bar"); + s4.push_back(":baz"); + ssm.put(s4, "Bang"); + + //@ssm[Set[:bip, :bop, :blip]] = "Qux" + std::vector s5; + s5.push_back(":bip"); + s5.push_back(":bop"); + s5.push_back(":blip"); + ssm.put(s5, "Qux"); + + //@ssm[Set[:bip, :bop]] = "Thram" + std::vector s6; + s6.push_back(":bip"); + s6.push_back(":bop"); + ssm.put(s6, "Thram"); +} + +void testEqualKeys() { + std::cout << "testEqualKeys" << std::endl; + + //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2]) + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); + + //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz"]) + std::vector k2; + k2.push_back("fizz"); + k2.push_back("fazz"); + assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); + + std::cout << std::endl; +} + +void testSubsetKeys() { + std::cout << "testSubsetKeys" << std::endl; + + //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2, "fuzz"]) + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + k1.push_back("fuzz"); + assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); + + //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz", 3]) + std::vector k2; + k2.push_back("fizz"); + k2.push_back("fazz"); + k2.push_back("3"); + assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); + + std::cout << std::endl; +} + +void testSupersetKeys() { + std::cout << "testSupersetKeys" << std::endl; + + //assert_equal [], @ssm.get(Set[1]) + std::vector k1; + k1.push_back("1"); + assertEqual("[]", toString(ssm.get_kv(k1))); + + //assert_equal [], @ssm.get(Set[2]) + std::vector k2; + k2.push_back("2"); + assertEqual("[]", toString(ssm.get_kv(k2))); + + //assert_equal [], @ssm.get(Set["fizz"]) + std::vector k3; + k3.push_back("fizz"); + assertEqual("[]", toString(ssm.get_kv(k3))); + + //assert_equal [], @ssm.get(Set["fazz"]) + std::vector k4; + k4.push_back("fazz"); + assertEqual("[]", toString(ssm.get_kv(k4))); + + std::cout << std::endl; +} + +void testDisjointKeys() { + std::cout << "testDisjointKeys" << std::endl; + + //assert_equal [], @ssm.get(Set[3, 4]) + std::vector k1; + k1.push_back("3"); + k1.push_back("4"); + assertEqual("[]", toString(ssm.get_kv(k1))); + + //assert_equal [], @ssm.get(Set["fuzz", "frizz"]) + std::vector k2; + k2.push_back("fuzz"); + k2.push_back("frizz"); + assertEqual("[]", toString(ssm.get_kv(k2))); + + //assert_equal [], @ssm.get(Set["gran", 15]) + std::vector k3; + k3.push_back("gran"); + k3.push_back("15"); + assertEqual("[]", toString(ssm.get_kv(k3))); + + std::cout << std::endl; +} + +void testSemiDisjointKeys() { + std::cout << "testSemiDisjointKeys" << std::endl; + + //assert_equal [], @ssm.get(Set[2, 3]) + std::vector k1; + k1.push_back("2"); + k1.push_back("3"); + assertEqual("[]", toString(ssm.get_kv(k1))); + + //assert_equal [], @ssm.get(Set["fizz", "fuzz"]) + std::vector k2; + k2.push_back("fizz"); + k2.push_back("fuzz"); + assertEqual("[]", toString(ssm.get_kv(k2))); + + //assert_equal [], @ssm.get(Set[1, "fazz"]) + std::vector k3; + k3.push_back("1"); + k3.push_back("fazz"); + assertEqual("[]", toString(ssm.get_kv(k3))); + + std::cout << std::endl; +} + +void testEmptyKeySet() { + std::cout << "testEmptyKeySet" << std::endl; + + //assert_raises(ArgumentError) {@ssm[Set[]] = "Fail"} + std::vector s1; + try { + ssm.put(s1, "Fail"); + } + catch (const char* &e) { + assertEqual("internal error: subset map keys may not be empty", e); + } +} + +void testEmptyKeyGet() { + std::cout << "testEmptyKeyGet" << std::endl; + + //assert_equal [], @ssm.get(Set[]) + std::vector k1; + assertEqual("[]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} +void testMultipleSubsets() { + std::cout << "testMultipleSubsets" << std::endl; + + //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, "fizz", "fazz"]) + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + k1.push_back("fizz"); + k1.push_back("fazz"); + assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k1))); + + //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, 3, "fizz", "fazz", "fuzz"]) + std::vector k2; + k2.push_back("1"); + k2.push_back("2"); + k2.push_back("3"); + k2.push_back("fizz"); + k2.push_back("fazz"); + k2.push_back("fuzz"); + assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); + + //assert_equal [["Baz", Set[:foo, :bar]]], @ssm.get(Set[:foo, :bar]) + std::vector k3; + k3.push_back(":foo"); + k3.push_back(":bar"); + assertEqual("[[Baz, Set[:foo, :bar]]]", toString(ssm.get_kv(k3))); + + //assert_equal [["Baz", Set[:foo, :bar]], ["Bang", Set[:foo, :bar, :baz]]], @ssm.get(Set[:foo, :bar, :baz]) + std::vector k4; + k4.push_back(":foo"); + k4.push_back(":bar"); + k4.push_back(":baz"); + assertEqual("[[Baz, Set[:foo, :bar]], [Bang, Set[:foo, :bar, :baz]]]", toString(ssm.get_kv(k4))); + + std::cout << std::endl; +} +void testBracketBracket() { + std::cout << "testBracketBracket" << std::endl; + + //assert_equal ["Foo"], @ssm[Set[1, 2, "fuzz"]] + std::vector k1; + k1.push_back("1"); + k1.push_back("2"); + k1.push_back("fuzz"); + assertEqual("[Foo]", toString(ssm.get_v(k1))); + + //assert_equal ["Baz", "Bang"], @ssm[Set[:foo, :bar, :baz]] + std::vector k2; + k2.push_back(":foo"); + k2.push_back(":bar"); + k2.push_back(":baz"); + assertEqual("[Baz, Bang]", toString(ssm.get_v(k2))); + + std::cout << std::endl; +} + +void testKeyOrder() { + std::cout << "testEqualKeys" << std::endl; + + //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[2, 1]) + std::vector k1; + k1.push_back("2"); + k1.push_back("1"); + assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} + +void testOrderPreserved() { + std::cout << "testOrderPreserved" << std::endl; + //@ssm[Set[10, 11, 12]] = 1 + std::vector s1; + s1.push_back("10"); + s1.push_back("11"); + s1.push_back("12"); + ssm.put(s1, "1"); + + //@ssm[Set[10, 11]] = 2 + std::vector s2; + s2.push_back("10"); + s2.push_back("11"); + ssm.put(s2, "2"); + + //@ssm[Set[11]] = 3 + std::vector s3; + s3.push_back("11"); + ssm.put(s3, "3"); + + //@ssm[Set[11, 12]] = 4 + std::vector s4; + s4.push_back("11"); + s4.push_back("12"); + ssm.put(s4, "4"); + + //@ssm[Set[9, 10, 11, 12, 13]] = 5 + std::vector s5; + s5.push_back("9"); + s5.push_back("10"); + s5.push_back("11"); + s5.push_back("12"); + s5.push_back("13"); + ssm.put(s5, "5"); + + //@ssm[Set[10, 13]] = 6 + std::vector s6; + s6.push_back("10"); + s6.push_back("13"); + ssm.put(s6, "6"); + + //assert_equal([[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]], @ssm.get(Set[9, 10, 11, 12, 13])) + std::vector k1; + k1.push_back("9"); + k1.push_back("10"); + k1.push_back("11"); + k1.push_back("12"); + k1.push_back("13"); + assertEqual("[[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} +void testMultipleEqualValues() { + std::cout << "testMultipleEqualValues" << std::endl; + //@ssm[Set[11, 12]] = 1 + std::vector s1; + s1.push_back("11"); + s1.push_back("12"); + ssm.put(s1, "1"); + + //@ssm[Set[12, 13]] = 2 + std::vector s2; + s2.push_back("12"); + s2.push_back("13"); + ssm.put(s2, "2"); + + //@ssm[Set[13, 14]] = 1 + std::vector s3; + s3.push_back("13"); + s3.push_back("14"); + ssm.put(s3, "1"); + + //@ssm[Set[14, 15]] = 1 + std::vector s4; + s4.push_back("14"); + s4.push_back("15"); + ssm.put(s4, "1"); + + //assert_equal([[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]], @ssm.get(Set[11, 12, 13, 14, 15])) + std::vector k1; + k1.push_back("11"); + k1.push_back("12"); + k1.push_back("13"); + k1.push_back("14"); + k1.push_back("15"); + assertEqual("[[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]]", toString(ssm.get_kv(k1))); + + std::cout << std::endl; +} + +int main() +{ + std::vector s1; + s1.push_back("1"); + s1.push_back("2"); + + std::vector s2; + s2.push_back("2"); + s2.push_back("3"); + + std::vector s3; + s3.push_back("3"); + s3.push_back("4"); + + ssm.put(s1, "value1"); + ssm.put(s2, "value2"); + ssm.put(s3, "value3"); + + std::vector s4; + s4.push_back("1"); + s4.push_back("2"); + s4.push_back("3"); + + std::vector > > fetched(ssm.get_kv(s4)); + + std::cout << "PRINTING RESULTS:" << std::endl; + for (size_t i = 0, S = fetched.size(); i < S; ++i) { + std::cout << fetched[i].first << std::endl; + } + + Subset_Map ssm2; + ssm2.put(s1, "foo"); + ssm2.put(s2, "bar"); + ssm2.put(s4, "hux"); + + std::vector > > fetched2(ssm2.get_kv(s4)); + + std::cout << std::endl << "PRINTING RESULTS:" << std::endl; + for (size_t i = 0, S = fetched2.size(); i < S; ++i) { + std::cout << fetched2[i].first << std::endl; + } + + std::cout << "TRYING ON A SELECTOR-LIKE OBJECT" << std::endl; + + Subset_Map sel_ssm; + std::vector target; + target.push_back("desk"); + target.push_back(".wood"); + + std::vector actual; + actual.push_back("desk"); + actual.push_back(".wood"); + actual.push_back(".mine"); + + sel_ssm.put(target, "has-aquarium"); + std::vector > > fetched3(sel_ssm.get_kv(actual)); + std::cout << "RESULTS:" << std::endl; + for (size_t i = 0, S = fetched3.size(); i < S; ++i) { + std::cout << fetched3[i].first << std::endl; + } + + std::cout << std::endl; + + // BEGIN PORTED RUBY TESTS FROM /test/sass/util/subset_map_test.rb + + setup(); + testEqualKeys(); + testSubsetKeys(); + testSupersetKeys(); + testDisjointKeys(); + testSemiDisjointKeys(); + testEmptyKeySet(); + testEmptyKeyGet(); + testMultipleSubsets(); + testBracketBracket(); + testKeyOrder(); + + setup(); + testOrderPreserved(); + + setup(); + testMultipleEqualValues(); + + return 0; +} + +string toString(std::vector>> v) +{ + std::stringstream buffer; + buffer << "["; + for (size_t i = 0, S = v.size(); i < S; ++i) { + buffer << "[" << v[i].first; + buffer << ", Set["; + for (size_t j = 0, S = v[i].second.size(); j < S; ++j) { + buffer << v[i].second[j]; + if (j < S-1) { + buffer << ", "; + } + } + buffer << "]]"; + if (i < S-1) { + buffer << ", "; + } + } + buffer << "]"; + return buffer.str(); +} + +string toString(std::vector v) +{ + std::stringstream buffer; + buffer << "["; + for (size_t i = 0, S = v.size(); i < S; ++i) { + buffer << v[i]; + if (i < S-1) { + buffer << ", "; + } + } + buffer << "]"; + return buffer.str(); +} + +void assertEqual(string sExpected, string sResult) { + std::cout << "Expected: " << sExpected << std::endl; + std::cout << "Result: " << sResult << std::endl; + assert(sExpected == sResult); +} diff --git a/test/test_superselector.cpp b/test/test_superselector.cpp new file mode 100644 index 000000000..bf21c7c4d --- /dev/null +++ b/test/test_superselector.cpp @@ -0,0 +1,69 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include + +using namespace Sass; + +Context ctx = Context(Context::Data()); + +Compound_Selector* compound_selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } + +Complex_Selector* complex_selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_complex_selector(false); } + +void check_compound(std::string s1, std::string s2) +{ + std::cout << "Is " + << s1 + << " a superselector of " + << s2 + << "?\t" + << compound_selector(s1 + ";")->is_superselector_of(compound_selector(s2 + ";")) + << std::endl; +} + +void check_complex(std::string s1, std::string s2) +{ + std::cout << "Is " + << s1 + << " a superselector of " + << s2 + << "?\t" + << complex_selector(s1 + ";")->is_superselector_of(complex_selector(s2 + ";")) + << std::endl; +} + +int main() +{ + check_compound(".foo", ".foo.bar"); + check_compound(".foo.bar", ".foo"); + check_compound(".foo.bar", "div.foo"); + check_compound(".foo", "div.foo"); + check_compound("div.foo", ".foo"); + check_compound("div.foo", "div.bar.foo"); + check_compound("p.foo", "div.bar.foo"); + check_compound(".hux", ".mumble"); + + std::cout << std::endl; + + check_complex(".foo ~ .bar", ".foo + .bar"); + check_complex(".foo .bar", ".foo + .bar"); + check_complex(".foo .bar", ".foo > .bar"); + check_complex(".foo .bar > .hux", ".foo.a .bar.b > .hux"); + check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .hux"); + check_complex(".foo", ".bar .foo"); + check_complex(".foo", ".foo.a"); + check_complex(".foo.bar", ".foo"); + check_complex(".foo .bar .hux", ".bar .hux"); + check_complex(".foo ~ .bar .hux.x", ".foo.a + .bar.b > .hux.y"); + check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .mumble"); + check_complex(".foo + .bar", ".foo ~ .bar"); + check_complex("a c e", "a b c d e"); + check_complex("c a e", "a b c d e"); + + return 0; +} + + diff --git a/test/test_unification.cpp b/test/test_unification.cpp new file mode 100644 index 000000000..5c663ee90 --- /dev/null +++ b/test/test_unification.cpp @@ -0,0 +1,31 @@ +#include "../ast.hpp" +#include "../context.hpp" +#include "../parser.hpp" +#include + +using namespace Sass; + +Context ctx = Context(Context::Data()); + +Compound_Selector* selector(std::string src) +{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } + +void unify(std::string lhs, std::string rhs) +{ + Compound_Selector* unified = selector(lhs + ";")->unify_with(selector(rhs + ";"), ctx); + std::cout << lhs << " UNIFIED WITH " << rhs << " =\t" << (unified ? unified->to_string() : "NOTHING") << std::endl; +} + +int main() +{ + unify(".foo", ".foo.bar"); + unify("div:nth-of-type(odd)", "div:first-child"); + unify("div", "span:whatever"); + unify("div", "span"); + unify("foo:bar::after", "foo:bar::first-letter"); + unify(".foo#bar.hux", ".hux.foo#bar"); + unify(".foo#bar.hux", ".hux.foo#baz"); + unify("*:blah:fudge", "p:fudge:blah"); + + return 0; +} diff --git a/version.sh b/version.sh new file mode 100755 index 000000000..281de74d7 --- /dev/null +++ b/version.sh @@ -0,0 +1,10 @@ +if test "x$LIBSASS_VERSION" = "x"; then + LIBSASS_VERSION=`git describe --abbrev=4 --dirty --always --tags 2>/dev/null` +fi +if test "x$LIBSASS_VERSION" = "x"; then + LIBSASS_VERSION=`cat VERSION 2>/dev/null` +fi +if test "x$LIBSASS_VERSION" = "x"; then + LIBSASS_VERSION="[na]" +fi +echo $LIBSASS_VERSION diff --git a/win/libsass.sln b/win/libsass.sln new file mode 100644 index 000000000..2a55ad87e --- /dev/null +++ b/win/libsass.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsass", "libsass.vcxproj", "{E4030474-AFC9-4CC6-BEB6-D846F631502B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".SolutionItems", ".SolutionItems", "{33318C77-2391-4399-8118-C109155A4A75}" + ProjectSection(SolutionItems) = preProject + ..\.editorconfig = ..\.editorconfig + ..\.gitattributes = ..\.gitattributes + ..\.gitignore = ..\.gitignore + ..\.travis.yml = ..\.travis.yml + ..\appveyor.yml = ..\appveyor.yml + ..\Readme.md = ..\Readme.md + ..\res\resource.rc = ..\res\resource.rc + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|Win64 = Debug|Win64 + Release|Win32 = Release|Win32 + Release|Win64 = Release|Win64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.ActiveCfg = Debug|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.Build.0 = Debug|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.ActiveCfg = Debug|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.Build.0 = Debug|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.ActiveCfg = Release|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.Build.0 = Release|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.ActiveCfg = Release|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/win/libsass.sln.DotSettings b/win/libsass.sln.DotSettings new file mode 100644 index 000000000..405024e15 --- /dev/null +++ b/win/libsass.sln.DotSettings @@ -0,0 +1,9 @@ + + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded + ExplicitlyExcluded \ No newline at end of file diff --git a/win/libsass.targets b/win/libsass.targets new file mode 100644 index 000000000..c1c7d45f3 --- /dev/null +++ b/win/libsass.targets @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win/libsass.vcxproj b/win/libsass.vcxproj new file mode 100644 index 000000000..8cfd61f2a --- /dev/null +++ b/win/libsass.vcxproj @@ -0,0 +1,188 @@ + + + + [NA] + ..\src + ..\src + ..\include + + + + + + + + + + %(PreprocessorDefinitions);LIBSASS_VERSION="$(LIBSASS_VERSION)"; + + + + + + + + + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + {E4030474-AFC9-4CC6-BEB6-D846F631502B} + Win32Proj + libsass + + + libsass + Unicode + + + DynamicLibrary + ADD_EXPORTS;$(PreprocessorDefinitions); + + + StaticLibrary + + + v120 + + + v140 + + + true + + + true + + + false + true + + + false + true + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)bin\Debug\ + $(SolutionDir)bin\Debug\obj\ + + + true + $(SolutionDir)bin\Debug\ + $(SolutionDir)bin\Debug\obj\ + + + false + $(SolutionDir)bin\ + $(SolutionDir)bin\obj\ + + + false + $(SolutionDir)bin\ + $(SolutionDir)bin\obj\ + + + + ..\include;%(AdditionalIncludeDirectories) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + + + Console + true + true + true + + + + + + + diff --git a/win/libsass.vcxproj.filters b/win/libsass.vcxproj.filters new file mode 100644 index 000000000..980f00f3f --- /dev/null +++ b/win/libsass.vcxproj.filters @@ -0,0 +1,357 @@ + + + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {bb9c270d-e9f5-49bf-afda-771a1a4bb5b7} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + Include Headers + + + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Source Files + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + From a6aab24737fcc99122f980171c0fa08a6e8a4a46 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Nov 2018 16:36:45 +1100 Subject: [PATCH 174/286] Remove call to removed sass_option_push_import_extension --- src/binding.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/binding.cpp b/src/binding.cpp index cb9ed132c..c8376e982 100644 --- a/src/binding.cpp +++ b/src/binding.cpp @@ -113,7 +113,6 @@ int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapp sass_option_set_precision(sass_options, Nan::To(Nan::Get(options, Nan::New("precision").ToLocalChecked()).ToLocalChecked()).FromJust()); sass_option_set_indent(sass_options, ctx_w->indent); sass_option_set_linefeed(sass_options, ctx_w->linefeed); - sass_option_push_import_extension(sass_options, ".css"); v8::Local importer_callback = Nan::Get(options, Nan::New("importer").ToLocalChecked()).ToLocalChecked(); @@ -303,7 +302,7 @@ NAN_METHOD(render_sync) { } sass_free_context_wrapper(ctx_w); - + info.GetReturnValue().Set(result == 0); } From ea9ffd68dbc8b6e8ec977e7bbf9b6a08ae1c0b91 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Sun, 11 Nov 2018 16:50:11 +1100 Subject: [PATCH 175/286] Update sass-spec devDependency to use commit hash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ccc32e3a..d8a8fe5c2 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.4-1", + "sass-spec": "https://github.com/sass/sass-spec.git#dc2d573", "unique-temp-dir": "^1.0.0" } } From 8319be23bf9b56ef64c9341bff8c1f6f5f093060 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 14 Nov 2018 22:59:23 +1100 Subject: [PATCH 176/286] 4.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8a8fe5c2..fb011087b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.10.0", + "version": "4.11.0", "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", From dfe1f05aba0f681eb237bc146d8cf1716d1086c4 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 14 Nov 2018 23:00:04 +1100 Subject: [PATCH 177/286] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a96e5726..006dbb9df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v4.11.0 + +https://github.com/sass/node-sass/releases/tag/v4.11.0 + ## v4.10.0 https://github.com/sass/node-sass/releases/tag/v4.10.0 From 0bd48bbad6fccb0da16d3bdf76ad541f5f45ec70 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Sat, 1 Dec 2018 11:37:15 -0500 Subject: [PATCH 178/286] docs: Add a small version table for README Closes #2383 --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02b3f6879..6d8ff49a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # node-sass -#### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases) +#### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases). Below is a quick guide for minimium support: + +NodeJS | Minium node-sass version +--------|------------------------- +Node 11 | 4.10+ +Node 10 | 4.9+ +Node 8 | 4.5.3+ From 454bb8ea15341bd55c83beb9fac20840ea8843a1 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 5 Dec 2018 12:05:41 -0500 Subject: [PATCH 179/286] docs: Add Node Module version to release support --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6d8ff49a5..ef9655ad4 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ #### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases). Below is a quick guide for minimium support: -NodeJS | Minium node-sass version ---------|------------------------- -Node 11 | 4.10+ -Node 10 | 4.9+ -Node 8 | 4.5.3+ +NodeJS | Minium node-sass version | Node Module +--------|--------------------------|------------ +Node 11 | 4.10+ | 67 +Node 10 | 4.9+ | 64 +Node 8 | 4.5.3+ | 57
From 088b80b61936dd2a177618a928de48a7e79833d2 Mon Sep 17 00:00:00 2001 From: Adam Yeats Date: Fri, 4 Jan 2019 13:50:43 +0100 Subject: [PATCH 180/286] Remove @adamyeats from maintainers list --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ef9655ad4..4ed7c9135 100644 --- a/README.md +++ b/README.md @@ -609,7 +609,6 @@ This module is brought to you and maintained by the following people: * Keith Cirkel ([Github](https://github.com/keithamus) / [Twitter](https://twitter.com/Keithamus)) * Laurent Goderre ([Github](https://github.com/laurentgoderre) / [Twitter](https://twitter.com/laurentgoderre)) * Nick Schonning ([Github](https://github.com/nschonni) / [Twitter](https://twitter.com/nschonni)) -* Adam Yeats ([Github](https://github.com/adamyeats) / [Twitter](https://twitter.com/adamyeats)) * Adeel Mujahid ([Github](https://github.com/am11) / [Twitter](https://twitter.com/adeelbm)) ## Contributors From 44366b39e705e123cf77350e34aa414664c2d754 Mon Sep 17 00:00:00 2001 From: Cheesestringer <4462478+cheesestringer@users.noreply.github.com> Date: Mon, 21 Jan 2019 17:17:39 +1100 Subject: [PATCH 181/286] Update lodash and remove prototype vulnerabilities Fixes: https://github.com/sass/node-sass/issues/2574 by removing prototype vulnerabilities for: https://ossindex.sonatype.org/component/pkg:npm/lodash.assign https://ossindex.sonatype.org/component/pkg:npm/lodash.clonedeep https://ossindex.sonatype.org/component/pkg:npm/lodash.mergewith --- lib/index.js | 4 ++-- lib/watcher.js | 2 +- package.json | 4 +--- test/spec.js | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/index.js b/lib/index.js index fafbe55f2..1521ec503 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,8 +3,8 @@ */ var path = require('path'), - clonedeep = require('lodash.clonedeep'), - assign = require('lodash.assign'), + clonedeep = require('lodash/cloneDeep'), + assign = require('lodash/assign'), sass = require('./extensions'); /** diff --git a/lib/watcher.js b/lib/watcher.js index cdb965c53..89443b415 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -1,5 +1,5 @@ var grapher = require('sass-graph'), - clonedeep = require('lodash.clonedeep'), + clonedeep = require('lodash/cloneDeep'), path = require('path'), config = {}, watcher = {}, diff --git a/package.json b/package.json index fb011087b..23bc0502a 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,7 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash.assign": "^4.2.0", - "lodash.clonedeep": "^4.3.2", - "lodash.mergewith": "^4.6.0", + "lodash": "^4.17.11", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.10.0", diff --git a/test/spec.js b/test/spec.js index 0c3de2f40..1ccaa8da6 100644 --- a/test/spec.js +++ b/test/spec.js @@ -7,8 +7,8 @@ var assert = require('assert'), ? require('../lib-cov') : require('../lib'), readYaml = require('read-yaml'), - mergeWith = require('lodash.mergewith'), - assign = require('lodash.assign'), + mergeWith = require('lodash/mergeWith'), + assign = require('lodash/assign'), glob = require('glob'), specPath = require('sass-spec').dirname.replace(/\\/g, '/'), impl = 'libsass', From a2ac801e280c526f09b9c69ff2eb389a3e9314ca Mon Sep 17 00:00:00 2001 From: DerZyklop Date: Mon, 4 Feb 2019 12:56:45 +0100 Subject: [PATCH 182/286] removed outdated TOC entries --- TROUBLESHOOTING.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index ae20d3470..6326ddfe1 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -7,10 +7,6 @@ should always follow these steps before opening a new issue. - [Installation problems](#installation-problems) - [404 downloading binding.node file](#404s) - - [Assertion failed: (handle->flags & UV_CLOSING), function uv__finish_close](#assertion-failed-handle-flags-&-uv_closing-function-uv__finish_close) - - [Cannot find module '/root/<...>/install.js'](#cannot-find-module-rootinstalljs) - - [Linux](#linux) - - [npm 5](#npm-5) - [Glossary](#glossary) - [Which node runtime am I using?](#which-node-runtime-am-i-using) - [Which version of node am I using?](#which-version-of-node-am-i-using) From 33a32c37df560c3f730b487d0144b532c1be8451 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 4 Mar 2019 16:36:28 -0800 Subject: [PATCH 183/286] https-ify sass-lang.com urls (#2608) * https-ify sass-lang.com urls See https://github.com/sass/sass-site/issues/217 Committed via https://github.com/asottile/all-repos * Revert changes to vendored libsass --- CODE_OF_CONDUCT.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index dfc4c84a7..c4164af10 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -7,4 +7,4 @@ fair place to play. [The full community guidelines can be found on the Sass website.][link] -[link]: http://sass-lang.com/community-guidelines +[link]: https://sass-lang.com/community-guidelines diff --git a/README.md b/README.md index 4ed7c9135..f55f487bd 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ An array of paths that [LibSass] can look in to attempt to resolve your `@import * Type: `Boolean` * Default: `false` -`true` values enable [Sass Indented Syntax](http://sass-lang.com/documentation/file.INDENTED_SYNTAX.html) for parsing the data string or file. +`true` values enable [Sass Indented Syntax](https://sass-lang.com/documentation/file.INDENTED_SYNTAX.html) for parsing the data string or file. __Note:__ node-sass/libsass will compile a mixed library of scss and indented syntax (.sass) files with the Default setting (false) as long as .sass and .scss extensions are used in filenames. From 0f86a0ae61515c15a97803516ede53fee7d5292d Mon Sep 17 00:00:00 2001 From: Nico385412 Date: Tue, 19 Mar 2019 11:37:29 +0100 Subject: [PATCH 184/286] Update README.md (#2617) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f55f487bd..25fa995a0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ #### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases). Below is a quick guide for minimium support: -NodeJS | Minium node-sass version | Node Module +NodeJS | Minimum node-sass version | Node Module --------|--------------------------|------------ Node 11 | 4.10+ | 67 Node 10 | 4.9+ | 64 From da10866c45d2c65ece1bed260af3e99423aead34 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 24 Apr 2019 07:17:14 +1000 Subject: [PATCH 185/286] Add support for Node 12 --- .travis.yml | 6 ++++++ CHANGELOG.md | 4 ++++ README.md | 1 + appveyor.yml | 8 +++++++- lib/extensions.js | 1 + src/create_string.cpp | 4 ++-- 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c7acd20b..303cd892a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,12 @@ jobs: - stage: platform-test node_js: "node" os: osx + - stage: platform-test + node_js: "11" + os: linux + - stage: platform-test + node_js: "11" + os: osx - stage: platform-test node_js: "10" os: linux diff --git a/CHANGELOG.md b/CHANGELOG.md index 006dbb9df..4590a801b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v4.12.0 + +https://github.com/sass/node-sass/releases/tag/v4.12.0 + ## v4.11.0 https://github.com/sass/node-sass/releases/tag/v4.11.0 diff --git a/README.md b/README.md index 25fa995a0..4aa1cba48 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ NodeJS | Minimum node-sass version | Node Module --------|--------------------------|------------ +Node 12 | 4.12+ | 72 Node 11 | 4.10+ | 67 Node 10 | 4.9+ | 64 Node 8 | 4.5.3+ | 57 diff --git a/appveyor.yml b/appveyor.yml index ed7c2f8ff..86e0ae2c3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -73,6 +73,9 @@ - nodejs_version: 11 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - nodejs_version: 12 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 install: - ps: Install-Product node $env:nodejs_version $env:platform @@ -166,7 +169,10 @@ - nodejs_version: 11 GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - + - nodejs_version: 12 + GYP_MSVS_VERSION: 2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + install: - ps: Install-Product node $env:nodejs_version $env:platform - node --version diff --git a/lib/extensions.js b/lib/extensions.js index 47ada7166..8d365b45d 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -77,6 +77,7 @@ function getHumanNodeVersion(abi) { case 59: return 'Node.js 9.x'; case 64: return 'Node.js 10.x'; case 67: return 'Node.js 11.x'; + case 72: return 'Node.js 12.x'; default: return false; } } diff --git a/src/create_string.cpp b/src/create_string.cpp index 1b35b125f..27a496f7a 100644 --- a/src/create_string.cpp +++ b/src/create_string.cpp @@ -5,7 +5,7 @@ char* create_string(Nan::MaybeLocal maybevalue) { v8::Local value; - + if (maybevalue.ToLocal(&value)) { if (value->IsNull() || !value->IsString()) { return 0; @@ -14,7 +14,7 @@ char* create_string(Nan::MaybeLocal maybevalue) { return 0; } - v8::String::Utf8Value string(value); + Nan::Utf8String string(value); char *str = (char *)malloc(string.length() + 1); strcpy(str, *string); return str; From 34f99a20d529434ce98a2cf9627cd804bb3b7427 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 25 Apr 2019 12:11:07 +1000 Subject: [PATCH 186/286] Fix OSX compilation for Mojavi --- binding.gyp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/binding.gyp b/binding.gyp index f4507e6b8..8b3415293 100644 --- a/binding.gyp +++ b/binding.gyp @@ -28,9 +28,8 @@ } }, 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS': [ - '-std=c++11' - ], + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', + 'CLANG_CXX_LIBRARY': 'libc++', 'OTHER_LDFLAGS': [], 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', 'MACOSX_DEPLOYMENT_TARGET': '10.7' From f4eebc85f96fa54e306ad2c799ba7ca727cdf1da Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 25 Apr 2019 12:16:38 +1000 Subject: [PATCH 187/286] Node 12 requires at least nan@2.13.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23bc0502a..94d889271 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "lodash": "^4.17.11", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.10.0", + "nan": "^2.13.2", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", From 480250c1624551b5ec38a00b89c8cb34dcc175d7 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 25 Apr 2019 12:23:38 +1000 Subject: [PATCH 188/286] Use g++4.9 for all Node >= 10 in Travis CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 303cd892a..c720ad811 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,7 +80,7 @@ before_install: - echo $TRAVIS_NODE_VERSION - npm config set python `which python` - if [ $TRAVIS_OS_NAME == "linux" ]; then - if [[ $(node -v) =~ v1[01] ]]; then + if [[ $(node -v) =~ v[1-9][0-9] ]]; then export CC="gcc-4.9"; export CXX="g++-4.9"; export LINK="gcc-4.9"; From 5aae0d235ec2d9823e9d7ae3c07ea06c80ca56f6 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 25 Apr 2019 12:36:16 +1000 Subject: [PATCH 189/286] Use Visual Studio 2017 for Node >= 10 --- appveyor.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 86e0ae2c3..8edbcd7c0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -68,14 +68,14 @@ GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - nodejs_version: 10 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - nodejs_version: 11 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - nodejs_version: 12 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: - ps: Install-Product node $env:nodejs_version $env:platform @@ -164,14 +164,14 @@ GYP_MSVS_VERSION: 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - nodejs_version: 10 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - nodejs_version: 11 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - nodejs_version: 12 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: - ps: Install-Product node $env:nodejs_version $env:platform From 9e564a170e38826247bbf02b7a44d04f6cb094ea Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 25 Apr 2019 12:50:29 +1000 Subject: [PATCH 190/286] Use explicit Node version numbers in CI config --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index c720ad811..220f34050 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,28 +36,28 @@ jobs: node_js: "9" os: osx - stage: platform-test - node_js: "7" + node_js: "8" os: linux - stage: platform-test - node_js: "7" + node_js: "8" os: osx - stage: platform-test - node_js: "lts/carbon" + node_js: "7" os: linux - stage: platform-test - node_js: "lts/carbon" + node_js: "7" os: osx - stage: platform-test - node_js: "lts/boron" + node_js: "6" os: linux - stage: platform-test - node_js: "lts/boron" + node_js: "6" os: osx - stage: platform-test - node_js: "lts/argon" + node_js: "4" os: linux - stage: platform-test - node_js: "lts/argon" + node_js: "4" os: osx - stage: platform-test node_js: "0.12" From 12c0052200260d72a1b5d5cac3a9b6c28b333e61 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 26 Apr 2019 14:04:18 +1000 Subject: [PATCH 191/286] Workaround waiting for AppVeyor to add Node versions --- appveyor.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 8edbcd7c0..bfd75922a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -78,7 +78,8 @@ APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: - - ps: Install-Product node $env:nodejs_version $env:platform + # https://www.appveyor.com/docs/lang/nodejs-iojs/#installing-any-version-of-nodejs-or-iojs + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform - node --version - npm --version - npm install @@ -174,7 +175,8 @@ APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: - - ps: Install-Product node $env:nodejs_version $env:platform + # https://www.appveyor.com/docs/lang/nodejs-iojs/#installing-any-version-of-nodejs-or-iojs + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform - node --version - npm --version - npm install From 23c86596a9bbcf1e72c6871e3a7cf084d7b68496 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Fri, 26 Apr 2019 20:18:21 +1000 Subject: [PATCH 192/286] 4.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94d889271..2cb30c930 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-sass", - "version": "4.11.0", + "version": "4.12.0", "libsass": "3.5.4", "description": "Wrapper around libsass", "license": "MIT", From e59f5ba248af14d1850b8e9e0ac48c62a14988af Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Sun, 28 Apr 2019 15:12:38 -0400 Subject: [PATCH 193/286] chore: Change note about Node 12 support --- .github/ISSUE_TEMPLATE/Bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index cae82da69..16ff60358 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -8,7 +8,7 @@ about: If you're having an issue with installing node-sass or the compiled resul From 15355dd3d287e3e2715163d9339924060b80c1fb Mon Sep 17 00:00:00 2001 From: abetomo Date: Tue, 21 May 2019 09:58:25 +0900 Subject: [PATCH 195/286] Remove sudo settings from .travis.yml 'sudo' seems to be unavailable. Remove sudo settings from .travis.yml https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 220f34050..8ad4bf424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js compiler: gcc -sudo: false env: global: From 2513e6aaea245935ca4207f43ee3d74bf2ba6cf4 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 30 May 2019 19:45:36 -0400 Subject: [PATCH 196/286] chore: Remove PR template Warning about node-gyp isn't needed anymore --- .github/PULL_REQUEST_TEMPLATE.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 8555b0ec5..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,4 +0,0 @@ - From 8421979fe9506ce6556cb10f18e04d70ad885d8e Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 30 Aug 2019 15:38:56 +0300 Subject: [PATCH 197/286] Assorted typo fixes. --- README.md | 2 +- lib/extensions.js | 2 +- test/watcher.js | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4aa1cba48..08b276786 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # node-sass -#### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases). Below is a quick guide for minimium support: +#### Supported Node.js versions vary by release, please consult the [releases page](https://github.com/sass/node-sass/releases). Below is a quick guide for minimum support: NodeJS | Minimum node-sass version | Node Module --------|--------------------------|------------ diff --git a/lib/extensions.js b/lib/extensions.js index 8d365b45d..d2f1fe879 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -214,7 +214,7 @@ function getBinaryName() { * By default fetch from the node-sass distribution * site on GitHub. * - * The default URL can be overriden using + * The default URL can be overridden using * the environment variable SASS_BINARY_SITE, * .npmrc variable sass_binary_site or * or a command line option --sass-binary-site: diff --git a/test/watcher.js b/test/watcher.js index dca632fad..c96d39875 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -35,7 +35,7 @@ describe('watcher', function() { ]); }); - it('should record its decendants as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.changed(file); assert.deepEqual(files.added, [ @@ -59,7 +59,7 @@ describe('watcher', function() { ]); }); - it('should record its decendants as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'one.scss'); var files = watcher.changed(file); assert.deepEqual(files.added, [ @@ -112,7 +112,7 @@ describe('watcher', function() { assert.deepEqual(files.added, []); }); - it('should record its decendants as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.added(file); assert.deepEqual(files.added, [ @@ -140,7 +140,7 @@ describe('watcher', function() { assert.deepEqual(files.added, []); }); - it('should record its decendants as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'one.scss'); var files = watcher.added(file); assert.deepEqual(files.added, [ @@ -248,7 +248,7 @@ describe('watcher', function() { describe('when a file is changed', function() { describe('and it is in the graph', function() { describe('if it is a partial', function() { - it('should record its decendents as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.changed(file); assert.deepEqual(files.added, [ @@ -272,7 +272,7 @@ describe('watcher', function() { }); describe('if it is not a partial', function() { - it('should record its decendents as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'one.scss'); var files = watcher.changed(file); assert.deepEqual(files.added, [ @@ -338,7 +338,7 @@ describe('watcher', function() { assert.deepEqual(files.added, []); }); - it('should record its decendants as added', function() { + it('should record its descendants as added', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.added(file); assert.deepEqual(files.added, [ @@ -376,7 +376,7 @@ describe('watcher', function() { ]); }); - it('should not record its decendants as added', function() { + it('should not record its descendants as added', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.added(file); assert.deepEqual(files.added, [ From b1f54d7667d2545b3f689549125a8c123d5d7a4e Mon Sep 17 00:00:00 2001 From: Kevin Kessenich Date: Fri, 6 Sep 2019 10:53:47 +0200 Subject: [PATCH 198/286] Fix #2614 - Update lodash version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2cb30c930..707dca1ac 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.13.2", From b0d4d853f7d4f49ee13d655b9e78279ce3346fb6 Mon Sep 17 00:00:00 2001 From: Griffen Schwiesow Date: Wed, 16 Oct 2019 11:20:27 -0700 Subject: [PATCH 199/286] Fix broken link to NodeJS docs in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08b276786..0e035bed5 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Follow @nodesass on twitter for release updates: npm install node-sass ``` -Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager) to install NodeJS so that `#!/usr/bin/env node` correctly resolves. +Some users have reported issues installing on Ubuntu due to `node` being registered to another package. [Follow the official NodeJS docs](https://github.com/nodesource/distributions/blob/master/README.md#debinstall) to install NodeJS so that `#!/usr/bin/env node` correctly resolves. Compiling on Windows machines requires the [node-gyp prerequisites](https://github.com/nodejs/node-gyp#on-windows). From 8498f7092cbd2a8caa8b9284e775a59e3a091d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Cie=C5=9Blak?= Date: Thu, 17 Oct 2019 18:52:33 +0000 Subject: [PATCH 200/286] Fix #2394: sourceMap option should have consistent behaviour render() and renderSync() should return "map" property in the results only if source map has been enabled. --- README.md | 6 ++++-- lib/index.js | 4 +++- test/api.js | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e035bed5..c50097b1f 100644 --- a/README.md +++ b/README.md @@ -310,9 +310,11 @@ Used to determine how many digits after the decimal will be allowed. For instanc * Type: `Boolean | String | undefined` * Default: `undefined` -**Special:** Setting the `sourceMap` option requires also setting the `outFile` option +Enables source map generation during `render` and `renderSync`. -Enables the outputting of a source map during `render` and `renderSync`. When `sourceMap === true`, the value of `outFile` is used as the target output location for the source map. When `typeof sourceMap === "string"`, the value of `sourceMap` will be used as the writing location for the file. +When `sourceMap === true`, the value of `outFile` is used as the target output location for the source map with the suffix `.map` appended. If no `outFile` is set, `sourceMap` parameter is ignored. + +When `typeof sourceMap === "string"`, the value of `sourceMap` will be used as the writing location for the file. ### sourceMapContents diff --git a/lib/index.js b/lib/index.js index 1521ec503..3f20708f1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -300,9 +300,11 @@ module.exports.render = function(opts, cb) { var stats = endStats(result.stats); var payload = { css: result.css, - map: result.map, stats: stats }; + if (result.map) { + payload.map = result.map; + } if (cb) { options.context.callback.call(options.context, null, payload); diff --git a/test/api.js b/test/api.js index a26254ad3..5db31137d 100644 --- a/test/api.js +++ b/test/api.js @@ -63,6 +63,26 @@ describe('api', function() { }); }); + it('should not generate source map when not requested', function(done) { + sass.render({ + file: fixture('simple/index.scss'), + sourceMap: false + }, function(error, result) { + assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + done(); + }); + }); + + it('should not generate source map without outFile and no explicit path given', function(done) { + sass.render({ + file: fixture('simple/index.scss'), + sourceMap: true + }, function(error, result) { + assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + done(); + }); + }); + it('should compile generate map with sourceMapRoot pass-through option', function(done) { sass.render({ file: fixture('simple/index.scss'), @@ -1348,6 +1368,26 @@ describe('api', function() { done(); }); + it('should not generate source map when not requested', function(done) { + var result = sass.renderSync({ + file: fixture('simple/index.scss'), + sourceMap: false + }); + + assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + done(); + }); + + it('should not generate source map without outFile and no explicit path given', function(done) { + var result = sass.renderSync({ + file: fixture('simple/index.scss'), + sourceMap: true + }); + + assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + done(); + }); + it('should compile generate map with sourceMapRoot pass-through option', function(done) { var result = sass.renderSync({ file: fixture('simple/index.scss'), From 64b6f32b7b9772e24300e1f459892f8a9046404c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Cie=C5=9Blak?= Date: Tue, 22 Oct 2019 18:19:38 +0000 Subject: [PATCH 201/286] Node 13 support --- .travis.yml | 19 ++++++++++++++++--- README.md | 1 + appveyor.yml | 6 ++++++ lib/extensions.js | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ad4bf424..d2a3dadc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,12 @@ jobs: - stage: platform-test node_js: "node" os: osx + - stage: platform-test + node_js: "12" + os: linux + - stage: platform-test + node_js: "12" + os: osx - stage: platform-test node_js: "11" os: linux @@ -74,12 +80,19 @@ addons: - g++-4.7 - gcc-4.9 - g++-4.9 + - gcc-6 + - g++-6 before_install: - echo $TRAVIS_NODE_VERSION - npm config set python `which python` - if [ $TRAVIS_OS_NAME == "linux" ]; then - if [[ $(node -v) =~ v[1-9][0-9] ]]; then + if [[ $(node -v) =~ v13 ]]; then + export CC="gcc-6"; + export CXX="g++-6"; + export LINK="gcc-6"; + export LINKXX="g++-6"; + elif [[ $(node -v) =~ v[1-9][0-9] ]]; then export CC="gcc-4.9"; export CXX="g++-4.9"; export LINK="gcc-4.9"; @@ -94,8 +107,8 @@ before_install: - nvm --version - node --version - npm --version - - gcc --version - - g++ --version + - ${CC:-gcc} --version + - ${CXX:-g++} --version install: - npm install diff --git a/README.md b/README.md index 0e035bed5..c88dfa215 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ NodeJS | Minimum node-sass version | Node Module --------|--------------------------|------------ +Node 13 | (not yet release) | 79 Node 12 | 4.12+ | 72 Node 11 | 4.10+ | 67 Node 10 | 4.9+ | 64 diff --git a/appveyor.yml b/appveyor.yml index bfd75922a..077c65a3d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -76,6 +76,9 @@ - nodejs_version: 12 GYP_MSVS_VERSION: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - nodejs_version: 13 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: # https://www.appveyor.com/docs/lang/nodejs-iojs/#installing-any-version-of-nodejs-or-iojs @@ -173,6 +176,9 @@ - nodejs_version: 12 GYP_MSVS_VERSION: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - nodejs_version: 13 + GYP_MSVS_VERSION: 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: # https://www.appveyor.com/docs/lang/nodejs-iojs/#installing-any-version-of-nodejs-or-iojs diff --git a/lib/extensions.js b/lib/extensions.js index d2f1fe879..1e344c40e 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -78,6 +78,7 @@ function getHumanNodeVersion(abi) { case 64: return 'Node.js 10.x'; case 67: return 'Node.js 11.x'; case 72: return 'Node.js 12.x'; + case 79: return 'Node.js 13.x'; default: return false; } } From 3838eae74ff1d4d2d37b80b20b30f8a0f42f3e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Cie=C5=9Blak?= Date: Wed, 23 Oct 2019 10:46:52 +0000 Subject: [PATCH 202/286] Use GCC 6 for Node 12 binaries Node 12 is built using GCC 6 therefore it is mandatory that we use the same C++ runtime as the mothership. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2a3dadc0..1bfa408e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,12 +87,12 @@ before_install: - echo $TRAVIS_NODE_VERSION - npm config set python `which python` - if [ $TRAVIS_OS_NAME == "linux" ]; then - if [[ $(node -v) =~ v13 ]]; then + if [[ $(node -v) =~ v1[23] ]]; then export CC="gcc-6"; export CXX="g++-6"; export LINK="gcc-6"; export LINKXX="g++-6"; - elif [[ $(node -v) =~ v[1-9][0-9] ]]; then + elif [[ $(node -v) =~ v1[01] ]]; then export CC="gcc-4.9"; export CXX="g++-4.9"; export LINK="gcc-4.9"; From 0c8d308fef78f25fccebe6a6f35f76489e76df04 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 23 Oct 2019 23:52:27 +1100 Subject: [PATCH 203/286] Update references for v4.13 release --- .github/ISSUE_TEMPLATE/Bug_report.md | 2 +- CHANGELOG.md | 4 ++++ README.md | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 16ff60358..6e19f215c 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -8,7 +8,7 @@ about: If you're having an issue with installing node-sass or the compiled resul