diff --git a/README.md b/README.md index 6807210..a89ed71 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,35 @@ # lunary +> [!WARNING] +> This project is an experiment and should not be relied on in any production environment ever! + +### About + +Lunary is a dynamically-typed, procedural, interpreted scripting toy language with expressive syntax focused on composability and functional ergonomics. It currently runs on an Elixir backend (BEAM VM runtime), with the eventual goal being to port execution to LLVM. + +### Requirements + +- [Erlang/OTP](https://www.erlang.org/) >= 27 +- [Elixir](https://elixir-lang.org/) >= 1.18 + +### Quickstart + +#### Build lunary binary and run the REPL ```bash mix escript.build && mix repl ``` -## Installation +#### Build demo project (HTML resume builder) -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `lunary` to your list of dependencies in `mix.exs`: +You will need to install [WeasyPrint](https://weasyprint.org/) if you want to render the resume to a PDF. -```elixir -def deps do - [ - {:lunary, "~> 0.1.0"} - ] -end -``` +```bash + mix escript.build -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at . + ./lunary examples/resume/resume.lun > examples/resume/resume.html | weasyprint - examples/resume/resume.pdf --media-type print --encoding utf-8 +``` +#### Run test suite +```bash + mix test +``` diff --git a/example.lun b/example.lun deleted file mode 100644 index 1a00c44..0000000 --- a/example.lun +++ /dev/null @@ -1 +0,0 @@ -val = "test" \ No newline at end of file diff --git a/examples/resume/fonts/MonaspaceArgon-Regular.woff2 b/examples/resume/fonts/MonaspaceArgon-Regular.woff2 new file mode 100644 index 0000000..1d0207f Binary files /dev/null and b/examples/resume/fonts/MonaspaceArgon-Regular.woff2 differ diff --git a/examples/resume/fonts/MonaspaceNeon-Regular.woff2 b/examples/resume/fonts/MonaspaceNeon-Regular.woff2 new file mode 100644 index 0000000..005fa27 Binary files /dev/null and b/examples/resume/fonts/MonaspaceNeon-Regular.woff2 differ diff --git a/examples/resume/fonts/MonaspaceNeon-Var.woff2 b/examples/resume/fonts/MonaspaceNeon-Var.woff2 new file mode 100644 index 0000000..9b7d48d Binary files /dev/null and b/examples/resume/fonts/MonaspaceNeon-Var.woff2 differ diff --git a/examples/resume/icons/github.svg b/examples/resume/icons/github.svg new file mode 100644 index 0000000..cf8ed84 --- /dev/null +++ b/examples/resume/icons/github.svg @@ -0,0 +1 @@ + diff --git a/examples/resume/icons/globe.svg b/examples/resume/icons/globe.svg new file mode 100644 index 0000000..6522f68 --- /dev/null +++ b/examples/resume/icons/globe.svg @@ -0,0 +1 @@ + diff --git a/examples/resume/icons/linkedin.svg b/examples/resume/icons/linkedin.svg new file mode 100644 index 0000000..fa50df7 --- /dev/null +++ b/examples/resume/icons/linkedin.svg @@ -0,0 +1 @@ + diff --git a/examples/resume/resume.html b/examples/resume/resume.html new file mode 100644 index 0000000..13dc45d --- /dev/null +++ b/examples/resume/resume.html @@ -0,0 +1,523 @@ + + + + + Lunary Resume! + + + + + + +
+
+
Generated with Lunary, my custom built programming language
+
+
+
+ +
+
+
+

Dennis Pacewicz

+

Software Engineer

+
+ +
+
+ +
+

Contact

+ +
+ +
+

About

+
Software Engineer with experience delivering high-impact initiatives at global companies. Thriving in environments that require technical creativity and multidisciplinary collaboration to solve complex problems. Passionate about learning; everything from compiler design to sailing.
+
+ +
+

Experience

+
    +
  • +
    +

    GitHub

    +
    +
    February 2022 - Present
    +
    +
    +
      +
    • Senior Product Security Engineer
    • +
    + +
    +
    Technical lead within the Product Security Engineering team, specializing in architecting secure "paved paths" across the SDL to scale systemic protections while enhancing developer experience.
    + +
    +
    • Ruby
    • Rails
    • React
    • Copilot
    • Actions
    • Kubernetes
    • CodeQL
    • Kusto
    +
    +
  • + +
  • +
    +

    Shopify

    +
    +
    January 2017 - August 2022
    +
    +
    +
      +
    • Senior Developer (2021 - 2022)
    • Developer (2018 - 2021)
    • Developer Intern (2017)
    • +
    + +
    +
    Core contributor to Shopify's App Systems and Theme Store, focused on enabling platform growth while maintaining merchant security and trust.
    + +
    +
    • Ruby
    • Rails
    • React
    • GraphQL
    • MySQL
    • Elasticsearch
    +
    +
  • +
+
+ +
+

Community

+
    +
  • +
    +

    RubyKaigi

    +
    +
    April 2025
    +
    +
    +
      +
    • Featured Speaker - RubyKaigi 2025 (Matsuyama, Japan)
    • +
    + +
    +
    Co-authored and presented "Keeping Secrets: Lessons Learned From Securing GitHub" alongside security researcher Wei Lin Ngo.
    + +
    +
      +
      +
    • + +
    • +
      +

      Hack the 6ix

      +
      +
      August 2017 - December 2019
      +
      +
      +
        +
      • Director of Engineering
      • +
      + +
      +
      Led the engineering team for Toronto’s largest hackathon, growing the team and building out custom event management tooling, supporting 1000+ hackers.
      + +
      +
        +
        +
      • +
      +
      + +
      +

      Education

      +
        +
      • +
        +

        University of Ontario Institute of Technology

        +
        +
        2013 - 2018
        +
        +
        +
        B.Sc. in Computer Science
        +
        +
      • +
      +
      + +
      +

      Interests

      +
        +
      • +

        Outdoors

        +
          +
        • Sailing
        • Backwoods camping
        • Mountain biking
        • +
        +
      • + +
      • +

        Creative

        +
          +
        • Music production
        • 3D modelling
        • CNC / Digital manufacturing
        • +
        +
      • +
      +
      + +
      + + diff --git a/examples/resume/resume.lun b/examples/resume/resume.lun new file mode 100644 index 0000000..524a1c3 --- /dev/null +++ b/examples/resume/resume.lun @@ -0,0 +1,276 @@ +# Lunary HTML Resume Demo + +# TODO +# - multiline strings are weird and broken +# - raw string literals would be nice to have +# - modules loading is unfinished (modules other than @kernel must be inlined for now) + +mod @resume ( + fn page (map) -> ( + map at :sections <- () + ) + fn styles (map, styles) -> ( + map at :styles <- styles + ) + fn header (map, header) -> ( + map at :header <- header + ) + fn section (map, key, data) -> ( + section = (label: "Error", content: "Unknown section type!", style: "color: red;") + section = (label: data.label, content: "
      #{data.content}
      ", style: data.style) if data.type == :text + section = (label: data.label, content: "", style: data.style) if data.type == :projects + section = (label: data.label, content: "", style: data.style) if data.type == :education + section = (label: data.label, content: render_list(data), style: data.style) if data.type == :list + section = (label: data.label, content: render_categories(data), style: data.style) if data.type == :categories + + sections = map at :sections at key <- section + map = map at :sections <- sections + ) + fn render_list(data) -> ( + rendered = "" + for item in (:content from data) -> ( + rendered = "#{rendered}
    • #{(:label from item)}
      #{(:value from item)}
    • " + ) + rendered + "" + ) + fn render_categories(data) -> ( + rendered = "" + for category in (:content from data) -> ( + values = "" + for value in (:value from category) -> ( + values = "#{values}
    • #{value}
    • " + ) + rendered = "#{rendered} +
    • +

      #{(:category from category)}

      + +
    • + " + ) + "" + ) + fn render_projects(data) -> ( + rendered = "" + for project in (:content from data) -> ( + titles = nil + for title in (:titles from project) -> ( + titles = "#{titles}
    • #{title}
    • " + ) + highlights = nil + highlights_data = (:highlights from project) + highlights_data_present = true if highlights_data + highlights_data = [] unless highlights_data_present + for highlight in highlights_data -> ( + highlights = "#{highlights}
    • #{highlight}
    • " + ) + highlights_section = " +
      +

      Highlights

      + +
      + " if highlights_data_present + highlights_section = "" unless highlights_data_present + tools = nil + tools_data = (:tools from project) + tools_data = [] unless tools_data + for tool in tools_data -> ( + tools = "#{tools}
    • #{tool}
    • " + ) + project_website = :website from project + rendered = "#{rendered} +
    • +
      +

      #{:name from project}

      +
      +
      #{:date from project}
      +
      +
      +
        + #{titles} +
      + +
      +
      #{:description from project}
      + #{highlights_section} +
      +
        #{tools}
      +
      +
    • + " + ) + rendered + ) + fn render_education(data) -> ( + rendered = "" + for education in (:content from data) -> ( + rendered = "#{rendered} +
    • +
      +

      #{(education at :name)}

      +
      +
      #{(education at :date)}
      +
      +
      +
      #{(education at :title)}
      +
      +
    • + " + ) + rendered + ) + fn render (data) -> ( + rendered_sections = "" + rendered_links = "" + header = data at :header + links = header at :links + for link in links -> ( + rendered_links = "#{rendered_links} +
      +
      #{link at :icon}
      + #{link at :label} +
      + " + ) + rendered_sections = "#{rendered_sections} +
      +
      +
      +

      #{header at :name}

      +

      #{header at :title}

      +
      +
      + #{rendered_links} +
      +
      +
      + " + sections = data at :sections + for section in sections -> ( + rendered_sections = "#{rendered_sections} +
      +

      #{(section from sections) at :label}

      +
      #{(section from sections) at :content}
      +
      + " + ) + styles = data at :styles + fonts = data at :fonts + + " + + + + #{data.title} + + + + + + +
      +
      +
      Generated with Lunary, my custom built programming language
      +
      +
      +
      + #{rendered_sections} +
      + + " + ) +) + +@resume.page(( + title: "Lunary Resume!" +)) +|> @resume.styles(@kernel.load_raw("examples/resume/style.css")) +|> @resume.header(( + name: "Dennis Pacewicz", + title: "Software Engineer", + links: [ + (label: "GitHub", url: "https://github.com/lyninx", icon: @kernel.load_raw("examples/resume/icons/github.svg")), + (label: "LinkedIn", url: "https://linkedin.com/in/dennispacewicz", icon: @kernel.load_raw("examples/resume/icons/linkedin.svg")) + ] +)) +|> @resume.section(:@01_contact, ( + type: :list, + label: "Contact", + content: [ + (label: "Email", value: "redacted@email"), + (label: "Phone", value: "redacted") + ] +)) +|> @resume.section(:@02_about, ( + type: :text, + label: "About", + content: "Software Engineer with experience delivering high-impact initiatives at global companies. Thriving in environments that require technical creativity and multidisciplinary collaboration to solve complex problems. Passionate about learning; everything from compiler design to sailing." +)) +|> @resume.section(:@03_experience, ( + type: :projects, + label: "Experience", + content: [ + ( + name: "GitHub", + titles: ["Senior Product Security Engineer"], + date: "February 2022 - Present", + website: (label: "github.com", url: "https://github.com"), + description: "Technical lead within the Product Security Engineering team, specializing in architecting secure \"paved paths\" across the SDL to scale systemic protections while enhancing developer experience.", + tools: ["Ruby", "Rails", "React", "Copilot", "Actions", "Kubernetes", "CodeQL", "Kusto"] + ), + ( + name: "Shopify", + titles: ["Senior Developer (2021 - 2022)", "Developer (2018 - 2021)", "Developer Intern (2017)"], + date: "January 2017 - August 2022", + website: (label: "shopify.com", url: "https://shopify.com"), + description: "Core contributor to Shopify's App Systems and Theme Store, focused on enabling platform growth while maintaining merchant security and trust.", + tools: ["Ruby", "Rails", "React", "GraphQL", "MySQL", "Elasticsearch"] + ) + ] +)) +|> @resume.section(:@04_community, ( + type: :projects, + label: "Community", + content: [ + ( + name: "RubyKaigi", + titles: ["Featured Speaker - RubyKaigi 2025 (Matsuyama, Japan)"], + date: "April 2025", + website: (label: "rubykaigi.org", url: "https://rubykaigi.org/2025/presentations/lyninx"), + description: "Co-authored and presented \"Keeping Secrets: Lessons Learned From Securing GitHub\" alongside security researcher Wei Lin Ngo." + ), + ( + name: "Hack the 6ix", + titles: ["Director of Engineering"], + date: "August 2017 - December 2019", + website: (label: "hackthe6ix.com", url: "https://hackthe6ix.com/"), + description: "Led the engineering team for Toronto’s largest hackathon, growing the team and building out custom event management tooling, supporting 1000+ hackers." + ) + ] +)) +|> @resume.section(:@05_education, ( + type: :education, + label: "Education", + content: [ + ( + name: "University of Ontario Institute of Technology", + title: "B.Sc. in Computer Science", + date: "2013 - 2018" + ) + ] +)) +|> @resume.section(:@06_interests, ( + type: :categories, + label: "Interests", + content: [ + (category: "Outdoors", value: ["Sailing", "Backwoods camping", "Mountain biking"]), + (category: "Creative", value: ["Music production", "3D modelling", "CNC / Digital manufacturing"]) + ] +)) +|> @resume.render() diff --git a/examples/resume/resume.pdf b/examples/resume/resume.pdf new file mode 100644 index 0000000..89b9229 Binary files /dev/null and b/examples/resume/resume.pdf differ diff --git a/examples/resume/style.css b/examples/resume/style.css new file mode 100644 index 0000000..92ed1d8 --- /dev/null +++ b/examples/resume/style.css @@ -0,0 +1,339 @@ +@font-face { + font-family: 'Monaspace Neon'; + src: url('./fonts/MonaspaceNeon-Var.woff2') format('woff2-variations'); + font-weight: 600; + font-style: normal; +} +@page { + size: A4; + margin: 40px 0; + margin-top: 40px; + padding: 0; + @top-left { content: none; } + @top-center { content: none; } + @top-right { content: none; } + @bottom-left { content: none; } + @bottom-center { content: none; } + @bottom-right { content: none; } +} + +@page :first { + margin-top: 0; +} + +@media print { + .underlay { + position: absolute; + } + .content-layer { + margin-top: 0; + } + .wrap { + padding-right: 32px; + } +} + +body { + font-family: Inter, sans-serif; + margin: 0; +} + +* { + break-inside: auto; + break-before: auto; + break-after: auto; +} + +header { + padding: 32px 0; + margin: 8px 0; + background: #f5f5f5; + break-after: avoid; +} +header .wrap { + display: flex; + justify-content: space-between; + gap: 32px; +} +header .main { + margin-left: calc(160px - 16px); + padding-left: 24px; +} +header .main h1 { margin: 0 0 8px 0; } +.label { + color: #ffe06b; +} + +.list-section { + list-style: none; + padding: 0; + margin: 0; + display: grid; + grid-template-columns: 1fr 1fr; +} +.categories-section li, .list-section li { + font-size: 14px; + line-height: 1.5; + margin-bottom: 0; + font-weight: 600; + color: #111; + break-inside: avoid; +} +.list-section a { + text-decoration: none; + border-bottom: 1px solid #36c3be; + font-weight:400; + color: inherit; +} + +.categories-section { + list-style: none; + padding: 0; + margin: 0; + column-count: 2; + column-gap: 24px; +} +.categories-section h4 { + font-size: 18px; + font-weight: 600; +} +section .content .categories-section ul { + margin: 0 0 0 16px; +} +.categories-section > li ul li { + font-size: 14px; + line-height: 1.6; + color: #444; + font-weight: 400; + margin-left: 16px; +} + +section { + display: grid; + grid-template-columns: 160px 1fr; + break-inside: auto; +} +section .label { + text-align: right; + padding: 8px 16px 8px 0; +} +section .content { + margin-top: 4px; + padding: 4px; + font-weight: 400; +} +section .content .paragraph { + font-size: 14px; + line-height: 1.5; + margin-top: 2px; + color: #444; +} +section .content .name { + font-size: 16px; + font-weight: 600; + white-space: nowrap; +} +section .content .separator { + border-top: 2px dotted #eee; + width: 100%; + position: relative; +} +section .content .date { + margin-left: auto; + font-size: 14px; + font-weight: 600; + color: #95a5a6; + white-space: nowrap; +} +section .content .description { + font-size: 14px; + line-height: 1.5; + margin: 4px 0; + color: #444; +} +section .content ul { + list-style: none; + padding: 0; + margin: 0; +} +section .content > ul:not(.list-section) > li { + margin-bottom: 8px; +} +section .content .project-header, section .content .education-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4px; + margin-top: 1px; + gap: 16px; +} + +section .content .project-header .name, section .content .education-header .name { + font-size: 18px; + font-weight: 600; +} +section .content .titles { + display: flex; + justify-content: space-between; + margin: 4px 0 0 0; + line-height: 1.6; + font-weight: 400; +} +section .content .titles h5 { + font-weight: 600; + color: #111; +} +section .content .website { + font-size: 14px; + line-height: 1.4; + font-weight: 500; + color: #36c3be; + white-space: nowrap; +} +section .content .website a { + text-decoration: none; + color: inherit; +} +section .content .highlights { + margin-top: 8px; + color: #444; +} + +section .content .highlights ul { + list-style: none; + margin: 8px 0 8px 16px; +} +section .content .highlights li { + font-size: 14px; + margin-left: 16px; + margin-bottom: 4px; + line-height: 1.5; + color: #444; +} +section .content .highlights li::before, section .content .categories-section > li ul li::before { + content: url('data:image/svg+xml;utf8,'); + margin-left: -1rem; + position: absolute; +} +section .content .highlights b { + font-weight: 600; + color: #111; +} +section .content .tools { + font-size: 12px; +} +section .content .tools ul { + display: flex; +} +section .content .tools ul li { + background: #95a5a6; + color: white; + margin-right: 4px; + font-weight: 600; + padding: 4px 8px; + border-radius: 16px; +} +h1, h2, h3, h4, h5, h6 { margin: 0 0 0 0; } +.underlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + min-height: 100vh; + background: #040404; + color: #eee; + z-index: 0; + display: flex; +} +.underlay:hover { + background: #111; +} +.lunary { + font-family: 'Monaspace Neon', monospace; + font-weight: 600; + font-size: 14px; + color: #ddd; +} +.lunary .top { + margin:0 auto; + display: flex; + align-items: center; + justify-content: center; + height: 40px; +} +.lunary .top i { + display: inline-block; + font-weight: 800; + margin-left: .5rem; + font-size: 16px; + color: #87d8dd; +} +.lunary .top a { + color: inherit; + text-decoration: none; +} +/* Main content layer that scrolls over underlay */ +.content-layer { + position: relative; + z-index: 1; + margin-top: 40px; /* Same as underlay height */ + background: white; + min-height: calc(100vh - 40px); + box-shadow: 0 -4px 20px rgba(0,0,0,0.1); +} +.wrap { width: 100%; max-width: 700px; margin: 0 auto; padding: 0 16px; } +.inter { + font-family: 'Inter', sans-serif; + font-optical-sizing: auto; + font-weight: 600; + font-style: normal; +} +.monaspace { + font-family: 'Monaspace Neon', monospace; + font-weight: 600; + font-style: normal; +} +.text-grey { + color: #95a5a6; +} +.links { + padding: 0 8px; + display: flex; + flex-direction: column; + gap: 8px; + justify-content: center; +} +.link { + display: flex; + align-items: center; + gap: 6px; +} +.link .icon { + width: 14px; + height: 14px; + flex-shrink: 0; + opacity: 0.5; + transition: opacity 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} +.link .icon svg { + width: 100%; + height: 100%; + fill: currentColor; + cursor: pointer +} +.link a { + text-decoration: none; + color: #95a5a6; + font-size: 12px; + font-weight: 500; + transition: color 0.2s ease; +} +.link:hover .icon { + opacity: 1; +} +.link:hover a { + color: #36c3be; +} diff --git a/html_builder.lun b/html_builder.lun deleted file mode 100644 index 499415d..0000000 --- a/html_builder.lun +++ /dev/null @@ -1,5 +0,0 @@ - -text = "hello world" -content = "

      {text}

      " -body = "{content}" -result = "{body}" \ No newline at end of file diff --git a/lib/lunary.ex b/lib/lunary.ex index 73cae44..1d6ceee 100644 --- a/lib/lunary.ex +++ b/lib/lunary.ex @@ -439,6 +439,15 @@ end string_uri = Enum.at(args, 0) {result, _} = evaluate({:import, string_uri}, scope, opts) {result, scope} + :load_raw -> + root_path = opts[:path] || "" + {:string, _, file_path} = Enum.at(args, 0) + fullpath = Path.join(root_path, file_path) + case File.read(fullpath) do + {:ok, content} -> + {content, scope} + {:error, _} -> raise "File #{file_path} (#{fullpath}) not found" + end _ -> raise "Kernel function #{name} is not defined" end diff --git a/lib/lunary/string.lun b/lib/lunary/string.lun deleted file mode 100644 index e69de29..0000000 diff --git a/lib/main.ex b/lib/main.ex index afa0455..8318fe2 100644 --- a/lib/main.ex +++ b/lib/main.ex @@ -1,3 +1,16 @@ +# LUNARY programming language +# copyright © 2026 Dennis Pacewicz. all rights reserved. + +# TODO +# - add more elegant control flow options +# - add support for tuples? maybe? +# - error handling and stack traces +# - build standard library modules +# - add default params support +# - handle division by zero (:infinity/-:infinity/:nan) +# - fix repl (support fdef, error handling) +# - multi stage evaluator which outputs llvm IR + defmodule Lunary.Main do require IEx def main([]), do: start_repl() @@ -55,11 +68,3 @@ defmodule Lunary.Main do end end end - -# todo: -# - add control flow -# - add support for tuples? -# - add default params -# - handle division by zero (:infinity/-:infinity/:nan) -# - fix repl (support fdef, error handling) -# - multi stage evaluator which outputs llvm IR (stretch goal) diff --git a/projects/resume/main.lun b/projects/resume/main.lun deleted file mode 100644 index ce4688b..0000000 --- a/projects/resume/main.lun +++ /dev/null @@ -1,51 +0,0 @@ -# Lunary HTML Page Builder Demo - -# TO FIX -# - multiline strings are weird -# - strings without interpolation should be an option - -# Core HTML functions -mod @html ( - fn page (map) -> ( - map - ) - fn styles (map, styles) -> ( - map at :styles <- styles - ) - fn section (map, key, text, style) -> ( - section = (content: "Section: #{key} #{text}", style: style) - sections = map at :sections at key <- section - map = map at :sections <- sections - ) - fn render (data) -> ( - rendered_sections = "" - sections = data at :sections - for section in sections -> ( - rendered_sections = "#{rendered_sections}
      #{(section from sections) at :content}
      " - ) - styles = data at :styles - - "#{data.title}#{rendered_sections}" - ) -) - -mod @resume ( - fn styles -> ( - " - body { font-family: Arial, sans-serif; margin: 20px; } - div { margin-bottom: 1rem; } - " - ) -) - -# @resume.styles() - -@html.page(( - title: "Lunary HTML Example", - sections: () -)) -|> @html.styles("") -|> @html.section(:test, "meow", "color:blue;text-align:center;") -|> @html.section(:test2, "woof", "color:red;text-align:left;") -|> @html.section(:test3, "moo", "color:green;text-align:right;") -|> @html.render() diff --git a/src/lunary_parser.yrl b/src/lunary_parser.yrl index e075926..2af87b8 100644 --- a/src/lunary_parser.yrl +++ b/src/lunary_parser.yrl @@ -163,10 +163,6 @@ fparam -> identifier : '$1'. fcall -> identifier '(' fargs ')' : {fn, '$1', '$3'}. fcall -> identifier '(' ')' : {fn, '$1', []}. -% fcall -> identifier fargs : {fn, '$1', '$2'}. - -% fcall -> enum fargs : {fn, '$1', '$2'}. -% fcall -> enum '(' fargs ')' : {fn, '$1', '$3'}. fcall -> '::' identifier fargs : {const_fn, '$2', '$3'}. fcall -> '::' identifier '(' fargs ')' : {const_fn, '$2', '$4'}. @@ -180,14 +176,12 @@ for_loop -> 'for' identifier 'in' expr '->' '(' statements ')' : {for_loop, '$2' inline_assignment -> identifier '=' chain : {assign, '$1', '$3'}. inline_assignment -> identifier '=' expr : {assign, '$1', '$3'}. inline_assignment -> identifier '=' enum_assignment : {assign, '$1', '$3'}. -% assignment -> identifier '=' expr 'if' expr : {assign_if, '$1', '$3', '$5'}. + assignment -> identifier '=' chain newline : {assign, '$1', '$3'}. -% assignment -> identifier '=' chain newline : {assign, '$1', '$3'}. + assignment -> identifier '=' expr newline : {assign, '$1', '$3'}. assignment -> identifier '=' enum_assignment newline : {assign, '$1', '$3'}. -% import -> '&' identifier : {import, '$2'}. -% import -> '&' uri_path : {import, '$2'}. array -> '[' ']' : {list, []}. array -> '[' array_elements ']' : {list, '$2'}. @@ -206,7 +200,6 @@ map_elements -> map_element ',' map_elements : ['$1' | '$3']. map_elements -> map_element : ['$1']. map_elements -> newline map_element : ['$2']. -% map_element -> expr ':' anon_fdef : ['$1', '$3']. map_element -> expr ':' expr : ['$1', '$3']. uri_path -> uri : '$1'. @@ -215,17 +208,12 @@ enum_assignment -> expr at expr '<-' expr : {assign_enum, {access, '$1', '$3'}, enum -> expr at expr : {access, '$1', '$3'}. enum -> expr from expr : {access, '$3', '$1'}. -% enum -> fcall '.' identifier : {atom_access, '$1', '$3'}. enum -> expr '.' fcall : {func_access, '$1', '$3'}. enum -> expr '.' identifier : {atom_access, '$1', '$3'}. -% enum -> expr '.' expr : {access, '$1', '$3'}. -% enum -> identifier at expr : {access, '$1', '$3'}. enum -> string at expr : {access, '$1', '$3'}. enum -> template_string : '$1'. enum -> string : unwrap('$1'). -% enum -> array at expr : {access, '$1', '$3'}. enum -> array : '$1'. -% enum -> map at expr : {access, '$1', '$3'}. enum -> map : '$1'. concatenation -> expr concat expr : {concat, '$1', '$3'}. diff --git a/test/fixtures/raw.txt b/test/fixtures/raw.txt new file mode 100644 index 0000000..fe660e1 --- /dev/null +++ b/test/fixtures/raw.txt @@ -0,0 +1 @@ +this is a raw file... diff --git a/test/ideas.lun b/test/ideas.lun index 68dc7b4..5de74cb 100644 --- a/test/ideas.lun +++ b/test/ideas.lun @@ -1,9 +1,14 @@ +# rough scratch file for language syntax ideas +# most of these are not valid lunary code snippets + +# constant definition block ::( title: "default", category: "resources", math: &Maths ) +# nested function chaining /> page ::title /> page "second" |/> md "A collection of works which I vastly enjoy, some of which I consider as being *timeless*. These works have had a significant impact on my creative output, and I often turn to them for inspiration." @@ -12,20 +17,24 @@ ) +# list accessors [1, 2, 3] at 1 [1, 2, 3] at 1~2 [1, 2, 3] at [0,2] +# invoke function without params /> page ::title +# function chaining vis? |> md "visible page" |> ex ::title |> "title" +# invoke function without params and pass anonymous function /> each ::tags \> (tag) -> (tag) - +# function call returning a map \> page (param, param2) -> ( { "a":"fblock", @@ -34,21 +43,22 @@ vis? } ) +# dependency import dep math.lun -::( - defined_values: [0,1,2,3] -) - +# 0-arity function fn values -> ( [0,1,2,3,4] ) -hashmap = (a: -1, b: 2) +# map definition +map = (a: -1, b: 2) +# inline function definition add = fn (value, some) -> (value + some) some = 10 +# nested function definition map = fn (value, function) -> ( fn at (enum, position) -> (enum at position) @@ -58,6 +68,8 @@ map = fn (value, function) -> ( ) ) +# function chaining experiments + result = (a: -1, b: 2) |> @lib/map:each_value |> add_some ::max @@ -72,7 +84,7 @@ result4 = ::defined_values |> map add_one :ok +# map access experiments map = (a: "value", "bee": ) key = map at :a - diff --git a/test/unit/atom_test.exs b/test/unit/atom_test.exs index 093d49d..42ffaa2 100644 --- a/test/unit/atom_test.exs +++ b/test/unit/atom_test.exs @@ -8,5 +8,12 @@ defmodule AtomTest do val " |> Lunary.Main.eval() == :atom end + + test "can contain underscores and numbers" do + assert " + val = :atom_with_123 + val + " |> Lunary.Main.eval() == :atom_with_123 + end end end diff --git a/test/unit/kernel_test.exs b/test/unit/kernel_test.exs index d9344c9..65e8961 100644 --- a/test/unit/kernel_test.exs +++ b/test/unit/kernel_test.exs @@ -8,5 +8,12 @@ defmodule KernelTest do res.b " |> Lunary.Main.eval() == "test" end + + test "can load raw files" do + assert " + res = @kernel.load_raw(\"test/fixtures/raw.txt\") + res + " |> Lunary.Main.eval() == "this is a raw file...\n" + end end end