Compare commits

...

10 Commits

18 changed files with 2891 additions and 63 deletions

52
.vim/indent/kyn.vim Normal file
View File

@@ -0,0 +1,52 @@
" Vim indent file
" Language: Kyn
" Maintainer: Gemini
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
setlocal indentexpr=GetKynIndent()
setlocal indentkeys+=={,},0)
let b:undo_indent = "setlocal indentexpr< indentkeys<"
" Only define the function once
if exists("*GetKynIndent")
finish
endif
function GetKynIndent()
" Get the line number of the current line
let lnum = v:lnum
" Get the current line
let cline = getline(lnum)
" If the current line has a '}', decrease indent
if cline =~ '^\s*}'
let lnum = prevnonblank(lnum - 1)
return indent(lnum)
endif
" Find the previous non-blank line
let lnum = prevnonblank(lnum - 1)
" At the start of the file, no indent
if lnum == 0
return 0
endif
" Get the indent of the previous line
let prev_indent = indent(lnum)
let prev_line = getline(lnum)
" If the previous line ends with '{', increase indent
if prev_line =~ '{\s*$'
return prev_indent + &shiftwidth
endif
" Otherwise, keep the same indent
return prev_indent
endfunction

View File

@@ -39,7 +39,7 @@ Value handleListGet(const Value& subject, const std::vector<Value>& args) {
return Value();
}
Value accessor = args[0];
if (accessor.valtype == ValueType::String && accessor.string_val == "size") {
if (accessor.valtype == ValueType::Identifier && accessor.string_val == "size") {
return Value((long long)subject.list.size());
}
if (accessor.valtype != ValueType::Int) {

View File

@@ -39,7 +39,7 @@ Value handleStringGet(const Value& subject, const std::vector<Value>& args) {
}
Value accessor = args[0];
if (accessor.valtype != ValueType::Int) {
if (accessor.valtype == ValueType::String && accessor.string_val == "size") {
if (accessor.valtype == ValueType::Identifier && accessor.string_val == "size") {
return Value((long long)subject.string_val.length());
}
error("String accessor must be an integer or \"size\"");
@@ -51,7 +51,7 @@ Value handleStringGet(const Value& subject, const std::vector<Value>& args) {
error("String index out of bounds");
return Value();
}
return Value(std::string(1, subject.string_val.at(index)));
return Value("\"" + std::string(1, subject.string_val.at(index)) + "\"");
}
void handleStringSet(std::string var_name, Value& subject, const Value& index_val, const Value& new_val) {

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include "../datatypes/lists/lists.h"
#include "../utils/escapes/escapes.h"
InstructionType strToInstructionType(std::string in) {
if (in == "println") return InstructionType::Println;
@@ -53,7 +54,11 @@ Instruction::Instruction(std::vector<Value> toks) {
} else if (toks[0].valtype == ValueType::Variable) {
instruction = InstructionType::Variable;
} else {
error("Instruction should be an identifier or variable value");
if (inReplMode) {
instruction = InstructionType::Variable;
} else {
error("Instruction should be an identifier or variable value");
}
}
if (instruction == InstructionType::Variable) {
for (const auto& tok : toks) {
@@ -72,20 +77,20 @@ Instruction::Instruction(std::vector<Value> toks) {
Value::Value(std::string stringval) {
// This constructor will attempt to parse the string into the most specific type possible.
if (stringval.length() >= 2 && stringval.front() == '"' && stringval.back() == '"') {
valtype = ValueType::String;
string_val = interpretEscapeSequences(stringval.substr(1, stringval.length() - 2));
return;
}
if (stringval.length() > 2 && stringval.front() == '<' && stringval.back() == '>') {
valtype = ValueType::TypePlaceholder;
type_placeholder_name = stringval.substr(1, stringval.length() - 2);
return;
}
if (stringval.length() >= 2 && stringval.front() == '"' && stringval.back() == '"') {
valtype = ValueType::String;
string_val = stringval.substr(1, stringval.length() - 2);
return;
}
if (stringval.empty()) {
valtype = ValueType::String;
valtype = ValueType::None;
string_val = "";
return;
}
@@ -269,7 +274,7 @@ std::vector<Value> split(std::string line) {
splitvals.push_back(Value(buf));
}
buf = "";
} else if (chr == '(' || chr == '[') {
} else if ((chr == '(' || chr == '[') && !instring) {
if (brackets == 0) {
bracket_type = chr;
if (!buf.empty()) {
@@ -280,7 +285,7 @@ std::vector<Value> split(std::string line) {
buf += chr;
}
brackets++;
} else if (chr == ')' || chr == ']') {
} else if ((chr == ')' || chr == ']') && !instring) {
brackets--;
if (brackets == 0) {
if (!buf.empty()) {

View File

@@ -10,7 +10,7 @@ enum class InstructionType {
};
enum class ValueType {
Processed, Variable, InstructionGroup, List, Map, String, Int, Double, Identifier, Custom, TypePlaceholder
Processed, Variable, InstructionGroup, List, Map, String, Int, Double, Identifier, Custom, TypePlaceholder, None
};
struct Instruction;
@@ -67,6 +67,7 @@ struct Instruction {
InstructionType instruction = InstructionType::None;
std::vector<Value> args;
std::string toString() const;
int lineNum;
Instruction() = default;
Instruction(std::vector<std::string> toks);
Instruction(std::vector<Value> toks);

View File

@@ -44,6 +44,8 @@ Value execute(Instruction inst) {
if (inst.instruction == InstructionType::Assert) {
return modules::assert(inst.args);
}
// Handle variable operations that require unevaluated arguments
if (inst.instruction == InstructionType::Variable) {
auto& args = inst.args;
@@ -117,8 +119,6 @@ Value execute(Instruction inst) {
return instance;
}
// Indexed assignment: $var index = value
if (args.size() == 4 && args[2].valtype == ValueType::Identifier && args[2].string_val == "=") {
std::string var_name;
@@ -166,14 +166,23 @@ Value execute(Instruction inst) {
data::modifyValue(var_name, new_val);
return Value();
}
}
// Variable access or indexed get
// If args[0] is an Identifier and there are no args, it's a variable access.
if (args[0].valtype == ValueType::Identifier && args.empty()) {
return data::getValue(args[0].string_val);
// For all other instructions, evaluate the arguments first
for(auto& arg : inst.args) {
arg = evaluate(arg);
}
if (inst.instruction == InstructionType::Variable) {
// This part handles variable access and member/method access on evaluated arguments
auto& args = inst.args;
if (args.empty()) {
error("Empty variable instruction after evaluation");
return Value();
}
Value subject = evaluate(args[0]);
Value subject = args[0]; // First arg is already evaluated
std::vector<Value> op_args(args.begin() + 1, args.end());
if (op_args.empty()) {
@@ -183,14 +192,14 @@ Value execute(Instruction inst) {
// Indexed get: subject index
if (subject.valtype == ValueType::List) {
return handleListGet(subject, op_args);
} else if (subject.valtype == ValueType::String) {
} else if (subject.valtype == ValueType::String) {
return handleStringGet(subject, op_args);
} else if (subject.valtype == ValueType::Map) {
if (op_args.empty()) {
return subject;
}
if (op_args[0].valtype != ValueType::Identifier) {
error("Struct member name must be an identifier.");
if (op_args[0].valtype != ValueType::Identifier && op_args[0].valtype != ValueType::String) {
error("Struct member name must be an identifier or string.");
return Value();
}
std::string member_name = op_args[0].string_val;
@@ -212,7 +221,7 @@ Value execute(Instruction inst) {
data::pushScope();
data::scopes.back()["self"] = subject;
for (size_t i = 0; i < func.arg_names.size(); ++i) {
data::scopes.back()[func.arg_names[i]] = evaluate(func_args[i]);
data::scopes.back()[func.arg_names[i]] = func_args[i];
}
Value return_val;
for (const auto& body_inst : func.body) {
@@ -235,11 +244,6 @@ Value execute(Instruction inst) {
return Value();
}
// For all other instructions, evaluate the arguments first
for(auto& arg : inst.args) {
arg = evaluate(arg);
}
// Then, execute the instruction with the evaluated arguments
switch (inst.instruction) {
case InstructionType::Print:

View File

@@ -7,6 +7,7 @@ Value modules::concat(std::vector<Value> args) {
std::string buf;
for (Value val : args) {
switch (val.valtype) {
case ValueType::Identifier:
case ValueType::String:
buf += val.string_val;
break;
@@ -17,7 +18,7 @@ Value modules::concat(std::vector<Value> args) {
buf += std::to_string(val.double_val);
break;
default:
error("Can only concatenate String, Int, and Double values");
error("Can only concatenate String, Int, and Double values. Value given: " + val.toString());
break;
}
}

View File

@@ -22,17 +22,12 @@ Value modules::file(std::vector<Value> args) {
}
std::string buf;
std::string retval;
bool notfirst = false;
while (getline(file, buf)) {
if (notfirst) {
buf += "\n";
} else {
notfirst = true;
}
buf += "\n";
retval += buf;
}
file.close();
return Value(retval);
return Value("\"" + retval + "\"");
} else if (args[0].string_val == "readlines") {
std::ifstream file(args[1].string_val);
if (!file) {
@@ -42,7 +37,7 @@ Value modules::file(std::vector<Value> args) {
std::string buf;
std::vector<Value> retval;
while (getline(file, buf)) {
retval.push_back(Value(buf));
retval.push_back(Value("\"" + buf + "\""));
}
file.close();
return Value(retval);

View File

@@ -15,14 +15,21 @@ size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* use
return totalSize;
}
std::string curlWrapper(std::string url, RequestType requestType, std::string data = "") {
std::string curlWrapper(std::string url, RequestType requestType, std::vector<std::string> headers = {"Content-Type: text/plain"}, std::string data = "") {
CURL* curl = curl_easy_init();
std::string response;
struct curl_slist *list = NULL;
if (curl) {
for (const std::string& header : headers) {
list = curl_slist_append(list, header.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "kyn-libcurl-agent/1.0");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
if (requestType == RequestType::POST) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
@@ -51,17 +58,57 @@ Value modules::request(std::vector<Value> args) {
}
if (args[0].valtype == ValueType::Identifier) {
if (args[0].string_val == "get") {
std::string response = curlWrapper(args[1].string_val, RequestType::GET);
if (response == "KYNERR") return Value("Error");
return Value(response);
if (args.size() == 3) {
if (args[2].valtype == ValueType::List) {
std::vector<std::string> headers;
for (const Value& value : args[2].list) {
if (value.valtype == ValueType::String) {
headers.push_back(value.string_val);
} else {
error("Expecting second argument of submodule get to be a list of strings");
return Value("Error");
}
}
std::string response = curlWrapper(args[1].string_val, RequestType::GET, headers);
if (response == "KYNERR") return Value("Error");
return Value(response);
} else {
error("Expecting second argument of submodule get to be a list of strings");
return Value("Error");
}
} else {
std::string response = curlWrapper(args[1].string_val, RequestType::GET);
if (response == "KYNERR") return Value("Error");
return Value(response);
}
} else if (args[0].string_val == "post") {
if (args.size() < 3 || args[2].valtype != ValueType::String) {
error("post submodule of request expects a string as 2nd arg");
return Value("Error");
}
std::string response = curlWrapper(args[1].string_val, RequestType::POST, args[2].string_val);
if (response == "KYNERR") return Value("Error");
return Value(response);
if (args.size() == 4) {
if (args[3].valtype == ValueType::List) {
std::vector<std::string> headers;
for (const Value& value : args[3].list) {
if (value.valtype == ValueType::String) {
headers.push_back(value.string_val);
} else {
error("Expecting third argument of submodule post to be a list of strings");
return Value("Error");
}
}
std::string response = curlWrapper(args[1].string_val, RequestType::POST, headers, args[2].string_val);
if (response == "KYNERR") return Value("Error");
return Value(response);
} else {
error("Expecting third argument of submodule post to be a list of strings");
return Value("Error");
}
} else {
std::string response = curlWrapper(args[1].string_val, RequestType::POST, {"Content-Type: text/plain"}, args[2].string_val);
if (response == "KYNERR") return Value("Error");
return Value(response);
}
}
}
error("Unknown submodule for request. Only 'get' and 'post' are available.");

View File

@@ -1,8 +1,45 @@
#include "split.h"
#include "../../defs/defs.h"
#include "../../error/error.h"
#include <vector>
#include <string>
Value modules::split(std::vector<Value> args) {
return Value("Work in progress!");
if (args.size() < 1) {
error("Not enough args for split module");
}
if (args[0].valtype != ValueType::String) {
error("First argument of split must be a string");
}
std::string delimiter = " ";
if (args.size() > 1) {
if (args[1].valtype == ValueType::String) {
delimiter = args[1].string_val;
} else {
error("Expected a string delimiter as second argument of split");
}
}
std::string buf;
std::vector<Value> list;
for (const char& chr : args[0].string_val) {
buf += chr;
if (buf.size() >= delimiter.size()) {
std::string checker = buf.substr(buf.size() - delimiter.size());
if (checker == delimiter) {
list.push_back(Value("\"" + buf.substr(0, buf.size() - delimiter.size()) + "\""));
buf.clear();
}
}
}
if (!buf.empty()) {
list.push_back(Value("\"" + buf + "\""));
}
return Value(list);
}

View File

@@ -9,8 +9,10 @@ std::vector<Instruction> parse(std::string program) {
std::vector<std::string> lines;
std::string buf;
int blockTracker = 0;
int lineNum = 0;
for (char chr : program) {
if (chr == '\n') lineNum ++;
if (chr == '{') {
blockTracker++;
} else if (chr == '}') {
@@ -23,7 +25,7 @@ std::vector<Instruction> parse(std::string program) {
if ((chr == '\n' || chr == ';') && blockTracker == 0) {
if (!buf.empty()) {
lines.push_back(buf);
lines.push_back(trim(buf));
buf.clear();
}
} else {
@@ -90,9 +92,18 @@ std::vector<Instruction> parse(std::string program) {
error("Expected '{' for else statement");
continue;
}
size_t else_block_end = remaining_line.find_last_of('}');
if (else_block_end == std::string::npos) {
error("Expected '}' for else statement");
size_t else_block_end = 0;
int brace_level = 0;
for (size_t i = else_block_start; i < remaining_line.length(); ++i) {
if (remaining_line[i] == '{') brace_level++;
else if (remaining_line[i] == '}') brace_level--;
if (brace_level == 0) {
else_block_end = i;
break;
}
}
if (else_block_end == 0) {
error("Mismatched braces in else statement");
continue;
}
std::string else_content = remaining_line.substr(else_block_start + 1, else_block_end - else_block_start - 1);

View File

@@ -5,11 +5,12 @@
#include <iostream>
#include <string>
#include "../data/data.h"
#include "../vendor/linenoise/linenoise.hpp"
void repl() {
data::initScopes();
inReplMode = true;
std::cout << "Kyn REPL v0.0.1" << std::endl;
std::cout << "Kyn REPL v0.0.2" << std::endl;
std::cout << "Type 'exit' to exit" << std::endl;
std::string full_command;
@@ -17,14 +18,11 @@ void repl() {
int brace_level = 0;
while (true) {
if (brace_level == 0) {
std::cout << "kyn> ";
} else {
std::cout << ".. > ";
}
auto prompt = brace_level == 0 ? "kyn> " : ".. > ";
auto quit = linenoise::Readline(prompt, line_buf);
if (!getline(std::cin, line_buf)) {
break; // Handle EOF (Ctrl+D)
if (quit) {
break;
}
for (char c : line_buf) {
@@ -44,9 +42,13 @@ void repl() {
if (full_command.find_first_not_of(" \t\n\r") != std::string::npos) {
std::vector<Instruction> parsed = parse(full_command);
for (Instruction inst : parsed) {
execute(inst);
Value instruction = execute(inst);
if (!(instruction.valtype == ValueType::None)) {
std::cout << instruction.toString() << std::endl;
}
}
}
linenoise::AddHistory(full_command.c_str());
full_command.clear();
brace_level = 0;
}

View File

@@ -0,0 +1,34 @@
#include "escapes.h"
#include <string>
std::string interpretEscapeSequences(std::string input) {
std::string output;
for (size_t i = 0; i < input.length(); ++i) {
if (input[i] == '\\' && i + 1 < input.length()) {
char next = input[i + 1];
switch (next) {
case 'n': output += '\n'; break;
case 't': output += '\t'; break;
case 'r': output += '\r'; break;
case 'b': output += '\b'; break;
case 'f': output += '\f'; break;
case 'a': output += '\a'; break;
case 'v': output += '\v'; break;
case '\\': output += '\\'; break;
case '\'': output += '\''; break;
case '\"': output += '\"'; break;
case '0': output += '\0'; break;
default:
output += '\\';
output += next;
break;
}
++i;
} else {
output += input[i];
}
}
return output;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string interpretEscapeSequences(std::string input);

View File

@@ -2,11 +2,11 @@
// Helper to trim whitespace from the start and end of a string
std::string trim(const std::string& str) {
size_t first = str.find_first_not_of(" \t\n\r");
size_t first = str.find_first_not_of(" \t\n");
if (std::string::npos == first) {
return str;
return "";
}
size_t last = str.find_last_not_of(" \t\n\r");
size_t last = str.find_last_not_of(" \t\n");
return str.substr(first, (last - first + 1));
}

0
src/vendor/linenoise/linenoise.h vendored Normal file
View File

2587
src/vendor/linenoise/linenoise.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

47
tests/toInt.kyn Normal file
View File

@@ -0,0 +1,47 @@
fun toInt in {
assert $in is <String>
let retint = 0
let counter = 0
let power = ($in size)
let size = ($in size)
while compare $counter < $size {
let char = ($in $counter)
power = (math $power - 1)
if compare $char == "1" {
retint = (math $retint + (math 1 * 10 ^ $power))
}
if compare $char == "2" {
retint = (math $retint + (math 2 * 10 ^ $power))
}
if compare $char == "3" {
retint = (math $retint + (math 3 * 10 ^ $power))
}
if compare $char == "4" {
retint = (math $retint + (math 4 * 10 ^ $power))
}
if compare $char == "5" {
retint = (math $retint + (math 5 * 10 ^ $power))
}
if compare $char == "6" {
retint = (math $retint + (math 6 * 10 ^ $power))
}
if compare $char == "7" {
retint = (math $retint + (math 7 * 10 ^ $power))
}
if compare $char == "8" {
retint = (math $retint + (math 8 * 10 ^ $power))
}
if compare $char == "9" {
retint = (math $retint + (math 9 * 10 ^ $power))
}
if compare $char == "0" {
# whole lotta nothing
}
counter = (math $counter + 1)
}
return $retint
}
let myInt = (toInt "4738927643289")
println $myInt