Skip to content

Commit cbdfbc6

Browse files
committed
better compile time error reporting with line number leafo#14
1 parent c8d28f2 commit cbdfbc6

File tree

2 files changed

+48
-33
lines changed

2 files changed

+48
-33
lines changed

pscss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ $scss = new scssc();
5050
if (has("f")) {
5151
$scss->setFormatter($opts["f"]);
5252
}
53-
echo $scss->compile($data);
53+
echo $scss->compile($data, "STDIN");

scss.inc.php

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ protected function compileImport($rawPath, $out) {
505505

506506
// return a value to halt execution
507507
protected function compileChild($child, $out) {
508+
$this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
509+
508510
switch ($child[0]) {
509511
case "import":
510512
list(,$rawPath) = $child;
@@ -646,7 +648,7 @@ protected function compileChild($child, $out) {
646648
list(,$name, $argValues, $content) = $child;
647649
$mixin = $this->get(self::$namespaces["mixin"] . $name, false);
648650
if (!$mixin) {
649-
throw new Exception(sprintf('Undefined mixin "%s"', $name));
651+
$this->throwError("Undefined mixin $name");
650652
}
651653

652654
$callingScope = $this->env;
@@ -676,7 +678,7 @@ protected function compileChild($child, $out) {
676678
case "mixin_content":
677679
$content = $this->get(self::$namespaces["special"] . "content");
678680
if (is_null($content)) {
679-
throw new Exception("Unexpected @content inside of mixin");
681+
$this->throwError("Unexpected @content inside of mixin");
680682
}
681683

682684
$this->storeEnv = $content->scope;
@@ -694,7 +696,7 @@ protected function compileChild($child, $out) {
694696
fwrite(STDERR, "Line $line DEBUG: $value\n");
695697
break;
696698
default:
697-
throw new Exception("unknown child type: $child[0]");
699+
$this->throwError("unknown child type: $child[0]");
698700
}
699701
}
700702

@@ -765,7 +767,7 @@ protected function reduce($value, $inExp = false) {
765767
$left[0] == "number" && $right[0] == "number")
766768
{
767769
if ($opName == "mod" && $right[2] != "") {
768-
throw new Exception(sprintf('Cannot modulo by a number with units: %s%s.', $right[1], $right[2]));
770+
$this->throwError("Cannot modulo by a number with units: $right[1]$right[2].");
769771
}
770772

771773
$unitChange = true;
@@ -1011,12 +1013,12 @@ protected function op_color_color($op, $left, $right) {
10111013
break;
10121014
case '/':
10131015
if ($rval == 0) {
1014-
throw new Exception("color: Can't divide by zero");
1016+
$this->throwError("color: Can't divide by zero");
10151017
}
10161018
$out[] = $lval / $rval;
10171019
break;
10181020
default:
1019-
throw new Exception("color: unknow op $op");
1021+
$this->throwError("color: unknow op $op");
10201022
}
10211023
}
10221024

@@ -1152,7 +1154,7 @@ protected function compileValue($value) {
11521154
case "null":
11531155
return "null";
11541156
default:
1155-
throw new Exception("unknown value type: $type");
1157+
$this->throwError("unknown value type: $type");
11561158
}
11571159
}
11581160

@@ -1288,13 +1290,13 @@ protected function applyArguments($argDef, $argValues) {
12881290
foreach ((array) $argValues as $arg) {
12891291
if (!empty($arg[0])) {
12901292
if (!isset($args[$arg[0][1]])) {
1291-
throw new Exception(sprintf('Mixin or function doesn\'t have an argument named $%s.', $arg[0][1]));
1293+
$this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
12921294
} elseif ($args[$arg[0][1]][0] < count($remaining)) {
1293-
throw new Exception(sprintf('The argument $%s was passed both by position and by name.', $arg[0][1]));
1295+
$this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
12941296
}
12951297
$keywordArgs[$arg[0][1]] = $arg[1];
12961298
} elseif (count($keywordArgs)) {
1297-
throw new Exception('Positional arguments must come before keyword arguments.');
1299+
$this->throwError('Positional arguments must come before keyword arguments.');
12981300
} elseif ($arg[2] == true) {
12991301
$val = $this->reduce($arg[1], true);
13001302
if ($val[0] == "list") {
@@ -1323,7 +1325,7 @@ protected function applyArguments($argDef, $argValues) {
13231325
} elseif (!empty($default)) {
13241326
$val = $default;
13251327
} else {
1326-
throw new Exception(sprintf('There is missing argument $%s', $name));
1328+
$this->throwError("Missing argument $$name");
13271329
}
13281330

13291331
$this->set($name, $this->reduce($val, true), true);
@@ -1598,18 +1600,18 @@ protected function coerceString($value) {
15981600

15991601
protected function assertList($value) {
16001602
if ($value[0] != "list")
1601-
throw new Exception("expecting list");
1603+
$this->throwError("expecting list");
16021604
return $value;
16031605
}
16041606

16051607
protected function assertColor($value) {
16061608
if ($color = $this->coerceColor($value)) return $color;
1607-
throw new Exception("expecting color");
1609+
$this->throwError("expecting color");
16081610
}
16091611

16101612
protected function assertNumber($value) {
16111613
if ($value[0] != "number")
1612-
throw new Exception("expecting number");
1614+
$this->throwError("expecting number");
16131615
return $value[1];
16141616
}
16151617

@@ -2126,14 +2128,14 @@ protected function getNormalizedNumbers($args) {
21262128
$numbers = array();
21272129
foreach ($args as $key => $item) {
21282130
if ('number' != $item[0]) {
2129-
throw new Exception(sprintf('%s is not a number', $item[0]));
2131+
$this->throwError("%s is not a number", $item[0]);
21302132
}
21312133
$number = $this->normalizeNumber($item);
21322134

21332135
if (null === $unit) {
21342136
$unit = $number[2];
21352137
} elseif ($unit !== $number[2]) {
2136-
throw new Exception(sprintf('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]));
2138+
$this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]);
21372139
}
21382140

21392141
$originalUnit = $item[2];
@@ -2246,7 +2248,7 @@ protected function lib_unitless($args) {
22462248
protected function lib_comparable($args) {
22472249
list($number1, $number2) = $args;
22482250
if (!isset($number1[0]) || $number1[0] != "number" || !isset($number2[0]) || $number2[0] != "number") {
2249-
throw new Exception('Invalid argument(s) for "comparable"');
2251+
$this->throwError('Invalid argument(s) for "comparable"');
22502252
}
22512253

22522254
$number1 = $this->normalizeNumber($number1);
@@ -2255,6 +2257,18 @@ protected function lib_comparable($args) {
22552257
return $number1[2] == $number2[2] || $number1[2] == "" || $number2[2] == "";
22562258
}
22572259

2260+
protected function throwError($msg = null) {
2261+
if (func_num_args() > 1) {
2262+
$msg = call_user_func_array("sprintf", func_get_args());
2263+
}
2264+
2265+
if ($this->sourcePos >= 0 && isset($this->parser)) {
2266+
$this->parser->throwParseError($msg, $this->sourcePos);
2267+
}
2268+
2269+
throw new Exception($msg);
2270+
}
2271+
22582272
static protected $cssColors = array(
22592273
'aliceblue' => '240,248,255',
22602274
'antiquewhite' => '250,235,215',
@@ -2526,7 +2540,7 @@ protected function parseChunk() {
25262540
$include = $this->pushSpecialBlock("include");
25272541
$include->child = $child;
25282542
} else {
2529-
$this->append($child);
2543+
$this->append($child, $s);
25302544
}
25312545

25322546
return true;
@@ -2538,7 +2552,7 @@ protected function parseChunk() {
25382552
$this->valueList($importPath) &&
25392553
$this->end())
25402554
{
2541-
$this->append(array("import", $importPath));
2555+
$this->append(array("import", $importPath), $s);
25422556
return true;
25432557
} else {
25442558
$this->seek($s);
@@ -2548,7 +2562,7 @@ protected function parseChunk() {
25482562
$this->selectors($selector) &&
25492563
$this->end())
25502564
{
2551-
$this->append(array("extend", $selector));
2565+
$this->append(array("extend", $selector), $s);
25522566
return true;
25532567
} else {
25542568
$this->seek($s);
@@ -2568,7 +2582,7 @@ protected function parseChunk() {
25682582
}
25692583

25702584
if ($this->literal("@return") && $this->valueList($retVal) && $this->end()) {
2571-
$this->append(array("return", $retVal));
2585+
$this->append(array("return", $retVal), $s);
25722586
return true;
25732587
} else {
25742588
$this->seek($s);
@@ -2630,14 +2644,14 @@ protected function parseChunk() {
26302644
if (($this->literal("@debug") || $this->literal("@warn")) &&
26312645
$this->valueList($value) &&
26322646
$this->end()) {
2633-
$this->append(array("debug", $value, $s));
2647+
$this->append(array("debug", $value, $s), $s);
26342648
return true;
26352649
} else {
26362650
$this->seek($s);
26372651
}
26382652

26392653
if ($this->literal("@content") && $this->end()) {
2640-
$this->append(array("mixin_content"));
2654+
$this->append(array("mixin_content"), $s);
26412655
return true;
26422656
} else {
26432657
$this->seek($s);
@@ -2667,7 +2681,7 @@ protected function parseChunk() {
26672681
if ($this->literal("@charset") &&
26682682
$this->valueList($charset) && $this->end())
26692683
{
2670-
$this->append(array("charset", $charset));
2684+
$this->append(array("charset", $charset), $s);
26712685
return true;
26722686
} else {
26732687
$this->seek($s);
@@ -2696,7 +2710,7 @@ protected function parseChunk() {
26962710
$this->end())
26972711
{
26982712
$name = array("string", "", array($name));
2699-
$this->append(array("assign", $name, $value));
2713+
$this->append(array("assign", $name, $value), $s);
27002714
return true;
27012715
} else {
27022716
$this->seek($s);
@@ -2717,7 +2731,7 @@ protected function parseChunk() {
27172731
$defaultVar = true;
27182732
}
27192733
}
2720-
$this->append(array("assign", $name, $value, $defaultVar));
2734+
$this->append(array("assign", $name, $value, $defaultVar), $s);
27212735
return true;
27222736
} else {
27232737
$this->seek($s);
@@ -2744,7 +2758,7 @@ protected function parseChunk() {
27442758
if ($this->propertyName($name) && $this->literal(":")) {
27452759
$foundSomething = false;
27462760
if ($this->valueList($value)) {
2747-
$this->append(array("assign", $name, $value));
2761+
$this->append(array("assign", $name, $value), $s);
27482762
$foundSomething = true;
27492763
}
27502764

@@ -2772,10 +2786,10 @@ protected function parseChunk() {
27722786
$include = $block->child;
27732787
unset($block->child);
27742788
$include[3] = $block;
2775-
$this->append($include);
2789+
$this->append($include, $s);
27762790
} elseif (empty($block->dontAppend)) {
27772791
$type = isset($block->type) ? $block->type : "block";
2778-
$this->append(array($type, $block));
2792+
$this->append(array($type, $block), $s);
27792793
}
27802794
return true;
27812795
}
@@ -2839,7 +2853,8 @@ protected function popBlock() {
28392853
return $old;
28402854
}
28412855

2842-
protected function append($statement) {
2856+
protected function append($statement, $pos=null) {
2857+
if ($pos !== null) $statement[-1] = $pos;
28432858
$this->env->children[] = $statement;
28442859
}
28452860

@@ -3202,7 +3217,7 @@ protected function argumentDef(&$out) {
32023217
if ($this->literal("...")) {
32033218
$sss = $this->seek();
32043219
if (!$this->literal(")")) {
3205-
throw new Exception('... has to be after the final argument');
3220+
$this->throwParseError("... has to be after the final argument");
32063221
}
32073222
$arg[2] = true;
32083223
$this->seek($sss);
@@ -3720,7 +3735,7 @@ protected function to($what, &$out, $until = false, $allowNewline = false) {
37203735
return true;
37213736
}
37223737

3723-
protected function throwParseError($msg = "parse error", $count = null) {
3738+
public function throwParseError($msg = "parse error", $count = null) {
37243739
$count = is_null($count) ? $this->count : $count;
37253740

37263741
$line = $this->getLineNo($count);

0 commit comments

Comments
 (0)