Explorar el Código

Add new 'type' procedure to print expr type

closes #119
David Peter hace 2 años
padre
commit
fe119c0f4f

+ 1 - 1
assets/numbat.sublime-syntax

@@ -7,7 +7,7 @@ file_extensions:
 scope: source.nbt
 contexts:
   main:
-    - match: \b(per|to|let|fn|dimension|unit|use|long|short|both|none|print|assert_eq)\b
+    - match: \b(per|to|let|fn|dimension|unit|use|long|short|both|none|print|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 dimension unit use long short both none print assert_eq
+syn keyword numbatKeywords per to let fn dimension unit use long short both none print 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 dimension unit use long short both none print assert_eq',
+      keyword: 'per to let fn dimension unit use long short both none print assert_eq type',
     },
     contains: [
       hljs.HASH_COMMENT_MODE,

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

@@ -89,4 +89,5 @@ unit thing                       # New base unit with automatically generated
 
 print(2 kilowarhol)              # Print a quantity
 assert_eq(1 ft, 12 in)           # Assert that two quantities are equal
+type(2 m/s)                      # Print the type of an expression
 ```

+ 1 - 0
examples/numbat_syntax.nbt

@@ -84,3 +84,4 @@ unit thing                       # New base unit with automatically generated
 
 print(2 kilowarhol)              # Print a quantity
 assert_eq(1 ft, 12 in)           # Assert that two quantities are equal
+type(2 m/s)                      # Print the type of an expression

+ 1 - 0
numbat-cli/src/keywords.rs

@@ -19,4 +19,5 @@ pub const KEYWORDS: &[&str] = &[
     // procedures
     "print(",
     "assert_eq(",
+    "type(",
 ];

+ 7 - 0
numbat-cli/tests/integration.rs

@@ -35,6 +35,13 @@ fn pass_expression_on_command_line() {
         .assert()
         .failure()
         .stderr(predicates::str::contains("runtime error"));
+
+    numbat()
+        .arg("--expression")
+        .arg("type(2 m/s)")
+        .assert()
+        .success()
+        .stdout(predicates::str::contains("Length / Time"));
 }
 
 #[test]

+ 2 - 0
numbat/src/ast.rs

@@ -399,6 +399,7 @@ impl PrettyPrint for DimensionExpression {
 pub enum ProcedureKind {
     Print,
     AssertEq,
+    Type,
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -624,6 +625,7 @@ impl PrettyPrint for Statement {
                 let identifier = match kind {
                     ProcedureKind::Print => "print",
                     ProcedureKind::AssertEq => "assert_eq",
+                    ProcedureKind::Type => "type",
                 };
                 m::identifier(identifier)
                     + m::operator("(")

+ 9 - 0
numbat/src/bytecode_interpreter.rs

@@ -1,5 +1,6 @@
 use std::collections::HashMap;
 
+use crate::ast::ProcedureKind;
 use crate::interpreter::{Interpreter, InterpreterResult, Result, RuntimeError};
 use crate::prefix::Prefix;
 use crate::typed_ast::{BinaryOperator, Expression, Statement, UnaryOperator};
@@ -187,6 +188,14 @@ impl BytecodeInterpreter {
                         .insert(name.into(), constant_idx);
                 }
             }
+            Statement::ProcedureCall(ProcedureKind::Type, args) => {
+                assert_eq!(args.len(), 1);
+                let arg = &args[0];
+                let type_str = format!("{}", arg.get_type());
+
+                let idx = self.vm.add_string(type_str);
+                self.vm.add_op1(Op::PrintString, idx);
+            }
             Statement::ProcedureCall(kind, args) => {
                 // Put all arguments on top of the stack
                 for arg in args {

+ 1 - 0
numbat/src/ffi.rs

@@ -44,6 +44,7 @@ pub(crate) fn procedures() -> &'static HashMap<ProcedureKind, ForeignFunction> {
                 callable: Callable::Procedure(assert_eq),
             },
         );
+        // Note: The 'type' procedure is missing here because it has special handling code in the compiler
 
         m
     })

+ 6 - 1
numbat/src/parser.rs

@@ -568,13 +568,18 @@ impl<'a> Parser<'a> {
                 })
             }
         } else if self
-            .match_any(&[TokenKind::ProcedurePrint, TokenKind::ProcedureAssertEq])
+            .match_any(&[
+                TokenKind::ProcedurePrint,
+                TokenKind::ProcedureAssertEq,
+                TokenKind::ProcedureType,
+            ])
             .is_some()
         {
             let span = self.last().unwrap().span;
             let procedure_kind = match self.last().unwrap().kind {
                 TokenKind::ProcedurePrint => ProcedureKind::Print,
                 TokenKind::ProcedureAssertEq => ProcedureKind::AssertEq,
+                TokenKind::ProcedureType => ProcedureKind::Type,
                 _ => unreachable!(),
             };
 

+ 2 - 0
numbat/src/tokenizer.rs

@@ -81,6 +81,7 @@ pub enum TokenKind {
     // Procedure calls
     ProcedurePrint,
     ProcedureAssertEq,
+    ProcedureType,
 
     // Variable-length tokens
     Number,
@@ -278,6 +279,7 @@ impl Tokenizer {
             m.insert("none", TokenKind::None);
             m.insert("print", TokenKind::ProcedurePrint);
             m.insert("assert_eq", TokenKind::ProcedureAssertEq);
+            m.insert("type", TokenKind::ProcedureType);
             m
         });
 

+ 23 - 2
numbat/src/typechecker.rs

@@ -4,13 +4,16 @@ use std::{
     fmt,
 };
 
-use crate::arithmetic::{pretty_exponent, Exponent, Power, Rational};
 use crate::dimension::DimensionRegistry;
 use crate::ffi::ArityRange;
 use crate::name_resolution::LAST_RESULT_IDENTIFIERS;
 use crate::registry::{BaseRepresentation, BaseRepresentationFactor, RegistryError};
 use crate::span::Span;
 use crate::typed_ast::{self, Type};
+use crate::{
+    arithmetic::{pretty_exponent, Exponent, Power, Rational},
+    ast::ProcedureKind,
+};
 use crate::{ast, decorator, ffi, suggestion};
 
 use ast::DimensionExpression;
@@ -924,6 +927,24 @@ impl TypeChecker {
                 }
                 typed_ast::Statement::DefineDimension(name.clone())
             }
+            ast::Statement::ProcedureCall(span, kind @ ProcedureKind::Type, args) => {
+                if args.len() != 1 {
+                    return Err(TypeCheckError::WrongArity {
+                        callable_span: *span,
+                        callable_name: "type".into(),
+                        callable_definition_span: None,
+                        arity: 1..=1,
+                        num_args: args.len(),
+                    });
+                }
+
+                let checked_args = args
+                    .into_iter()
+                    .map(|e| self.check_expression(e))
+                    .collect::<Result<Vec<_>>>()?;
+
+                typed_ast::Statement::ProcedureCall(kind.clone(), checked_args)
+            }
             ast::Statement::ProcedureCall(span, kind, args) => {
                 let procedure = ffi::procedures().get(&kind).unwrap();
                 if !procedure.arity.contains(&args.len()) {
@@ -939,7 +960,7 @@ impl TypeChecker {
                 let checked_args = args
                     .into_iter()
                     .map(|e| self.check_expression(e))
-                    .collect::<Result<Vec<typed_ast::Expression>>>()?;
+                    .collect::<Result<Vec<_>>>()?;
 
                 typed_ast::Statement::ProcedureCall(kind.clone(), checked_args)
             }

+ 21 - 1
numbat/src/vm.rs

@@ -63,6 +63,9 @@ pub enum Op {
     /// Same as above, but call a procedure which does not return anything (does not push a value onto the stack)
     FFICallProcedure,
 
+    /// Print a compile-time string
+    PrintString,
+
     /// Perform a simplification operation to the current value on the stack
     FullSimplify,
 
@@ -78,7 +81,8 @@ impl Op {
             | Op::ApplyPrefix
             | Op::SetVariable
             | Op::GetVariable
-            | Op::GetLocal => 1,
+            | Op::GetLocal
+            | Op::PrintString => 1,
             Op::Negate
             | Op::Factorial
             | Op::Add
@@ -111,6 +115,7 @@ impl Op {
             Op::Call => "Call",
             Op::FFICallFunction => "FFICallFunction",
             Op::FFICallProcedure => "FFICallProcedure",
+            Op::PrintString => "PrintString",
             Op::FullSimplify => "FullSimplify",
             Op::Return => "Return",
         }
@@ -178,6 +183,9 @@ pub struct Vm {
     /// Unit prefixes in use
     prefixes: Vec<Prefix>,
 
+    /// Strings that are already available at compile time
+    strings: Vec<String>,
+
     /// The names of global variables or [Unit]s. The second
     /// entry is the canonical name for units.
     global_identifiers: Vec<(String, Option<String>)>,
@@ -206,6 +214,7 @@ impl Vm {
             current_chunk_index: 0,
             constants: vec![],
             prefixes: vec![],
+            strings: vec![],
             global_identifiers: vec![],
             globals: HashMap::new(),
             ffi_callables: ffi::procedures().iter().map(|(_, ff)| ff).collect(),
@@ -571,6 +580,11 @@ impl Vm {
                         }
                     }
                 }
+                Op::PrintString => {
+                    let s_idx = self.read_u16() as usize;
+                    let s = &self.strings[s_idx];
+                    println!("{}", s);
+                }
                 Op::FullSimplify => {
                     let simplified = self.pop().full_simplify();
                     self.push(simplified);
@@ -631,6 +645,12 @@ impl Vm {
                 .join("] [")
         );
     }
+
+    pub fn add_string(&mut self, s: String) -> u16 {
+        self.strings.push(s);
+        assert!(self.strings.len() <= u16::MAX as usize);
+        (self.strings.len() - 1) as u16 // TODO: this can overflow, see above
+    }
 }
 
 #[test]

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

@@ -29,7 +29,7 @@
             "patterns": [
                 {
                     "name": "keyword.control.numbat",
-                    "match": "\\b(per|to|let|fn|dimension|unit|use|long|short|both|none|print|assert_eq)\\b"
+                    "match": "\\b(per|to|let|fn|dimension|unit|use|long|short|both|none|print|assert_eq|type)\\b"
                 }
             ]
         },