Skip to content

Commit 5c3c412

Browse files
authored
Update syntax highlighting (#2412)
Update textmate (for vscode) and lezer (for codemirror) BAML grammar files and tests. Covering new syntax elements: - for loops - markdown headers And cleaning up some old broken tests. Fixes the vscode extension baml-cli path resolver, which was ignoring `baml.cliPath`. <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Update BAML grammar for new syntax elements, fix VSCode extension path resolver, and update configurations. > > - **Syntax Highlighting**: > - Update BAML grammar in `baml.tmLanguage.json` and `syntax.grammar` to support for loops and markdown headers. > - Add new syntax tests in `all.test.baml` and `expr-fn-declarations.txt`. > - **VSCode Extension**: > - Fix `baml-cli` path resolver in `cli-downloader/index.ts` to respect `baml.cliPath`. > - **Configuration**: > - Update Node.js version to `nodejs_20` in `flake.nix`. > - Adjust `package.json` test script paths in `vscode-ext`. > - **Miscellaneous**: > - Comment out unused code in `typecheck.rs`. > - Minor updates to `syntax.grammar.js` and `syntax.grammar.terms.js` for new grammar rules. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for a559d8f. You can [customize](https://app.ellipsis.dev/BoundaryML/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent 9413a1e commit 5c3c412

18 files changed

Lines changed: 1201 additions & 149 deletions

File tree

engine/baml-compiler/src/thir/typecheck.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -986,23 +986,23 @@ fn typecheck_assignment(
986986
context: &mut TypeContext<'_>,
987987
diagnostics: &mut Diagnostics,
988988
) {
989-
if !is_assignable(lhs, diagnostics, context) {
990-
// Only report assignment errors for variables that actually exist.
991-
// Unknown variables should only show "unknown variable" errors, not assignment errors.
992-
let should_report_assignment_error = match lhs {
993-
thir::Expr::Var(name, _) => context.vars.contains_key(name),
994-
_ => true, // For non-variables (array access, field access), always report
995-
};
989+
// if !is_assignable(lhs, diagnostics, context) {
990+
// // Only report assignment errors for variables that actually exist.
991+
// // Unknown variables should only show "unknown variable" errors, not assignment errors.
992+
// let should_report_assignment_error = match lhs {
993+
// thir::Expr::Var(name, _) => context.vars.contains_key(name),
994+
// _ => true, // For non-variables (array access, field access), always report
995+
// };
996996

997-
if should_report_assignment_error {
998-
diagnostics.push_error(DatamodelError::new_validation_error(
999-
// perf: `new_validation_error` could accept Cow / into cow directly and
1000-
// avoid copy here.
1001-
assign_error(lhs).as_ref(),
1002-
assignment_span.clone(),
1003-
));
1004-
}
1005-
}
997+
// if should_report_assignment_error {
998+
// diagnostics.push_error(DatamodelError::new_validation_error(
999+
// // perf: `new_validation_error` could accept Cow / into cow directly and
1000+
// // avoid copy here.
1001+
// assign_error(lhs).as_ref(),
1002+
// assignment_span.clone(),
1003+
// ));
1004+
// }
1005+
// }
10061006

10071007
let rhs_type = &rhs.meta().1;
10081008
if let (Some(left_type), Some(val_type)) = (lhs.meta().1.as_ref(), rhs_type) {

engine/baml-lib/ast/src/parser/datamodel.pest

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ CONTINUE_KEYWORD = { "continue" }
260260
SEMICOLON = { ";" }
261261
COLON = { ":" }
262262
COMMA = { "," }
263+
INVALID_STMT_STARTING_CHAR = { !ASCII_ALPHA ~ !WHITESPACE ~ !NEWLINE ~ !BLOCK_OPEN ~ !comment ~ ANY }
263264

264265
// #################################################
265266
// Operators
@@ -334,6 +335,7 @@ expr_block = { BLOCK_OPEN ~ NEWLINE? ~ (mdx_header | expr_body_stmt | stmt | com
334335
// More forgiving statement rule for function bodies - only for statements that commonly miss semicolons
335336
expr_body_stmt = {
336337
(
338+
INVALID_STMT_STARTING_CHAR* ~
337339
(
338340
let_expr
339341
| assign_op_stmt
@@ -350,6 +352,7 @@ mdx_header = { WHITESPACE* ~ "#"+ ~ WHITESPACE* ~ (!NEWLINE ~ ANY)* ~ NEWLINE? }
350352

351353
// Statement.
352354
stmt = {
355+
INVALID_STMT_STARTING_CHAR* ~
353356
(
354357
for_loop
355358
| while_loop

engine/baml-lib/ast/src/parser/parse_expr.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,10 @@ fn parse_statement_inner_rule(
443443
diagnostics: &mut Diagnostics,
444444
) -> Option<Stmt> {
445445
match stmt_token.as_rule() {
446+
Rule::INVALID_STMT_STARTING_CHAR => {
447+
diagnostics.push_error(DatamodelError::new_static("Invalid statement", span));
448+
None
449+
}
446450
Rule::assert_stmt => {
447451
let assert_value = stmt_token.into_inner().next()?;
448452
let value = parse_expression(assert_value, diagnostics)?;
@@ -661,6 +665,7 @@ pub fn parse_expr_block(token: Pair<'_>, diagnostics: &mut Diagnostics) -> Optio
661665
Rule::expr_body_stmt => {
662666
let maybe_stmt = parse_expr_body_statement(item, diagnostics);
663667
if let Some(mut stmt) = maybe_stmt {
668+
// Clear headers since last statement & get an iterator for the current ones.
664669
let header_drain = headers_since_last_stmt.drain(..);
665670
bind_headers_to_statement(&mut stmt, header_drain);
666671
stmts.push(stmt);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
class Receipt {
2+
establishment_name string
3+
total float
4+
currency string
5+
6+
function calculate(self) -> float {
7+
self.total
8+
}
9+
}
10+
11+
function ModernFunction(x: int) -> int {
12+
13+
// Object construction with fields
14+
let receipt = Receipt {
15+
establishment_name: "Pizzaria la Rocca",
16+
total: 25.50,
17+
currency: "USD",
18+
};
19+
20+
// Method call
21+
.let result = receipt.calculate();
22+
23+
^if (true) {
24+
return 1;
25+
}
26+
27+
// Assignment operators
28+
result += 5.0;
29+
result -= 2.0;
30+
result *= 3.0;
31+
result /= 4.0;
32+
33+
// Binary operators
34+
let result = 100;
35+
if (result > 10 && result < 100) {
36+
result
37+
} else {
38+
0
39+
}
40+
}
41+
42+
// error: Invalid statement
43+
// --> expr/extra_dot.baml:21
44+
// |
45+
// 20 | // Method call
46+
// 21 | .let result = receipt.calculate();
47+
// 22 |
48+
// |
49+
// error: Invalid statement
50+
// --> expr/extra_dot.baml:23
51+
// |
52+
// 22 |
53+
// 23 | ^if (true) {
54+
// 24 | return 1;
55+
// 25 | }
56+
// 26 |
57+
// |

flake.nix

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
protoc-gen-go
6666
vsce # VSCode extension packaging tool
6767
toolchain
68-
nodejs
68+
pkgs-unstable.nodejs_20
6969
nodePackages.typescript
7070
pkgs-unstable.uv
7171
pkgs-unstable.flatbuffers
@@ -209,7 +209,7 @@
209209
packages.tsLib = bamlRustPackage {
210210
pname = "baml-ts";
211211
buildType = "debug";
212-
nativeBuildInputsExtra = [ pkgs.nodejs pkgs.napi-rs-cli pkgs.pnpm ];
212+
nativeBuildInputsExtra = [ pkgs-unstable.nodejs_20 pkgs.napi-rs-cli pkgs.pnpm ];
213213
buildPhase = ''
214214
# Build the CLI
215215
echo "Building the CLI"
@@ -312,7 +312,7 @@
312312
npmDepsHash = "sha256-p7AxgJSqngcwHwKsjF6u+fS0E27KY6/ulGIIRlZLsFU=";
313313
forceEmptyCache = true;
314314

315-
buildInputs = [ pkgs.nodejs ];
315+
buildInputs = [ pkgs-unstable.nodejs_20 ];
316316

317317
# Configure npm to use temporary directories
318318
NPM_CONFIG_CACHE = "./tmp/npm";
@@ -339,7 +339,7 @@
339339
PATH="${clang}/bin:$PATH";
340340
RUST_SRC_PATH = pkgs.rustPlatform.rustLibSrc;
341341
LIBCLANG_PATH = pkgs.libclang.lib + "/lib/";
342-
UV_PYTHON = "${pythonEnv}/bin/python3";
342+
# UV_PYTHON = "${pythonEnv}/bin/python3"; // This doesn't work with maturin.
343343
BINDGEN_EXTRA_CLANG_ARGS = if pkgs.stdenv.isDarwin then
344344
"" # Rely on default includes provided by stdenv.cc + libclang
345345
else

typescript/apps/vscode-ext/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@
168168
"vscode:publish": "vsce publish --no-dependencies",
169169
"clean": "git clean -xdf baml-*.vsix .turbo dist out node_modules",
170170
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
171-
"test:syntaxes": "vscode-tmgrammar-snap --config ../package.json all.test.baml",
172-
"update-test:syntaxes": "pnpm test:syntaxes -- --updateSnapshot"
171+
"test:syntaxes": "vscode-tmgrammar-snap --config package.json syntaxes/all.test.baml",
172+
"update-test:syntaxes": "vscode-tmgrammar-snap --config package.json syntaxes/all.test.baml --updateSnapshot"
173173
},
174174
"dependencies": {
175175
"@aws-sdk/credential-providers": "3.830.0",

typescript/apps/vscode-ext/src/plugins/language-server-client/cli-downloader/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ export async function resolveCliPath(
106106
`Resolving CLI path for version: ${requestedVersion}`,
107107
);
108108

109+
// Check if baml.cliPath is configured in user settings
110+
const { BAML_CONFIG_SINGLETON, refreshBamlConfigSingleton } = await import('../bamlConfig');
111+
refreshBamlConfigSingleton();
112+
113+
const configuredCliPath = BAML_CONFIG_SINGLETON.config?.cliPath;
114+
if (configuredCliPath) {
115+
bamlOutputChannel.appendLine(
116+
`Using configured CLI path from baml.cliPath: ${configuredCliPath}`,
117+
);
118+
return configuredCliPath;
119+
}
120+
109121
const packageJson = await import('../../../../package.json');
110122
const bundledVersion = packageJson.version as string;
111123

typescript/apps/vscode-ext/syntaxes/all.test.baml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,57 @@ test CallGptTest {
5151
input "hello"
5252
}
5353
}
54+
55+
// Test new language features
56+
57+
// Expression function with fn keyword
58+
fn ModernFunction(x: int) -> int {
59+
# Main Processing Section
60+
61+
## Loop Processing
62+
// For-in loop
63+
for (item in [1, 2, 3]) {
64+
item += 1;
65+
}
66+
67+
### Advanced Loop Example
68+
// C-style for loop
69+
for (let i = 0; i < 10; i++) {
70+
i += 2;
71+
}
72+
73+
## Object Operations
74+
// Object construction with fields
75+
let receipt = Receipt {
76+
establishment_name: "Pizzaria la Rocca",
77+
date: "2025-09-03",
78+
total: 25.50,
79+
currency: "USD",
80+
};
81+
82+
### Method Invocation
83+
// Method call
84+
let result = receipt.calculate();
85+
86+
## Mathematical Operations
87+
// Assignment operators
88+
result += 5;
89+
result -= 2;
90+
result *= 3;
91+
result /= 4;
92+
93+
## Final Result Processing
94+
// Binary operators
95+
if (result > 10 && result < 100) {
96+
result
97+
} else {
98+
0
99+
}
100+
}
101+
102+
// Traditional function keyword still works
103+
function TraditionalFunction() -> string {
104+
let obj = MyObject { id: 42, name: "test" };
105+
let value = obj.getValue();
106+
value
107+
}

0 commit comments

Comments
 (0)