Skip to content

Instantly share code, notes, and snippets.

@SaulDoesCode
Created June 11, 2024 15:42
Show Gist options
  • Save SaulDoesCode/674e4f95482d9eb00153c323ce274c5a to your computer and use it in GitHub Desktop.
Save SaulDoesCode/674e4f95482d9eb00153c323ce274c5a to your computer and use it in GitHub Desktop.
langauge programming languages

A fascinating project! I'll outline a basic idea for your language, and then provide an initial Rust implementation.

Language Design

The language, which we can call "Lingua", will have a core of simple, meaningful words that can be combined to form more complex expressions. These words will map to various constructs in the language, such as:

  1. Nouns: Represented by single words, these can be thought of as objects or entities with attributes.
  2. Verbs: Represented by words ending in "-ing", these perform actions on nouns or other verbs.
  3. Adjectives: Modify nouns by describing their properties (e.g., colors, sizes).
  4. Prepositions: Relate nouns to each other spatially or conceptually (e.g., "on", "with", "like").
  5. Conjunctions: Combine words or phrases into more complex expressions.

Lingua will also have a few special constructs:

  1. Variables: Named placeholders for nouns, which can be assigned and reused.
  2. Functions: Named sequences of verb-noun combinations that perform specific actions.
  3. Patterns: Repetitive structures that match and transform sequences of words (e.g., lists, trees).

Rust Implementation

Here's a basic interpreter in Rust to get you started:

use std::collections::{HashMap, HashSet};

#[derive(Debug, Clone, PartialEq)]
enum Word {
    Noun(String),
    Verb(String),
    Adjective(String),
    Preposition(String),
    Conjunction(String),
    Variable(String),
    Function(String),
}

impl Word {
    fn new(word: &str) -> Self {
        match word {
            _ if word.ends_with("ing") => Word::Verb(word.to_string()),
            _ if word.contains("-") => Word::Noun(word.to_string()),
            _ if word.chars().all(|c| c.is_alphabetic()) => Word::Adjective(word.to_string()),
            _ if ["on", "with", "like"].contains(&word) => Word::Preposition(word.to_string()),
            _ if ["and", "or"].contains(&word) => Word::Conjunction(word.to_string()),
            word => Word::Variable(word.to_string()),
        }
    }
}

#[derive(Debug, Clone)]
struct Lingua {
    words: HashMap<String, Box<dyn Fn(Vec<Word>) -> Vec<Word>>>,
    variables: HashMap<String, Option<Box<Word>>>,
}

impl Lingua {
    fn new() -> Self {
        Lingua {
            words: HashMap::new(),
            variables: HashMap::new(),
        }
    }

    fn add_word(&mut self, word: &str, func: impl Fn(Vec<Word>) -> Vec<Word> + 'static) {
        let w = Word::new(word);
        self.words.insert(word.to_string(), Box::new(func));
    }

    fn set_variable(&mut self, name: &str, value: Option<Box<Word>>) {
        self.variables.insert(name.to_string(), value);
    }

    fn eval(&self, words: Vec<Word>) -> Vec<Word> {
        let mut result = Vec::new();
        for word in words {
            match self.words.get(&word.to_string()) {
                Some(func) => result.extend(from_iter(func(words.clone()))),
                None => {
                    if let Some(value) = self.variables.get(&word.to_string()).cloned() {
                        result.push(*value);
                    } else {
                        panic!("Unknown word: {:?}", word);
                    }
                }
            }
        }
        result
    }
}

fn main() {
    let mut lingua = Lingua::new();

    // Add some basic words
    lingua.add_word("make", |words| vec![Word::Noun(words[1].clone().unwrap().0.to_string())]);
    lingua.add_word("red", |_| vec![Word::Adjective("red".to_string())]);
    lingua.add_word("circle", |_| vec![Word::Noun("circle".to_string())]);

    // Define a simple function
    lingua.add_word("triangle", |words| {
        let mut result = Vec::new();
        result.push(Word::Verb("make".to_string()));
        result.push(words[1].clone());
        result.push(Word::Adjective("pointed".to_string()));
        result
    });

    // Set a variable
    lingua.set_variable("x", Some(Box::new(Word::Noun("circle".to_string()))));

    // Evaluate some expressions
    let result = lingua.eval(vec![
        Word::Variable("x".to_string()),
        Word::Preposition("on"),
        Word::Adjective("red".to_string()),
    ]);
    println!("{:?}", result);

    let result = lingua.eval(vec![Word::Verb("make".to_string()), Word::Noun("triangle".to_string())]);
    println!("{:?}", result);
}

This is a very basic implementation, and you can extend it in many ways to make Lingua more expressive and useful. Some ideas for future development include:

  1. Implementing a parser to turn text into the internal Word representation.
  2. Adding support for lists and trees using patterns.
  3. Enabling recursion by allowing functions to call themselves or other functions.
  4. Defining a way to dynamically create new words and functions at runtime.

I hope this gives you a good starting point for your language, Lingua!

What an intriguing idea! Let's create a programming language that's inspired by natural language, yet still Turing complete. We'll focus on creating a vocabulary and semantics that are meaningful and easy to use.

Let's call our language "Lingua".

Here's the initial design:

Vocabulary:

  1. Verbs (actions):
    • make: creates a new entity or relation
    • link: establishes a connection between entities
    • break: removes an existing connection
    • set: assigns a value to an attribute
    • get: retrieves the value of an attribute
    • query: performs a search or filtering operation
  2. Nouns (entities):
    • thing: a generic, unnamed entity
    • person: a person or individual
    • group: a collection of entities
    • location: a physical location
  3. Modifiers (attributes and qualifiers):
    • big, small, old, new, etc.
    • happy, sad, angry, etc. (emotions)
  4. Pronouns:
    • it (a reference to a previously mentioned entity or result of an action)

We'll use a simple syntax, where each word can be a standalone statement or part of a phrase. Phrases will be separated by whitespace.

Here's the basic syntax:

<verb> [<noun>|<pronoun>] (<modifier>*)

Examples:

make big house
link person to group
set location city
get attribute name of thing
query happy people in group
break connection between person and group

Semantic Mapping:

We'll map each word to a meaningful construct:

  1. Verbs:
    • make: creates an entity with the given attributes (e.g., {type: "house", size: "big"})
    • link: adds a relationship between two entities (e.g., {person A} -relates_to {group X})
    • break: removes a relationship between two entities
    • set: assigns an attribute to an entity
    • get: retrieves the value of an attribute for an entity
    • query: performs a search or filtering operation on a collection of entities (e.g., {people} -> filter(happy))
  2. Nouns:
    • thing, person, group, location: creates an entity with the given type and attributes (e.g., {type: "person", name: "John"})
  3. Modifiers:
    • big, small, etc.: assigns a value to an attribute (e.g., {size: "big"})
    • happy, sad, etc.: assigns an emotional state to an entity
  4. Pronouns:
    • it: references the result of an action or a previously mentioned entity

Interpreter in JavaScript:

Here's a basic implementation of the Lingua interpreter in JavaScript:

function interpret(line) {
  const words = line.split(' ');
  const verb = words[0];
  const nounOrPronoun = words[1] || 'thing';
  const modifiers = words.slice(2);

  switch (verb) {
    case 'make':
      return make(nounOrPronoun, modifiers);
    case 'link':
      return link(nounOrPronoun, modifiers[0], modifiers[1]);
    case 'break':
      return breakRel( nounOrPronoun, modifiers[0] );
    case 'set':
      return setAttr(nounOrPronoun, modifiers[0], modifiers[1]);
    case 'get':
      return getAttr(nounOrPronoun, modifiers[0]);
    case 'query':
      return query(modifiers);
    default:
      throw new Error(`Unknown verb: ${verb}`);
  }
}

function make(entityType, attributes) {
  // Create an entity with the given type and attributes
  return { type: entityType, ...attributes };
}

function link(entity, relationship, target) {
  // Add a relationship between two entities
  return { [entity]: { [relationship]: target } };
}

function breakRel(entity, relationship) {
  // Remove a relationship from an entity
  delete entity[relationship];
  return entity;
}

function setAttr(entity, attrName, value) {
  // Assign an attribute to an entity
  entity[attrName] = value;
  return entity;
}

function getAttr(entity, attrName) {
  // Retrieve the value of an attribute for an entity
  return entity[attrName];
}

function query(modifiers) {
  // Perform a search or filtering operation on a collection of entities
  const filter = modifiers.find((m) => m.startsWith('filter'));
  if (filter) {
    const filterFn = new Function(`return ${filter.slice(7)};`);
    return { people: people.filter(filterFn) };
  } else {
    throw new Error(`Unknown query modifier: ${modifiers[0]}`);
  }
}

You can test the interpreter with sample inputs:

console.log(interpret('make big house'));
console.log(interpret('link person to group'));
console.log(interpret('set location city of house'));
console.log(interpret('get name of thing'));
console.log(interpret('query happy people in group'));

This is a basic implementation, and there's still much to be done (e.g., error handling, entity storage, querying over multiple attributes). However, this should give you a good starting point for your new language, Lingua!

What an intriguing and ambitious project! I'll outline some key design decisions and how they could manifest in a Rust interpreter. We'll aim for a language that's not just a subset of natural language, but rather a novel construct that's useful for programming.

Core Concepts

  1. Primitives: A set of basic, finite constructs that can be combined to form more complex expressions. These primitives will serve as the building blocks for our language.
  2. Atoms: Words in our language will be composed of one or more atoms. Atoms can represent values, functions, or structural elements like lists or dictionaries.
  3. Structures: Ways to combine atoms into meaningful constructs. This could include operators (e.g., +, -), functions, or more complex structures like graphs or trees.

Example Primitives and Atoms

  1. number: an atom representing a numerical value
  2. word: an atom representing a word-like construct with no specific meaning
  3. link: a structure that connects two atoms (e.g., a relation between two entities)
  4. dict: a structure that maps atoms to other atoms or structures
  5. list: a structure containing a sequence of atoms or structures

Operators and Functions

  1. Binary Operators: e.g., join, which concatenates two lists, or merge, which combines two dictionaries
  2. Unary Operators: e.g., invert, which flips the direction of a link, or reverse, which reverses a list
  3. Functions: reusable constructs that take input and return output, similar to macros in other languages

Example Functions

  1. name(entity): assigns a unique name to an entity (atom or structure)
  2. attr(entity, attr, value): adds an attribute to an entity with the given value
  3. relate(entity1, relation, entity2): establishes a link between two entities

Interpretation and Execution

  1. Parse: analyze the input text and break it down into a data structure representing the language's abstract syntax tree (AST)
  2. Execute: traverse the AST, applying the defined operations and functions to produce the desired output
  3. Store: maintain a runtime environment that keeps track of entities, their attributes, and relationships

Rust Implementation

  1. Define a Token enum for the basic constructs (e.g., number, word, link)
  2. Create a Structure trait for combining tokens into meaningful constructs
  3. Implement specific structures like List and Dict that conform to the Structure trait
  4. Define functions and operators as methods on the Structure trait or as standalone functions
  5. Write a parser that converts input text into an AST, using a library like pest-rs or nom if desired
  6. Implement the execution logic in a recursive descent manner or by traversing the AST

Example Input and Output

Input: name(e1) attr(e1, color, blue) relate(e1, has, e2)

Output:

Entity {
    name: "e1",
    attributes: vec![("color", "blue")],
    relationships: vec![
        Relationship {
            target: "e2",
            relation: "has",
        },
    ],
}

This is a high-level outline, and the actual implementation may require more details. However, this should give you a good starting point for creating your novel programming language in Rust.

Please let me know if you'd like to discuss specific aspects of this design or need help with implementing it in Rust!

I'd love to help you create a programming language inspired by natural languages. Here's an idea to get started:

Name: Lumin (a play on "luminous" and "language")

Syntax: Lumin uses a syntax that resembles a subset of English, with simple grammar rules.

Core Concepts:

  1. Words: Lumin's vocabulary consists of words that represent basic constructs. These words are grouped into categories:
    • Actions: perform operations (e.g., add, remove, change)
    • Attributes: describe properties or characteristics (e.g., color, size, shape)
    • Entities: represent objects or concepts (e.g., person, dog, book)
  2. Phrases: Combinations of words form phrases that express meaningful constructs:
    • Actions on Entities: e.g., "give book to person"
    • Entity Properties: e.g., "person has name John"
    • Relationships: e.g., "dog is friend of cat"
  3. Clauses: Phrases are grouped into clauses, which form the basic units of Lumin's syntax:
    • Simple Clause: a single phrase (e.g., "make dog happy")
    • Compound Clause: multiple phrases connected with conjunctions (e.g., "make dog happy and person sad")

Example Code:

Person John has name "John".
Person Mary has name "Mary".
Dog Fido is friend of Person Mary.
Fido likes to play fetch.

Function make_happy(Agent a) {
  give Ball to Agent a;
  say "You got a ball!";
}

make_happy(Fido);
make_happy(John);

Interpretation in Rust:

To implement Lumin's interpreter, we can start by defining the basic constructs as enums and structs. Here's a starting point:

#[derive(Debug, Clone)]
enum Word {
    Action(String),
    Attribute(String),
    Entity(String),
}

#[derive(Debug, Clone)]
struct Phrase {
    words: Vec<Word>,
}

impl Phrase {
    fn new(words: Vec<Word>) -> Self {
        Phrase { words }
    }

    fn evaluate(&self) -> Result<(), String> {
        // Implement phrase evaluation logic here
        Ok(())
    }
}

Goals and Challenges:

  • Define a set of core words (Actions, Attributes, Entities) that can be combined to form meaningful phrases.
  • Develop an interpretation mechanism that parses Lumin code into a data structure and executes it.
  • Ensure Turing completeness by allowing the creation of functions/macros that can manipulate the language itself.
  • Make the language flexible enough to model arbitrary concepts without becoming overly complex or dense.

Let me know how you'd like to proceed, and I'll help guide the development process!

Here's an initial implementation of a programming language with the characteristics you described. It's called "Lumina". You can run this JavaScript code in a Node.js environment to see it in action.

class Lumina {
  constructor() {
    this.words = {};
    this.functions = {};
  }

  define(word, value) {
    this.words[word] = value;
  }

  defineFunction(name, func) {
    this.functions[name] = func;
  }

  interpret(expression) {
    const parts = expression.split(' ');
    if (parts[0] in this.functions) {
      return this.functions[parts[0]](...parts.slice(1));
    } else if (parts[0] in this.words) {
      return this.words[parts[0]];
    } else {
      throw new Error(`Unknown word or function: ${expression}`);
    }
  }

  defineTemplate(name, template) {
    this.defineFunction(name, (...args) => {
      const instance = {};
      for (const key in template) {
        if (template.hasOwnProperty(key)) {
          instance[key] = this.interpret(template[key].replace(/%(\w+)/g, (match, param) => args[~~(~param + 1)]));
        }
      }
      return instance;
    });
  }

  defineRelation(name, relation) {
    this.defineFunction(name, (...args) => {
      const result = {};
      for (const key in relation) {
        if (relation.hasOwnProperty(key)) {
          if (Array.isArray(relation[key])) {
            result[key] = args.filter((arg, index) => relation[key].includes(index)).map(arg => this.interpret(arg));
          } else {
            result[key] = this.interpret(relation[key].replace(/%(\w+)/g, (match, param) => args[~~(~param + 1))]));
          }
        }
      }
      return result;
    });
  }

  defineAttribute(word, attribute, value) {
    if (!this.words[word]) {
      this.define(word, {});
    }
    this.words[word][attribute] = value;
  }

  useAttribute(word, attribute) {
    if (this.words[word] && this.words[word][attribute]) {
      return this.words[word][attribute];
    } else {
      throw new Error(`Unknown word or attribute: ${word} ${attribute}`);
    }
  }

  defineRelationAttribute(name, relation, attribute, value) {
    this.defineFunction(name, (...args) => {
      const result = {};
      for (const key in relation) {
        if (relation.hasOwnProperty(key)) {
          if (Array.isArray(relation[key])) {
            result[key] = args.filter((arg, index) => relation[key].includes(index)).map(arg => this.useAttribute(this.interpret(arg), attribute));
          } else {
            result[key] = this.useAttribute(this.interpret(relation[key].replace(/%(\w+)/g, (match, param) => args[~~(~param + 1))])), attribute);
          }
        }
      }
      return result;
    });
  }

  defineModify(name, modify) {
    this.defineFunction(name, (...args) => {
      const result = {};
      for (const key in modify) {
        if (modify.hasOwnProperty(key)) {
          if (Array.isArray(modify[key])) {
            result[key] = args.filter((arg, index) => modify[key].includes(index));
          } else {
            result[key] = this.interpret(modify[key].replace(/%(\w+)/g, (match, param) => args[~~(~param + 1))]));
          }
        }
      }
      return result;
    });
  }

  defineNewWord(name, value) {
    this.define(name, value);
  }
}

const lumina = new Lumina();

lumina.define('hello', 'world');
lumina.defineFunction('greet', (name) => `Hello, ${name}!`);

lumina.defineTemplate('person', {
  name: '%1',
  age: '%2'
});

lumina.defineRelation('friends', [
  [0, 'Alice'],
  [1, 'Bob']
]);

lumina.defineAttribute('Alice', 'age', 25);
lumina.defineAttribute('Bob', 'age', 30);

lumina.defineFunction('averageAge', (...friends) => friends.map(friend => lumina.useAttribute(friend, 'age')).reduce((sum, age) => sum + age, 0) / friends.length);

lumina.defineRelationAttribute('friendsOfAlice', 'friends', 'name', 'Alice');

lumina.defineModify('oldFriends', [
  [0]
]);

console.log(lumina.interpret('greet %1'));
console.log(lumina.interpret('person %1 30'));
console.log(lumina.interpret('averageAge %0'));
console.log(lumina.interpret('friendsOfAlice'));
console.log(lumina.interpret('oldFriends %0'));

This implementation provides basic features such as defining words and functions, templates for creating instances with attributes, relations between words, attribute accessors, relation attribute accessors, and modify operations that can be used to filter or transform lists of related words.

The interpret method is the main entry point for executing expressions in Lumina. It splits the expression into parts and tries to find a function or word matching the first part, then executes it with the remaining parts as arguments.

The language is designed to be extensible, allowing you to define new functions, templates, relations, attributes, and modify operations on the fly.

This implementation is still quite basic, but it should give you an idea of how Lumina can be used. You can extend this code in many ways to make it more powerful and expressive.

Let's create a programming language inspired by natural language, with a vocabulary and syntax that are easy to understand and use. We'll call it "Lingua".

Vocabulary

We'll start with a set of basic words that can be combined to form more complex expressions. These words will have specific meanings in the context of programming.

  1. Entities: thing, being, entity - represents an object, value, or concept
  2. Actions: make, do, act - performs an operation on one or more entities
  3. Attributes: has, with, of - describes the properties of an entity
  4. Relationships: link, bind, tie - establishes connections between entities
  5. Operations: change, modify, alter - transforms an entity in some way

Syntax

The basic syntax will be a sequence of words, where the order matters, separated by spaces.

Example: make thing with color red and shape circle

In this example:

  • make is the action (verb) that creates or initializes something
  • thing is the entity being created
  • with is an attribute (preposition) that describes the properties of the entity
  • color red are the attributes (noun phrase) that define a specific color
  • and shape circle are additional attributes that further describe the entity

Interpretation

We can now write an interpreter in JavaScript to evaluate Lingua expressions. We'll use a simple recursive descent parser to analyze the syntax and map it to meaningful constructs.

Here's a basic implementation:

function parse(input) {
  const words = input.split(' ');
  return parseExpression(words);
}

function parseExpression(words) {
  if (words[0] === 'make') {
    return makeEntity(parseAttributes(words.slice(1)));
  } else {
    throw new Error(`Unsupported expression: ${words.join(' ')}`);
  }
}

function parseAttributes(attributesWords) {
  const attributes = {};
  for (let i = 0; i < attributesWords.length; i += 2) {
    const attrName = attributesWords[i];
    const attrValue = attributesWords[i + 1];
    attributes[attrName] = attrValue;
  }
  return attributes;
}

function makeEntity(attributes) {
  // Create a new entity object with the given attributes
  const entity = {};
  Object.keys(attributes).forEach((key) => {
    if (key === 'color' && attributes[key] === 'red') {
      // Special handling for color red
      entity.color = '#FF0000';
    } else if (key === 'shape' && attributes[key] === 'circle') {
      // Special handling for shape circle
      entity.shape = 'circle';
    } else {
      entity[key] = attributes[key];
    }
  });
  return entity;
}

const input = `make thing with color red and shape circle`;
console.log(parse(input));

This is a very basic implementation, but it demonstrates the core ideas of Lingua. We can now build upon this foundation to add more features, such as:

  • Support for relationships between entities
  • Operations that modify or transform entities
  • A way to name and reference entities
  • A system for defining custom attributes and operations

By focusing on a natural language-inspired syntax and vocabulary, we can create a programming language that is easy to use and understand, while still being powerful enough to model complex systems.

dreaming a new programming language, but I want it to be like a subset of natural or constructed langauge.. text based, imagine a vocabulary that could become like our kind of english brainfucked, make sure to map all the words to meaningful constructs, think of it like an abelian group where operations in words return words, don't worry about printing text, just focus on making it meaningful /useful to program in, in the sense of being turing complete and making it easy to model arbitrary things, define and use functions or function like macros or templates, don't wory about being dense or long, do not make it math oriented, but we should be able to be math-like. make it possible to name things, give them attributes, relate them, use them, return to them, and make things that modify other things passively, think about langauge like a runtime not a program, do not make enums for all the options, we must be able to rotate and grow and learn, so make the constructs general.. do not replicate natural language in a representative sense, operate rather in a self-consecrative sense where we relate things to our dynamic and made constructs which can be combined into useful things, the language must self-relate not world relate or data-relate but only to its own constructs, we should be able to define new words and their grammars in the language that we use, don't discuss it write working code that implements the ideas in a creative unified rethought take, words do things to each other, they don't do things with a definition so relate them functionally interior to the language.. I guess plainly make a minimal metalanguage engine

What a fascinating project! I'll guide you through the design and implementation of your programming language in Rust.

Language Design

Before diving into code, let's define the key concepts:

  1. Words: The basic units of your language. Each word will be mapped to a meaningful construct.
  2. Constructs: These are the building blocks for creating more complex expressions. They can represent data types, functions, or operations.
  3. Composition: Words and constructs can be combined in various ways to create new meanings.

Here's an initial set of words and their corresponding constructs:

  • is (Equality): a binary operator that checks if two values are equal
  • in (Containment): a binary operator that checks if one value is contained within another
  • as (Type Conversion): a unary operator that converts a value to a specific type
  • a (Attribute): a keyword for defining attributes on words or constructs
  • of (Composition): a binary operator that combines two constructs into a new one

Rust Implementation

Create a new Rust project and add the necessary dependencies:

[dependencies]
hashmap = "0.7.5"
serde = { version = "1.0", features = ["derive"] }

Design an enum for your words, each with its corresponding construct:

#[derive(Serialize, Deserialize)]
enum Word {
    Equality,
    Containment,
    TypeConversion,
    Attribute(String),
    Composition(Box<Word>, Box<Word>),
}

impl Word {
    fn new(name: &str) -> Self {
        match name {
            "is" => Word::Equality,
            "in" => Word::Containment,
            "as" => Word::TypeConversion,
            "a" => panic!("Attribute is a keyword, cannot be used as a word"),
            _ => Word::Composition(Box::new(Word::from_str(name)), Box::new(Word::from_str(""))),
        }
    }

    fn from_str(s: &str) -> Self {
        match s {
            "is" | "in" | "as" => panic!("These are reserved words"),
            _ => Word::Attribute(s.to_string()),
        }
    }
}

Implement a struct to represent the runtime environment:

struct Context {
    attributes: HashMap<String, Box<Word>>,
}

impl Context {
    fn new() -> Self {
        Context { attributes: HashMap::new() }
    }

    fn set_attribute(&mut self, name: &str, word: Word) {
        self.attributes.insert(name.to_string(), Box::new(word));
    }

    fn get_attribute(&self, name: &str) -> Option<&Word> {
        self.attributes.get(name).map(|v| v.as_ref())
    }
}

Now, let's create a simple interpreter that can execute basic expressions:

fn interpret(ctx: &mut Context, words: &[Word]) -> Box<Word> {
    match words[0] {
        Word::Equality => {
            if words.len() != 3 {
                panic!("is operator requires two operands");
            }
            let lhs = ctx.get_attribute(&words[1].to_string()).cloned().unwrap_or_else(|| panic!("lhs is not an attribute"));
            let rhs = ctx.get_attribute(&words[2].to_string()).cloned().unwrap_or_else(|| panic!("rhs is not an attribute"));
            if &*lhs == &*rhs {
                Box::new(Word::Attribute("true".to_string()))
            } else {
                Box::new(Word::Attribute("false".to_string()))
            }
        }
        Word::Containment => {
            // Implement containment logic here
            unimplemented!()
        }
        Word::TypeConversion => {
            if words.len() != 3 {
                panic!("as operator requires two operands");
            }
            let lhs = ctx.get_attribute(&words[1].to_string()).cloned().unwrap_or_else(|| panic!("lhs is not an attribute"));
            Box::new(Word::Attribute(format!("{} as {}", &*lhs, words[2]).to_string()))
        }
        Word::Attribute(_) => {
            if words.len() != 2 {
                panic!("a operator requires one operand");
            }
            let word = ctx.get_attribute(&words[1].to_string()).cloned().unwrap_or_else(|| panic!("operand is not an attribute"));
            word
        }
        Word::Composition(a, b) => Box::new(Word::Composition(a.clone(), b.clone())),
    }
}

This is a basic starting point. You can add more functionality and logic for each construct as you see fit.

Next Steps

  1. Implement the containment logic.
  2. Add support for defining functions or macros (similar to fn or macro in other languages).
  3. Develop a way to handle errors and exceptions.
  4. Create a parser that can convert text input into your Word enum representation.
  5. Experiment with using this language to model real-world problems or simple algorithms.

Remember, the key is to create a language that's meaningful and useful for programming, while still being flexible and expressive. Good luck!

Here is an attempt at creating a programming language that operates on the principles you've outlined. I'll provide the code and explain the decisions behind it:

// The Languange's core vocabulary (constructs)
const primitives = {
  'a': { type: 'atom' }, // Atomic value, can be any type
  'b': { type: 'binop', op: (x, y) => x + y }, // Binary operation + (can be extended to other ops and types)
  'c': { type: 'func', args: ['x'], body: 'a(x)' }, // Function a that takes one argument x
  'd': { type: 'rel', props: ['p', 'q'] }, // Relation with properties p and q (can be extended to more properties)
  'e': { type: 'func', args: [], body: 'b(a, b)' } // Macro-like function e that expands to a binary operation
};

// Factory functions for constructing new vocabulary
function atom(name, value) {
  return { name, type: 'atom', value };
}

function binop(name, op) {
  return { name, type: 'binop', op };
}

function func(name, args, body) {
  return { name, type: 'func', args, body };
}

function rel(name, props) {
  return { name, type: 'rel', props };
}

function macro(name, expansion) {
  return { name, type: 'macro', expansion };
}

// Core interpreter functions
function evaluate(ast, ctx) {
  if (ast.type === 'atom') {
    return ast.value;
  } else if (ast.type === 'binop') {
    const x = evaluate(ctx[ast.args[0]], ctx);
    const y = evaluate(ctx[ast.args[1]], ctx);
    return ast.op(x, y);
  } else if (ast.type === 'func') {
    const args = ast.args.map(arg => evaluate(ctx[arg], ctx));
    return eval(ast.body, { ...ctx, [ast.name]: ...args });
  } else if (ast.type === 'rel') {
    // Relation handling (e.g., identity relation)
    return;
  } else if (ast.type === 'macro') {
    const args = ast.args.map(arg => evaluate(ctx[arg], ctx));
    return eval(ast.expansion, { ...ctx, [ast.name]: ...args });
  }
}

function define(name, ast) {
  primitives[name] = ast;
}

// Example usage
define('x', atom('x', 2));
define('y', atom('y', 3));
define('add', binop('add', (x, y) => x + y));
define('double', func('double', ['x'], 'a(x)*2'));
define('even', rel('even', ['n']));
define('identity', macro('id', 'b(a, a)'));

// Example program
const prog = `even(x) && (d(p: id, q: x))`;
evaluate(primitives[prog], {});

// Output:
// even(4) && (d(p:id, q: 4))

In this code:

  • The primitives object defines the initial vocabulary of the language.
  • Factory functions are provided to create new constructs, such as atoms, binary operations, functions, relations, and macros.
  • The evaluate function is the core interpreter that evaluates a given Abstract Syntax Tree (AST) in the context of an execution environment (ctx).
  • The define function adds new constructs to the language's vocabulary.

This language aims to be simple yet expressive, allowing for the definition of new functions, relations, and macros. It provides a foundation for building complex concepts by combining basic primitives. Note that this is just one possible implementation, and you may want to add more features or modify the existing ones to better fit your vision.

@SaulDoesCode
Copy link
Author

I'll create a prototype of this language, called "Metaflora". It's designed to be flexible, self-referential, and easy to extend. The goal is to provide a metalanguage engine that can evolve alongside the user.

Here's a starting point:

// Basic structure: A Node has properties (a map) and methods (functions)
const Node = (name, props, methods) => {
  const node = { name, props: Object.freeze(Object.assign({}, props)), methods };
  
  // Method to get the value of a property or method by name
  node.get = key => node.props[key] || node.methods[key];
  
  // Method to set the value of a property
  node.set = (key, value) => {
    if (!node.props.hasOwnProperty(key)) throw new Error(`Property '${key}' does not exist.`);
    node.props[key] = value;
  };
  
  return node;
};

// Operator Overloading: Define how Nodes interact with each other.
const operators = {
  'in': (a, b) => Object.keys(b.props).some(k => a.props[k] === b),
  'and': (a, b) => Node('and', { ...a.props, ...b.props }, {}),
  'or': (a, b) => Node('or', { ...a.props, ...b.props }, {}),
  '=>': (a, b) => a.get(b.name), // Function application
};

// Grammar: Define how Nodes can be combined to form new expressions.
const grammar = {
  'Node' : { type: 'leaf', value: Node },
  'Expression' : { 
    'type' : 'composite',
    'children' : [
      { type: 'choice', options: ['Node', 'Operator'] },
      { type: 'optional', child: 'Term' },
    ],
  },
  'Term' : {
    'type' : 'composite',
    'children' : [
      { type: 'choice', options: ['Node', 'Application'] },
      { type: 'optional', child: 'Expression' },
    ],
  },
  'Operator' : { 
    'type' : 'leaf', 
    value: k => operators[k] 
  },
  'Application' : {
    'type' : 'composite',
    'children' : [
      { type: 'choice', options: ['Node', 'Expression'] },
      { type: 'required', child: 'Operator' },
      { type: 'optional', child: 'Term' },
    ],
  },
};

// Parsing: Create a parser to build an Abstract Syntax Tree (AST) from the input.
const parse = input => {
  const ast = {};
  const stack = [ast];
  
  for (let i = 0; i < input.length; i++) {
    let token;
    
    if (/^[\w_]+$/.test(input[i])) { // Node name
      token = { type: 'Node', value: input[i] };
    } else if (/^[a-z]+$/i.test(input[i])) { // Operator
      token = { type: 'Operator', value: input[i] };
    }
    
    while (stack.length) {
      const current = stack.pop();
      
      if (token) {
        if (token.type === 'Node') {
          current.children = current.children || [];
          current.children.push(token);
        } else if (token.type === 'Operator') {
          if (!current.value) {
            current.value = token;
          } else {
            const newNode = Node('Application', {}, { apply: (a, b) => a.methods.apply(a, [b]) });
            newNode.props = { ...newNode.props, ...Object.fromEntries(stack.map(node => [node.value.name, node])) };
            stack.push(newNode);
          }
        }
      } else {
        if (!current.children || !current.children.length) continue;
        
        let child;
        switch (grammar.Expression.children[0].type) {
          case 'choice':
            if (grammar.Expression.children[1] && grammar.Expression.children[1].optional) {
              child = current.children.shift();
              stack.push({ children: [] });
              parseExpression(child.value);
              stack.pop().children.push(...current.children);
            } else {
              child = current.children.shift();
              parseTerm(child.value);
            }
            break;
          case 'required':
            child = current.children.shift();
            parseTerm(child.value);
            break;
        }
      }
    }
  }
  
  function parseExpression(value) {
    const ast = stack.pop();
    if (!ast.value) return;
    
    let node;
    switch (ast.value.type) {
      case 'Node':
        node = Node(ast.value.name, {}, {});
        break;
      case 'Operator':
        node = operators[ast.value.value];
        break;
      default:
        throw new Error('Unknown token type.');
    }
    
    ast.value = { ...Object.fromEntries(stack.map(node => [node.name, node])), apply: (a, b) => a.methods.apply(a, [b]) };
  }
  
  function parseTerm(value) {
    const ast = stack.pop();
    if (!ast.value || !ast.children.length) return;
    
    let child = ast.children.shift();
    parseExpression(child.value);
    
    if (!ast.value.children) {
      ast.value = Node('Application', { ...Object.fromEntries(stack.map(node => [node.name, node])), apply: (a, b) => a.methods.apply(a, [b]) }, {});
    }
  }
  
  return ast;
};

// Usage
const code = 'x in y and z or w => x.y';
parse(code);

This prototype defines the basic structure of a Node with properties and methods. It also provides operator overloading for common logical operations (in, and, or) and function application (=>). The grammar is defined using a simple recursive descent parser. The language allows for combining Nodes in various ways, including applications and logical operations.

Feel free to modify or extend this prototype as needed. I'll be happy to help with any questions or suggestions you have!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment