Skip to content

Commit

Permalink
Merge pull request #246 from KennyOliver/issue-245
Browse files Browse the repository at this point in the history
Issue 245: Add Support for Prompts in sample() Function
  • Loading branch information
KennyOliver authored Jan 20, 2025
2 parents b262e41 + 04577a0 commit 3876994
Show file tree
Hide file tree
Showing 16 changed files with 148 additions and 164 deletions.
3 changes: 1 addition & 2 deletions docs/brainf_interpreter.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ create brainf_interpreter(code) {
let character = chr(ascii_val);
serve(character, False);
} elif command == "," {
serve("Input a character:");
let input_char = sample();
let input_char = sample("Input a character:");
let ascii_val = ord(input_char);
data_tape[data_ptr] = ascii_val;
} elif command == "[" {
Expand Down
46 changes: 23 additions & 23 deletions docs/language_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,29 @@ FlavorLang is a programming language designed with a cooking-inspired syntax, co

### 1. Keywords

| Category | Keyword | Description | Example |
| ------------------------ | --------- | ------------------------------ | --------------------------- |
| **Variable Declaration** | `let` | Mutable variable declaration | `let x = 5;` |
| | `const` | Immutable constant declaration | `const PI = 3.14;` |
| **Control Flow** | `if` | Conditional execution | `if condition { ... }` |
| | `elif` | Alternative condition | `elif condition { ... }` |
| | `else` | Default condition | `else { ... }` |
| | `for` | Loop iteration | `for i in range { ... }` |
| | `while` | Conditional loop | `while condition { ... }` |
| | `break` | Exit loop or switch | `break;` |
| **Pattern Matching** | `check` | Pattern matching construct | `check value { ... }` |
| | `is` | Pattern case | `is pattern:` |
| **Functions** | `create` | Function declaration | `create func() { ... }` |
| | `deliver` | Return value | `deliver result;` |
| **Error Handling** | `try` | Exception handling | `try { ... }` |
| | `rescue` | Error catching | `rescue { ... }` |
| | `finish` | Cleanup block | `finish { ... }` |
| | `burn` | Raise error | `burn "Error message";` |
| **I/O Operations** | `serve` | Output | `serve("message");` |
| | `sample` | Input | `let input = sample();` |
| | `plate` | File write | `plate_file(path, data);` |
| | `garnish` | File append | `garnish_file(path, data);` |
| | `taste` | File read | `taste_file(path);` |
| Category | Keyword | Description | Example |
| ------------------------ | --------- | ------------------------------ | ---------------------------------------- |
| **Variable Declaration** | `let` | Mutable variable declaration | `let x = 5;` |
| | `const` | Immutable constant declaration | `const PI = 3.14;` |
| **Control Flow** | `if` | Conditional execution | `if condition { ... }` |
| | `elif` | Alternative condition | `elif condition { ... }` |
| | `else` | Default condition | `else { ... }` |
| | `for` | Loop iteration | `for i in range { ... }` |
| | `while` | Conditional loop | `while condition { ... }` |
| | `break` | Exit loop or switch | `break;` |
| **Pattern Matching** | `check` | Pattern matching construct | `check value { ... }` |
| | `is` | Pattern case | `is pattern:` |
| **Functions** | `create` | Function declaration | `create func() { ... }` |
| | `deliver` | Return value | `deliver result;` |
| **Error Handling** | `try` | Exception handling | `try { ... }` |
| | `rescue` | Error catching | `rescue { ... }` |
| | `finish` | Cleanup block | `finish { ... }` |
| | `burn` | Raise error | `burn "Error message";` |
| **I/O Operations** | `serve` | Output | `serve("message");` |
| | `sample` | Input | `let input = sample("Enter a number:");` |
| | `plate` | File write | `plate_file(path, data);` |
| | `garnish` | File append | `garnish_file(path, data);` |
| | `taste` | File read | `taste_file(path);` |

### 2. Data Types

Expand Down
10 changes: 7 additions & 3 deletions docs/standard_library.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The FlavorLang Standard Library provides a comprehensive set of built-in functio
- [`float(value) → float`](#floatvalue--float)
- [`int(value) → int`](#intvalue--int)
- [Input/Output](#inputoutput)
- [`sample() → string`](#sample--string)
- [`sample(prompt) → string`](#sampleprompt--string)
- [`serve(*args, newline=True)`](#serveargs-newlinetrue)
- [Error Handling](#error-handling)
- [`burn(*messages)`](#burnmessages)
Expand Down Expand Up @@ -110,9 +110,13 @@ int("-17"); # -17

### Input/Output

#### `sample() → string`
#### `sample(prompt) → string`

Reads a line of input from the terminal.
Reads a line of input from the terminal with an optional prompt.

**Parameters:**

- `prompt`: any value

**Returns:**

Expand Down
3 changes: 1 addition & 2 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ serve("Multiple", "values", "work"); # Spaces between values
Use `sample()` to get user input:

```js
serve("What's your favorite dish?");
let favorite = sample();
let favorite = sample("What's your favorite dish?");
serve("Ah,", favorite, "is delicious!");
```

Expand Down
208 changes: 100 additions & 108 deletions src/interpreter/builtins.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,86 @@
#include "builtins.h"

char *literal_value_to_string(LiteralValue lv) {
char buffer[128];
switch (lv.type) {
case TYPE_INTEGER: {
snprintf(buffer, sizeof(buffer), INT_FORMAT, lv.data.integer);
return strdup(buffer);
}
case TYPE_FLOAT: {
snprintf(buffer, sizeof(buffer), FLOAT_FORMAT, lv.data.floating_point);
return strdup(buffer);
}
case TYPE_STRING: {
return strdup(lv.data.string);
}
case TYPE_BOOLEAN: {
return strdup(lv.data.boolean ? "True" : "False");
}
case TYPE_ARRAY: {
// Estimate needed buffer size: assume roughly 32 chars per element plus
// brackets
size_t estimate = lv.data.array.count * 32 + 3;
char *result = malloc(estimate);
if (!result)
return NULL;
strcpy(result, "[");
for (size_t i = 0; i < lv.data.array.count; i++) {
char *elemStr = literal_value_to_string(lv.data.array.elements[i]);
if (!elemStr) {
free(result);
return NULL;
}
strncat(result, elemStr, estimate - strlen(result) - 1);
free(elemStr);
if (i < lv.data.array.count - 1) {
strncat(result, ", ", estimate - strlen(result) - 1);
}
}
strncat(result, "]", estimate - strlen(result) - 1);
return result;
}
default:
return strdup("<Unsupported>");
}
}

/**
* @brief Builds a single string by converting each function call argument
* (using literal_value_to_string) and concatenating them (separated by a single
* space).
*
* @param arg_node
* @param env
* @return char*
*/
char *build_arguments_string(ASTNode *arg_node, Environment *env) {
char buffer[1024] = {0};
size_t buffer_index = 0;

while (arg_node != NULL) {
InterpretResult r = interpret_node(arg_node, env);
if (r.is_error) {
return NULL; // If an error occurs, return NULL.
}
// Convert the evaluated argument to string.
char *s = literal_value_to_string(r.value);
if (!s)
return NULL;
size_t len = strlen(s);
if (buffer_index + len + 1 < sizeof(buffer)) {
strcpy(&buffer[buffer_index], s);
buffer_index += len;
if (arg_node->next != NULL && buffer_index < sizeof(buffer) - 1) {
buffer[buffer_index++] = ' ';
}
}
free(s);
arg_node = arg_node->next;
}
return strdup(buffer);
}

// Helper function to check if a LiteralType matches an ArgType
bool literal_type_matches_arg_type(LiteralType lit_type, ArgType arg_type) {
switch (arg_type) {
Expand Down Expand Up @@ -161,20 +242,28 @@ void print_formatted_string(const char *str) {
}
}

// Built-in `input()` function
// Built-in `input()` function with optional arguments for a prompt
InterpretResult builtin_input(ASTNode *node, Environment *env) {
(void)node; // Unused parameter, suppress compiler warning
(void)env; // Unused parameter
// If a prompt argument is provided, build and print it.
if (node->function_call.arguments != NULL) {
char *prompt =
build_arguments_string(node->function_call.arguments, env);
if (prompt) {
printf("%s", prompt);
fflush(stdout);
free(prompt);
}
}

// Now read input from the user.
size_t buffer_size = 128;
size_t input_length = 0;
char *input_buffer = malloc(buffer_size);
if (!input_buffer) {
fprintf(stderr, "Error: Failed to allocate memory for input buffer.\n");
LiteralValue lv = (LiteralValue){.type = TYPE_ERROR};
LiteralValue lv = {.type = TYPE_ERROR};
return make_result(lv, false, false);
}

int c;
while ((c = getchar()) != '\n' && c != EOF) {
if (input_length + 1 >= buffer_size) {
Expand All @@ -185,7 +274,7 @@ InterpretResult builtin_input(ASTNode *node, Environment *env) {
stderr,
"Error: Failed to reallocate memory for input buffer.\n");
free(input_buffer);
LiteralValue lv = (LiteralValue){.type = TYPE_ERROR};
LiteralValue lv = {.type = TYPE_ERROR};
return make_result(lv, false, false);
}
input_buffer = new_buffer;
Expand All @@ -201,12 +290,10 @@ InterpretResult builtin_input(ASTNode *node, Environment *env) {

if (!result.data.string) {
fprintf(stderr, "Error: Failed to duplicate input string.\n");
LiteralValue lv = (LiteralValue){.type = TYPE_ERROR};
LiteralValue lv = {.type = TYPE_ERROR};
return make_result(lv, false, false);
}

debug_print_int("Input received: `%s`\n", result.data.string);

return make_result(result, false, false);
}

Expand Down Expand Up @@ -320,106 +407,11 @@ void print_literal_value(LiteralValue lv) {

// Built-in `serve()` function for printing with optional newline control
InterpretResult builtin_output(ASTNode *node, Environment *env) {
debug_print_int("builtin_output() called\n");

ASTNode *arg_node = node->function_call.arguments;

bool add_newline = true; // by default

// Buffer to store all arguments as strings
char output_buffer[1024] = {0};
size_t buffer_index = 0;

// Iterate through arguments
while (arg_node != NULL) {
InterpretResult r = interpret_node(arg_node, env);
if (r.is_error) {
return r; // propagate error
}

LiteralValue lv = r.value;

// If last argument & is boolean, check for newline control
if (arg_node->next == NULL && lv.type == TYPE_BOOLEAN) {
add_newline = lv.data.boolean;
} else {
// Append the current argument's string representation to the buffer
char temp_buffer[128] = {0};

switch (lv.type) {
case TYPE_FLOAT:
if ((INT_SIZE)lv.data.floating_point ==
lv.data.floating_point) {
snprintf(temp_buffer, sizeof(temp_buffer), "%.1Lf",
lv.data.floating_point);
} else {
snprintf(temp_buffer, sizeof(temp_buffer), FLOAT_FORMAT,
lv.data.floating_point);
}
break;
case TYPE_INTEGER:
snprintf(temp_buffer, sizeof(temp_buffer), INT_FORMAT,
lv.data.integer);
break;
case TYPE_STRING: {
char *processed = process_escape_sequences(lv.data.string);
if (processed) {
snprintf(temp_buffer, sizeof(temp_buffer), "%s", processed);
free(processed);
} else {
// Fallback
snprintf(temp_buffer, sizeof(temp_buffer), "%s",
lv.data.string);
}
break;
}
case TYPE_BOOLEAN:
snprintf(temp_buffer, sizeof(temp_buffer), "%s",
lv.data.boolean ? "True" : "False");
break;
case TYPE_ARRAY:
printf("[");
for (size_t i = 0; i < lv.data.array.count; i++) {
LiteralValue elem = lv.data.array.elements[i];
print_literal_value(elem);

if (i < lv.data.array.count - 1) {
printf(", ");
}
}
printf("]");
break;
default:
snprintf(temp_buffer, sizeof(temp_buffer), "<Unsupported>");
break;
}

// Append to the output buffer with a space if needed
size_t temp_len = strlen(temp_buffer);
if (buffer_index + temp_len + 1 < sizeof(output_buffer)) {
if (buffer_index > 0) {
output_buffer[buffer_index++] =
' '; // Add space between arguments
}
strcpy(&output_buffer[buffer_index], temp_buffer);
buffer_index += temp_len;
} else {
return raise_error("Output buffer overflow in `serve()`.\n");
}
}

arg_node = arg_node->next;
char *output = build_arguments_string(node->function_call.arguments, env);
if (output) {
printf("%s\n", output);
free(output);
}

// Print final output buffer
printf("%s", output_buffer);

// Add newline if flag is true
if (add_newline) {
printf("\n");
}

// Return default value
LiteralValue lv = {.type = TYPE_INTEGER, .data.integer = 0};
return make_result(lv, false, false);
}
Expand Down
1 change: 1 addition & 0 deletions src/interpreter/builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ InterpretResult builtin_round(ASTNode *node, Environment *env);
InterpretResult builtin_abs(ASTNode *node, Environment *env);

// Helpers
char *literal_value_to_string(LiteralValue lv);
bool literal_type_matches_arg_type(LiteralType lit_type, ArgType arg_type);
InterpretResult interpret_arguments(ASTNode *node, Environment *env,
size_t num_args, ArgumentSpec *specs);
Expand Down
3 changes: 1 addition & 2 deletions src/tests/13_casting.flv
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ serve(b == c);
serve(b != c);

for _ in 1..=2 {
serve("Enter a number:");
let user_input = int(sample());
let user_input = int(sample("Enter a number:"));
let positive = user_input >= 0;
serve("Positive?", positive);
}
Expand Down
3 changes: 1 addition & 2 deletions src/tests/14_random.flv
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Using `sample()` to get user input
serve("Enter your favorite number:");
const favorite = sample();
const favorite = sample("Enter your favorite number:");
serve("Your favorite is:", favorite);

# Using `random()` with different argument counts
Expand Down
4 changes: 2 additions & 2 deletions src/tests/16_ternary.flv
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ create check_even_odd(num) {
}

for _ in 1..=5 {
serve("Enter a number:");
let input = int(sample());
serve();
let input = int(sample("Enter a number:"));
serve(check_even_odd(input));
}
6 changes: 2 additions & 4 deletions src/tests/9_input.flv
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
serve("What's your favorite dessert?");
const favorite = sample();

serve("You chose:", favorite);
const favorite = sample("What's your favorite dessert?");
serve("You chose:", favorite);
Loading

0 comments on commit 3876994

Please sign in to comment.