Bladeren bron

Change syntax to … where … and …, solve remaining tasks

David Peter 1 jaar geleden
bovenliggende
commit
2e778bdafd

+ 1 - 1
assets/articles/index.js

@@ -54,7 +54,7 @@ ace.define(
           {
             token: "keyword",
             regex:
-              "\\b(?:per|to|let|fn|where|dimension|unit|use|long|short|both|none|print|assert|assert_eq|type|if|then|else|true|false)\\b",
+              "\\b(?:per|to|let|fn|where|and|dimension|unit|use|long|short|both|none|print|assert|assert_eq|type|if|then|else|true|false)\\b",
           },
           {
             token: "constant.numeric",

+ 1 - 1
assets/numbat.sublime-syntax

@@ -7,7 +7,7 @@ file_extensions:
 scope: source.nbt
 contexts:
   main:
-    - match: \b(per|to|let|fn|where|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\b
+    - match: \b(per|to|let|fn|where|and|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\b
       scope: keyword.control.nbt
     - match: '#(.*)'
       scope: comment.line.nbt

+ 1 - 1
assets/numbat.vim

@@ -5,7 +5,7 @@ if exists("b:current_syntax")
 endif
 
 " Numbat Keywords
-syn keyword numbatKeywords per to let fn where dimension unit use struct long short both none if then else true false NaN inf print assert assert_eq type
+syn keyword numbatKeywords per to let fn where and dimension unit use struct long short both none if then else true false NaN inf print assert assert_eq type
 highlight default link numbatKeywords Keyword
 
 " Physical dimensions (every capitalized word)

+ 1 - 1
book/numbat.js

@@ -4,7 +4,7 @@ hljs.registerLanguage('numbat', function(hljs) {
     aliases: ['nbt'],
     case_insensitive: false,
     keywords: {
-      keyword: 'per to let fn where dimension unit use struct long short both none if then else true false print assert assert_eq type',
+      keyword: 'per to let fn where and dimension unit use struct long short both none if then else true false print assert assert_eq type',
     },
     contains: [
       hljs.HASH_COMMENT_MODE,

+ 3 - 0
book/src/example-numbat_syntax.md

@@ -71,6 +71,9 @@ fn foo(z: Scalar) -> Scalar = 2 * z + 3                   # A simple function
 fn speed(len: Length, dur: Time) -> Velocity = len / dur  # Two parameters
 fn my_sqrt<T: Dim>(q: T^2) -> T = q^(1/2)                 # A generic function
 fn is_non_negative(x: Scalar) -> Bool = x ≥ 0             # Returns a bool
+fn power_4(x: Scalar) = z                                 # A function with local variables
+  where y = x * x
+    and z = y * y
 
 # 6. Dimension definitions
 

+ 1 - 1
examples/numbat_syntax.nbt

@@ -68,7 +68,7 @@ fn my_sqrt<T: Dim>(q: T^2) -> T = q^(1/2)                 # A generic function
 fn is_non_negative(x: Scalar) -> Bool = x ≥ 0             # Returns a bool
 fn power_4(x: Scalar) = z                                 # A function with local variables
   where y = x * x
-        z = y * y
+    and z = y * y
 
 # 6. Dimension definitions
 

+ 2 - 3
numbat/modules/core/strings.nbt

@@ -32,9 +32,8 @@ fn str_find(haystack: String, needle: String) -> Scalar =
       else if str_find(tail_haystack, needle) == -1
         then -1
         else 1 + str_find(tail_haystack, needle)
-  where
-    len_haystack = str_length(haystack)
-    tail_haystack = str_slice(haystack, 1, len_haystack)
+  where len_haystack = str_length(haystack)
+    and tail_haystack = str_slice(haystack, 1, len_haystack)
 
 @description("Check if a string contains a substring")
 fn str_contains(haystack: String, needle: String) -> Bool =

+ 2 - 3
numbat/modules/numerics/solve.nbt

@@ -12,9 +12,8 @@ fn root_bisect<X: Dim, Y: Dim>(f: Fn[(X) -> Y], x1: X, x2: X, x_tol: X, y_tol: Y
       else if f_x_mean × f(x1) < 0
         then root_bisect(f, x1, x_mean, x_tol, y_tol)
         else root_bisect(f, x_mean, x2, x_tol, y_tol)
-  where
-    x_mean = (x1 + x2) / 2
-    f_x_mean = f(x_mean)
+  where x_mean = (x1 + x2) / 2
+    and f_x_mean = f(x_mean)
 
 fn _root_newton_helper<X: Dim, Y: Dim>(f: Fn[(X) -> Y], f_prime: Fn[(X) -> Y / X], x0: X, y_tol: Y, max_iterations: Scalar) -> X =
   if max_iterations <= 0

+ 71 - 23
numbat/src/parser.rs

@@ -233,6 +233,9 @@ pub enum ParseErrorKind {
 
     #[error("Empty string interpolation")]
     EmptyStringInterpolation,
+
+    #[error("Expected local variable definition after where/and")]
+    ExpectedLocalVariableDefinition,
 }
 
 #[derive(Debug, Clone, Error)]
@@ -395,7 +398,7 @@ impl<'a> Parser<'a> {
         }
 
         if self.match_exact(TokenKind::Let).is_some() {
-            self.parse_variable().map(Statement::DefineVariable)
+            self.parse_variable(true).map(Statement::DefineVariable)
         } else if self.match_exact(TokenKind::Fn).is_some() {
             self.parse_function_declaration()
         } else if self.match_exact(TokenKind::Dimension).is_some() {
@@ -415,7 +418,7 @@ impl<'a> Parser<'a> {
         }
     }
 
-    fn parse_variable(&mut self) -> Result<DefineVariable> {
+    fn parse_variable(&mut self, flush_decorators: bool) -> Result<DefineVariable> {
         if let Some(identifier) = self.match_exact(TokenKind::Identifier) {
             let identifier_span = self.last().unwrap().span;
 
@@ -434,14 +437,16 @@ impl<'a> Parser<'a> {
                 self.skip_empty_lines();
                 let expr = self.expression()?;
 
-                if decorator::contains_aliases_with_prefixes(&self.decorator_stack) {
-                    return Err(ParseError {
-                        kind: ParseErrorKind::DecoratorsWithPrefixOnLetDefinition,
-                        span: self.peek().span,
-                    });
-                }
                 let mut decorators = vec![];
-                std::mem::swap(&mut decorators, &mut self.decorator_stack);
+                if flush_decorators {
+                    if decorator::contains_aliases_with_prefixes(&self.decorator_stack) {
+                        return Err(ParseError {
+                            kind: ParseErrorKind::DecoratorsWithPrefixOnLetDefinition,
+                            span: self.peek().span,
+                        });
+                    }
+                    std::mem::swap(&mut decorators, &mut self.decorator_stack);
+                }
 
                 Ok(DefineVariable {
                     identifier_span,
@@ -567,23 +572,37 @@ impl<'a> Parser<'a> {
                 self.skip_empty_lines();
                 let body = self.expression()?;
 
-                // After parsing the `where` statement we have eaten all
-                // the newlines. We're not supposed to do that.
-                // We'll resume the parser to where we were at before doing that.
-                let mut step_back_to = self.current;
-
                 let mut local_variables = Vec::new();
-                self.skip_empty_lines();
-                if self.match_exact(TokenKind::Where).is_some() {
-                    step_back_to = self.current;
+
+                if self
+                    .match_exact_beyond_linebreaks(TokenKind::Where)
+                    .is_some()
+                {
+                    let keyword_span = self.last().unwrap().span;
                     self.skip_empty_lines();
-                    while let Ok(local_variable) = self.parse_variable() {
+                    if let Ok(local_variable) = self.parse_variable(false) {
                         local_variables.push(local_variable);
-                        step_back_to = self.current;
+                    } else {
+                        return Err(ParseError {
+                            kind: ParseErrorKind::ExpectedLocalVariableDefinition,
+                            span: keyword_span,
+                        });
+                    }
+
+                    while self.match_exact_beyond_linebreaks(TokenKind::And).is_some() {
+                        let keyword_span = self.last().unwrap().span;
                         self.skip_empty_lines();
+                        if let Ok(local_variable) = self.parse_variable(false) {
+                            local_variables.push(local_variable);
+                        } else {
+                            return Err(ParseError {
+                                kind: ParseErrorKind::ExpectedLocalVariableDefinition,
+                                span: keyword_span,
+                            });
+                        }
                     }
                 }
-                self.current = step_back_to;
+
                 (Some(body), local_variables)
             };
 
@@ -1779,6 +1798,26 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn look_ahead_beyond_linebreak(&self, token_kind: TokenKind) -> bool {
+        let mut i = self.current;
+        while i < self.tokens.len() {
+            if self.tokens[i].kind != TokenKind::Newline {
+                return self.tokens[i].kind == token_kind;
+            }
+            i += 1;
+        }
+        false
+    }
+
+    /// Same as 'match_exact', but skips empty lines before matching. Note that this
+    /// does *not* skip empty lines in case there is no match.
+    fn match_exact_beyond_linebreaks(&mut self, token_kind: TokenKind) -> Option<&'a Token> {
+        if self.look_ahead_beyond_linebreak(token_kind) {
+            self.skip_empty_lines();
+        }
+        self.match_exact(token_kind)
+    }
+
     fn match_any(&mut self, kinds: &[TokenKind]) -> Option<&'a Token> {
         for kind in kinds {
             if let result @ Some(..) = self.match_exact(*kind) {
@@ -2716,9 +2755,8 @@ mod tests {
 
         parse_as(
             &["fn kefirausaure(x) = z + y
-                where
-                    y = x + x
-                    z = y + x"],
+                 where y = x + x
+                   and z = y + x"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
                 function_name: "kefirausaure".into(),
@@ -2746,6 +2784,16 @@ mod tests {
             },
         );
 
+        should_fail_with(
+            &["fn f(x) = x where"],
+            ParseErrorKind::ExpectedLocalVariableDefinition,
+        );
+
+        should_fail_with(
+            &["fn f(x) = x where z = 1 and"],
+            ParseErrorKind::ExpectedLocalVariableDefinition,
+        );
+
         should_fail_with(
             &["@aliases(foo) fn foobar(a: Scalar) -> Scalar"],
             ParseErrorKind::AliasUsedOnFunction,

+ 2 - 0
numbat/src/tokenizer.rs

@@ -90,6 +90,7 @@ pub enum TokenKind {
     Let,
     Fn, // 'fn'
     Where,
+    And,
     Dimension,
     Unit,
     Use,
@@ -376,6 +377,7 @@ impl Tokenizer {
             m.insert("let", TokenKind::Let);
             m.insert("fn", TokenKind::Fn);
             m.insert("where", TokenKind::Where);
+            m.insert("and", TokenKind::And);
             m.insert("dimension", TokenKind::Dimension);
             m.insert("unit", TokenKind::Unit);
             m.insert("use", TokenKind::Use);

+ 3 - 3
numbat/src/typechecker/tests/type_checking.rs

@@ -194,9 +194,9 @@ fn recursive_functions() {
 fn function_definitions_with_local_variables() {
     assert_successful_typecheck("fn f(x: A) -> C = x * y where y: B = b");
     assert_successful_typecheck(
-        "fn f(x: A) -> C = y * z where
-                                    y = x * 2
-                                    z = b * 2",
+        "fn f(x: A) -> C = y * z
+           where y = x * 2
+             and z = b * 2",
     );
     assert!(matches!(
         get_typecheck_error("fn f(x: A) = y where y = x + b"),

+ 12 - 2
numbat/src/typed_ast.rs

@@ -947,8 +947,9 @@ impl PrettyPrint for Statement {
                 ));
 
                 let mut pretty_local_variables = None;
+                let mut first = true;
                 if !local_variables.is_empty() {
-                    let mut plv = m::keyword("where");
+                    let mut plv = m::empty();
                     for DefineVariable(
                         identifier,
                         _decs,
@@ -958,7 +959,16 @@ impl PrettyPrint for Statement {
                         readable_type,
                     ) in local_variables
                     {
-                        plv += m::space()
+                        let introducer_keyword = if first {
+                            first = false;
+                            m::space() + m::space() + m::keyword("where")
+                        } else {
+                            m::space() + m::space() + m::space() + m::space() + m::keyword("and")
+                        };
+
+                        plv += m::nl()
+                            + introducer_keyword
+                            + m::space()
                             + m::identifier(identifier)
                             + m::operator(":")
                             + m::space()

+ 1 - 1
vscode-extension/syntaxes/numbat.tmLanguage.json

@@ -32,7 +32,7 @@
             "patterns": [
                 {
                     "name": "keyword.control.numbat",
-                    "match": "\\b(per|to|let|fn|where|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\\b"
+                    "match": "\\b(per|to|let|fn|where|and|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\\b"
                 }
             ]
         },